面试官:如何搭建一个高可用评论系统,支撑百万级用户实时互动?
面试官:如何搭建一个高可用评论系统,支撑百万级用户实时互动?
大家好,我是牛哥。
今天来跟大家好好聊聊 “怎么搭一个能扛住百万级用户实时互动的高可用评论系统”。
做技术方案前,牛哥先讲句大实话:高可用评论系统不存在"完美架构"。咱们做技术拆解的核心不是找最优解,而是找适配业务的解。
第一步:先搞懂"评论到底是个啥"
要做高可用评论系统,得先把它的 “灵魂三问” 想明白:用户发评论图什么?产品靠评论解决什么问题?技术得扛住哪些核心压力?
评论的"三重人格"
评论这东西,对不同角色来说完全是两码事——这些差异直接决定了技术方案的"优先级排序"。
对用户来说" 这是"情绪出口+决策工具,刷剧到哭点想发"太刀了!编剧出来挨打",买手机前想翻"续航到底崩不崩",所以"能顺畅发、能痛快看"是底线。
对产品来说
这是"用户真心话收集器+内容护城河",比问卷真实10倍的用户反馈都藏在评论里,千万级的UGC评论堆起来,就是用户舍不得走的"内容堡垒"。
对技术来说
这是"流量放大器+数据大杂烩",评论就像演唱会散场时的地铁站,平时空荡荡,一到点瞬间挤爆——技术方案要是没提前准备好"限流闸机"和"分流通道",系统直接就崩了。
别被"百万级"吓住:拆解开其实很"友好"
很多人看到"百万级互动"就慌了,其实把流量拆成"小零件",技术目标会清晰很多。
第一拆:流量比例 — 读多写少是铁律
如果有10万用户发评论,那就有200万用户在刷评论。读写比大概是 20:1,所以读性能是体验的关键。
第二拆:流量形态 — 不是平均发力,而是脉冲式爆发
某明星官宣恋情时1小时的流量,就顶平时10天的量,因此设计时得按"峰值流量"配资源,按"低谷流量"用资源。
第三拆:用户忍耐力——对"慢"宽容,对"崩"零容忍
点赞数从1000变成1001慢3秒?无人在意。评论区转圈圈加载3次?80%用户直接划走。所以技术方案得拎得清:先保能用,再优化实时
存储引擎选型
聊完了评论系统的特性,接下来就得明确:这些评论数据该怎么存。存储是评论系统的地基,方案选得不对,后面不管加多少功能、优化多少性能,都容易出问题。
三种存储方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 纯MySQL(关系型) | 需要保证事务、评论嵌套层级≤3 级 | 支持ACID、数据可靠;索引成熟、运维熟悉 | 单表超千万后分页慢;分库分表逻辑复杂 |
| 纯MongoDB(文档型) | 非结构化内容多、评论嵌套深 | 原生存JSON嵌套、不用拆表;写入比MySQL快30% | 深嵌套查询慢;事务支持弱 |
| MySQL+Redis(混合) | 多数评论业务 | MySQL存全量保可靠;Redis存热点扛读压 | 双写易不一致(MySQL更了Redis没更);系统复杂度高 |
其中,大部分业务场景下会选择第三种方案:MySQL+Redis 混合存储,不是 MongoDB 不好,而是它撑不起大多数业务的刚需 — 数据不丢、查得别慢。
MongoDB 深嵌套查询慢,事务能力弱,万一遇到 "评论发布必须成功" 的场景,比如电商的评价有礼,丢一条数据用户就会投诉。
反观 "MySQL+Redis" 混合存储,刚好踩中了大多数业务的痛点:
- 用 MySQL 存全量数据,不管是事务保证(评论不丢),还是简单嵌套查询(3 级以内跟帖),都能稳住,相当于给数据找了个 "安全保险柜";
- 用 Redis 存热点数据,读请求直接从 Redis 走,速度比查 MySQL 快几十倍,完美解决 "刷评论卡顿" 的问题。
具体怎么搭?
- MySQL主表存核心字段:评论ID、内容ID、用户ID、文本内容、点赞数、创建时间,就像档案库,啥都得记全。
- Redis分两块:用ZSet存"评论列表"(按内容ID分组,score是时间戳,value是评论ID,方便分页);用Hash存"评论详情"(存文本、点赞数、用户头像,查的时候一次捞全)。
这里还有个问题,双写一致性怎么破?
核心方案是先写 MySQL,确认写入成功后再删 Redis 缓存。后续用户查询时,若 Redis 中没有对应数据,会自动从 MySQL 加载最新数据并回写到 Redis。
缓存策略:别让"快"变成"坑"
解决了存得稳的问题,接下来就得追求查得快了。缓存这东西,就像冰箱,不是塞得越满越好,而是得按需存放。
大部分高并发场景都得靠"本地缓存(Caffeine)+Redis集群"的组合拳,层层减压:
本地缓存(Caffeine):扛"超热点"的第一道防线
本地缓存适合存 5 分钟内被查≥1000 次的评论,比如爆款文章前 100 条评论,它的响应速度能到微秒级,比 Redis 快 100 倍。通常配置最大 10 万条缓存,过期时间 1 小时
Redis集群:扛"次热点"的主力军
除了那些极致火爆的评论,剩下的高频访问数据由 Redis 集群承载。这里按内容 ID 进行哈希分片,通常分为 16 个分片,避免热点Key扎堆。
这里你可能会问,本地缓存和 Redis、Redis 和 MySQL 之间,数据不一致怎么办?
其实核心思路是 "抓大放小":优先保证 "最终一致",不用追求实时同步。
比如更新评论内容或点赞数时:
- 先更 MySQL 底库,确认成功后删 Redis 缓存;
- 本地缓存则靠 "定时校验 + 主动失效" 双保险 — 每 10 分钟拉取 Redis 的最新数据对比,不一致就更新,同时如果评论有修改,也会主动删除对应的本地缓存。
这样一来,就算有极短时间的旧数据,用户刷新一下就能看到最新的,既不会影响体验,也避免了复杂的同步逻辑把系统搞复杂。
分库分表:当MySQL单表撑不住时的"拆家术"
当MySQL单表数据超千万,查询就像大海捞针——全表扫描慢到崩溃。这时候就得分库分表。
三种分表策略
按内容ID哈希分片
路由公式:tableIndex = contentId % 32(分32张表)。适用于热点分散场景,比如普通内容评论。
但这招有个致命伤:爆款内容会集中在一张表。比如某热点新闻的评论占全站80%,全挤在一张表上,查询时就像"排队买春运火车票" — 排到天荒地老。
时间+ID复合分片
路由公式:tableIndex = (contentId%8)+(month%4)(共32张表)。适用于时间衰减型内容比如新闻、短视频评论,旧的内容就没人看了。
比如3月的数据在(0-7)+0=0-7表,4月在(0-7)+1=8-15表,查去年的评论直接查对应月份的表,不用扫全量。但跨月数据查询要多表聚合,需要业务层处理。
热点表单独拆分"
预设10张热点表,通过监控触发数据迁移。适用于有极端热点风险,比如明星账号、重大事件评论。实现逻辑为:
- 监控所有表的写入 QPS,当某 contentId 的 5 分钟新增评论 > 1 万,标记为 “热点”
- 将该 contentId 的评论从普通表迁移到热点表,按 user_id 哈希分 3 张表,避免单表压力
- 访问时优先查热点表,没有再查普通表
这三种策略各有侧重:按内容 ID 哈希分片适合热点分散场景,却挡不住爆款内容扎堆;时间 + ID 复合分片对时效型内容友好,单跨月查询需额外处理;热点表单独拆分则专为极端流量设计,通过动态迁移化解压力。
不过分表的核心难题不是选策略,而是无感知迁移 — 用户正在发评论,你突然说"系统维护中",体验直接崩。正确姿势是"双写+校验":
- 全量同步:先把旧表数据按新规则同步到分表;
- 双写阶段:然后应用层同时写旧表和新表;
- 校验切换:写个脚本对比新旧表数据 — 当差异率低于 0.1%,说明双写逻辑没问题。这时候把读流量切到新分表
- 停写旧表:观察一周稳定后,停写旧表,完成迁移。
全程零停机,用户完全没感知。
架构演进:从小打小闹到百万级的"成长日记"
搞定了存储、缓存和分库分表这些"积木块",接下来就得看看怎么把它们搭成"高楼大厦"了。架构不是"一次设计管三年",而是跟着业务迭代,我总结了三个阶段
1.0 单体架构
架构组成:单应用服务 + 单MySQL + 本地缓存(Caffeine)
这个架构特别适合业务初期 — 比如产品刚上线、日活在 10 万以内,评论功能只需要基础的 “发、查、点赞” 的场景。
它的优点很明确:开发快、运维简单、成本低
但一旦用户量往上涨,问题就来了:
- 数据库先扛不住了:单 MySQL 的 CPU 早高峰经常跑满;
- 单表性能崩了:当评论表数据超 200 万,分页查LIMIT 2000,10要扫半张表,用户翻页时直接吐槽 “卡成 PPT”;
- 故障面影响太大:只要应用服务器内存溢出,整个评论功能就会 “一锅端” 地挂掉
2.0 垂直拆分
当单应用撑不住时,第一步往往是"分家"。核心思路是把"发评论"和"点赞互动"拆成两个独立服务,避免互相拖累。
- 评论发布/查询服务:管发评论、删评论、查列表,核心是"数据可靠";
- 互动服务:管点赞、回复、@用户,核心是"高并发低延迟";
服务之间怎么配合?用 REST API 对接就行。比如用户点赞后,互动服务会调用评论服务的接口,同步更新评论的总点赞数。
数据层也要跟着优化:
- 搞 MySQL 读写分离,评论服务写主库,互动服务读从库;
- 缓存提效:给互动服务加 Redis 缓存点赞数,Key 用comment:like:{id},Value 存具体点赞数,设 24 小时过期,减少频繁查数据库的压力。
这个架构解决了查询性能和出故障 "一锅端" 的问题,支撑起了10-80万的日活场景,但随着业务规模扩大,新的问题又来了:跨服务调用延迟、数据一致性、代码冗余
尤其单库写入 QPS 超 5000 后,就得拆单库、往分布式架构走了。
3.0 分布式架构
要支撑 "百万用户同时互动",靠之前的架构就像 "小马拉大车",必须得搞分布式,它的思路很简单:把系统拆成几个独立又能配合的层级,形成"分层防御"的架构体系
接入层:用户请求第一关
作为用户请求进入系统的第一关,接入层的核心作用是 “过滤无效流量、精准分发请求”,从源头避免后端服务被无关请求冲击。
首先是 LVS+Nginx 负载集群:2 台 LVS 物理机负责 4 层 TCP 流量分发,把请求均匀转发到 4 台 Nginx 服务器;Nginx 再基于 7 层 HTTP 协议做精细化路由 — 根据内容 ID 识别热点内容(比如爆款文章评论),直接将这类请求导向专用集群,防止单集群因流量集中而过载。
其次是自研网关:基于 Spring Cloud Gateway 开发,集成了三大核心能力:
一是鉴权,通过校验用户 Token 和设备行为特征,拦截 90% 以上的恶意请求;
二是限流,设置单用户 1 分钟最多发 5 条评论的规则,避免突发流量压垮后端;
三是监控,每 5 秒上报一次 QPS、响应延迟等数据,为流量峰值预警提供依据。
应用层:微服务的"各司其职"
这一层是业务能力的核心载体,按 "谁擅长谁来干" 的原则,拆成了 4 个微服务
评论发布服务:专门管发评论、删评论,设计上最在意 "数据不丢",比如用 "双写 + 日志" 的方式:发评论时先写 MySQL,再记录本地事务日志,万一写库失败,定时任务会自动重试;还会把评论发布的事件发到 Kafka,让其他服务同步数据。
核心流程就是:先校验用户登录和内容是否合规→保存到 MySQL→发事件到 Kafka,一步都不能少。
评论查询服务:负责加载评论列表、查评论详情,重点抓 "查询快",搞了多级缓存:用户查评论时,先看本地 Caffeine 缓存,没有就查 Redis 的 ZSet(存评论列表,方便按时间排序)和 Hash(存评论详情),再没有才查 MySQL,查完还会把数据回写到缓存里,下次再查就快了。还加了缓存预热和自动降级机制,比如 Redis 故障了,就直接查 MySQL,保证用户能看到评论。
互动服务:管点赞、回复、@用户,核心需求是 "扛高并发",所以点赞数用了 "本地计数器 + 批量同步" 的策略:用户点赞后,先在本地缓存里加 1,每 10 秒把这些计数批量同步到 Redis,避免每次点赞都写 Redis;同时把点赞事件发到 Kafka,再批量同步到 MySQL,既保证用户看到的点赞数是实时的,又不会给数据库添太多负担。
内容审核服务:负责过滤敏感词、识别垃圾评论,用了 "预审核 + 人工复核" 的机制:先过本地敏感词库;没命中的再调用百度 AI 接口做进一步检测;95% 的正常评论能自动通过,可疑的(比如模糊的敏感内容)就推到人工队列,审核结果会发 Kafka 通知其他服务。这样既保证了审核效率,又能减少" 误判 "。
这种拆分的好处很明显:哪个服务压力大,就给哪个服务加机器。
数据层:让数据待在该待的地方
根据数据特性匹配存储工具,实现 “热数据快取、全量数据可靠、冷数据低成本” 的目标:
MySQL分库分表:按content_id%16分 16 个库,每个库再按user_id%4分 4 张表(共 64 张表),搭配 1 主 2 从架构;预设 20 张热点表,当检测到某 content_id 5 分钟新增评论超 1 万时,通过 Canal 监听 Binlog 同步数据至热点表,避免单表压力过载。
Redis Cluster:16 个分片(3 主 3 从)共 48 个节点,每个节点 8G 内存,存储三类核心数据:评论列表(ZSet)、评论详情(Hash)、点赞数(String),支撑高频读写。
冷数据处理:3 个月前的评论迁移至 TiDB,通过 Flink 实时同步到 MySQL 视图,用户查询无感知;评论中的图片 / 视频存储于 OSS,MySQL 仅保留资源 URL,既节省数据库空间,又能通过 CDN 加速访问。
这个架构的设计思想是:让合适的数据待在合适的地方。
中间件层:解决共性问题
中间件层的核心作用是解决系统里的共性问题 - 比如异步通信和全文检索,降低业务复杂度:
先说 Kafka,它是负责异步通信的消息队列,主要处理非核心流程。Kafka 主要存储三类关键消息:
comment-publish:评论发布事件(评论查询服务消费,更新Redis缓存)like-event:点赞事件(统计服务消费,算热门评论)audit-event:审核事件(内容审核服务消费,过滤垃圾评论)
再看 Elasticsearch,它是负责全文检索的工具,专门支撑 “搜商品评价” 这类场景。存储评论的全文索引时,会用 “ik_max_word” 做中文分词,确保中文搜索的准确性;索引里包含了 content_id、comment_id、评论内容等核心字段,兼顾检索效率与业务需求。
由此,我们可以总结出一条用户发评论的完整链路
- 用户发起请求:APP 提交评论内容,请求经 LVS→Nginx→网关(鉴权限流),转发至评论发布服务。
- 内容审核:发布服务调用审核服务,先过本地敏感词库,未命中则调百度 AI,通过后进入发布流程。
- 写入数据库:发布服务按分表规则写入 MySQL,记录事务日志,同时发comment-publish事件至 Kafka。
- 缓存同步:查询服务消费 Kafka 事件,更新 Redis ZSet(列表)和 Hash(详情),热点内容同步至本地 Caffeine。
- 用户查询评论:请求经网关至查询服务,依次查 Caffeine→Redis→MySQL(未命中时),返回结果给用户。
- 异常处理:主库故障时自动切从库,Redis 故障时降级至 MySQL + 本地缓存,确保核心功能可用。
整个链路从请求发起至数据返回,延迟能控制在 500ms 以内,用户几乎感觉不到等待,支撑百万级用户同时互动完全没问题。
抗风险方案
架构搭好了,并不意味着可以高枕无忧。高可用不是"不出故障",而是"故障时用户无感"。下面从流量、数据、故障三个维度,拆解如何为系统筑起抗风险防线。
一、流量洪峰:让系统“扛住冲击不崩溃”
面对突发高流量,最忌讳 “一股脑冲击核心服务”。为了分步化解流量压力,可采取“缓冲、隔离、降级” 三类策略。
1. 缓冲策略:Kafka削峰
发评论时,先同步写入MySQL确保数据可靠,再将“更新Redis、计算热门评论”等非实时需求封装成“评论发布事件”,发送到Kafka队列。这样能实现流量削峰,比如瞬时10万写QPS,Kafka可将其缓冲为2万/秒的匀速流量,避免MySQL因突发压力过载。
2. 隔离策略:给爆款内容“开小灶”
提前通过“1小时阅读超50万”等指标预判热点内容,触发隔离策略后分两层操作:
- 应用层:将该内容的评论服务调度到独立集群,为其分配翻倍的CPU与内存资源;
- 数据层:将该内容的评论表分配到专属DB节点与Redis分片,避免占用普通内容的资源。
3. 降级策略:优先保障核心功能
按压力等级预设降级策略,优先保障核心功能可用:
- 轻度压力(QPS达阈值70%):限制新用户发布频率,每1分钟最多发1条评论;
- 中度压力(QPS达阈值90%):关闭评论图片上传功能,节省带宽资源;
- 重度压力(QPS超阈值):仅返回前100条评论,并向用户提示“高峰期稍后发布”。
这套模式就像手机快没电时的低电量模式——先关后台保核心功能,总比直接关机强。
二、数据安全:让评论“一条不丢、查得够快”
数据是业务的核心,既要保证存储不丢失,也要避免因存储设计不合理拖慢查询性能。以下从发布、计数、存储三个场景,拆解数据安全方案。
1. 发布防丢:双写+日志兜底
用户点 “发布” 的瞬间,系统先把评论稳稳写入 MySQL—— 这是数据的 “底线保障”;紧接着会删掉 Redis 里对应内容的旧缓存,确保下次用户刷新能看到新评论;同时,本地会悄悄记一笔操作日志,相当于 “留个备份”。
2. 计数:异步累加不卡顿
点赞这事儿,用户最在意 “点完马上能看到数变”,但要是每次点赞都直接写数据库,高频操作下肯定卡。我们的解法是 “先缓后同步”:点赞先在本地 Caffeine 缓存里算个数,10 秒批量同步到 Redis—— 用户看的是 Redis 里的实时数,完全没延迟;再每 5 分钟把 Redis 的数据同步到 MySQL,保证最终计数准确。
3. 存储:放弃树形存储“减负担”
摒弃复杂的树形存储结构,改用“父ID+层级路径”的设计,比如 “1001.2003.3005”,一眼就知道是某条评论的三级回复。查询时通过Redis的HMGET命令批量拉取数据,由前端自行组装层级关系。
数据安全其实没什么复杂逻辑,核心就是哪里容易出问题,就针对性补哪里。
三、故障恢复:让系统“秒级止血、快速恢复”
故障躲不掉,但可以 “让它不影响用户”。以下三个方案可让系统“秒级止血、快速恢复”
1. 缓存雪崩防御
缓存最怕 “一崩全崩”—— 比如 Redis 里大量 Key 同时过期,所有请求都冲去数据库,直接就垮了。我们的应对是 “本地缓存 + Redis” 双层防护:热点评论存在本地缓存,1 小时过期,就算 Redis 挂了,本地缓存能先扛一波;非热点评论存在 Redis,但给每个 Key 的过期时间加 ±10% 的随机值,避免 “集体过期”。相当于提前筑了个 “临时堤坝”,就算 Redis 出问题,用户也不会直接面对 “评论加载失败”,给修复留足时间。
2. 数据故障:多副本+快速切换
MySQL 和 Redis 要是出问题,整个评论功能都得停,所以要给它们做了 “多重保障”:
- MySQL:采用1主2从的MGR集群,主库故障时30秒内自动切换到从库,比传统方案快10倍;
- Redis:搭建3主3从集群,配合哨兵监控,单主节点故障时可在5秒内完成从库升主切换;
- 跨机房:核心数据同步到异地机房,实现北京+上海双活,若北京机房断电,上海机房5分钟内即可接管核心服务。
3. 一键降级:运维的“救命按钮”
有时候故障比较复杂,修复需要时间,这时候不能硬扛,得 “舍小保大”。我们在运维面板里做了三级降级按钮:
- 一级降级:关闭评论发布功能,仅保留评论查看能力,优先保障阅读体验;
- 二级降级:关闭嵌套回复功能,只显示一级评论,减少查询复杂度;
- 三级降级:仅返回前100条热评,不加载历史数据,大幅降低数据库压力。
钟接管。
故障恢复的核心思路就是不求 “零故障”,但求故障发生时,用户没感觉。
总结
看到这里,你可能觉得"组件太多、细节太绕",但核心就三句话:
一是先懂业务再选技术。别上来就堆 Redis、Kafka,先分清是电商评价还是直播弹幕 —— 前者丢一条用户会投诉,要重 “数据不丢”;后者漏几条用户无感知,要重 “并发能扛”,需求不同,方案天差地别,脱离业务的技术都是白费力气。
二是接受合理 “不完美”。点赞数延迟 3 秒用户无感,但评论加载失败 3 次会直接走人。不用死磕 “全量实时同步”,优先保热评加载快、发评不卡顿,冷评慢一点没关系,多数用户更在意核心体验。
三是平时多做预案,峰值才不慌。热点隔离预留独立集群、多副本架构提前搭好,这些 “平时占资源” 的设计,到流量峰值时就是 “救命稻草”。多想想 “100 万人点赞怎么办”“MySQL 宕机咋应对”,想透这些,系统才抗揍。
最后记个小技巧:设计时多模拟 “最坏情况”,像消防演习一样提前练,上线后才能睡得香。
