面试官: 如何设计一个评论系统?

在内容类产品中,评论系统几乎是标配。无论是博客、社区、视频平台,评论都是提升互动的关键组成部分。然而,评论系统的设计并非"一套走天下",它的底层结构需要根据业务的流量体量灵活调整。

本篇文章将结合不同流量场景,从表结构设计、查询逻辑、技术选型三个维度,讨论评论功能在不同阶段的落地方案,让你轻松应对面试!


场景一:中小流量系统(单库单表 + 两级评论)

适用场景:CSDN、掘金这类文字内容为主、日活低于 10 万的站点。

表结构设计

采用单表结构支持两级评论(一级评论 + 回复),结构简单、易维护:

sql 复制代码
CREATE TABLE comment (
  id BIGINT PRIMARY KEY,
  biz_id BIGINT NOT NULL,           -- 文章、视频等业务 ID
  biz_type TINYINT NOT NULL,        -- 枚举:1=文章 2=视频
  user_id BIGINT NOT NULL,          -- 评论人
  parent_id BIGINT DEFAULT 0,       -- 父评论 ID,0 表示一级评论
  root_id BIGINT DEFAULT 0,         -- 根评论 ID,自己是一级时等于 id
  content TEXT NOT NULL,
  like_count INT DEFAULT 0,
  reply_count INT DEFAULT 0,
  status TINYINT DEFAULT 0,         -- 0=正常 1=审核中 2=已删除
  created_at DATETIME,
  INDEX idx_biz (biz_id, parent_id, id),
  INDEX idx_root (root_id),
  INDEX idx_user (user_id)
);

查询逻辑设计

查询某个业务对象的一级评论(首屏 / 下拉翻页)

sql 复制代码
SELECT * FROM comment
WHERE biz_id = #{bizId} AND parent_id = 0 AND status = 0
ORDER BY id DESC
LIMIT #{pageSize} OFFSET #{offset};

查询某条评论的回复

sql 复制代码
SELECT * FROM comment
WHERE root_id = #{commentId} AND parent_id != 0 AND status = 0
ORDER BY id ASC;

示例代码

java 复制代码
public List<Comment> getTopComments(Long bizId, int page, int size) {
    return commentMapper.selectList(
        new LambdaQueryWrapper<Comment>()
            .eq(Comment::getBizId, bizId)
            .eq(Comment::getParentId, 0)
            .eq(Comment::getStatus, 0)
            .orderByDesc(Comment::getId)
            .last("LIMIT " + size + " OFFSET " + (page - 1) * size)
    );
}

public List<Comment> getReplies(Long rootId) {
    return commentMapper.selectList(
        new LambdaQueryWrapper<Comment>()
            .eq(Comment::getRootId, rootId)
            .ne(Comment::getParentId, 0)
            .eq(Comment::getStatus, 0)
            .orderByAsc(Comment::getId)
    );
}

场景二:中高流量系统(分库分表 + 异步更新)

适用场景:知乎、B 站、头条等中大型社区,评论量过亿,日均写入上百万。

架构策略

项目 策略
分表方式 按 biz_id 哈希路由,例如 comment_0 ~ comment_63
ID 生成 雪花算法(Snowflake)
点赞计数 Redis + MQ 异步聚合写库
审核系统 评论先落审核队列,审核通过再入主表
热度排序 预计算 hot_score,或 Redis ZSET 排序

表结构(逻辑结构)

sql 复制代码
comment_${n} (
  id BIGINT PRIMARY KEY,
  biz_id BIGINT,
  user_id BIGINT,
  parent_id BIGINT,
  root_id BIGINT,
  content TEXT,
  like_count INT,
  reply_count INT,
  hot_score DOUBLE,
  status TINYINT,
  created_at DATETIME,
  INDEX(biz_id, parent_id),
  INDEX(root_id)
)

点赞、回复计数等异步写入:

java 复制代码
// 点赞后异步写 MQ
likeService.like(commentId, userId);
mqProducer.send("COMMENT_LIKE", commentId);

查询某个视频的所有一级评论(分页 + 热度)

sql 复制代码
SELECT * FROM comment_03
WHERE biz_id = 1001 AND parent_id = 0 AND status = 0
ORDER BY hot_score DESC
LIMIT 20;

热度分数一般为:

ini 复制代码
hot_score = like_count * 0.8 + reply_count * 1.0 + time_decay(create_time)

热度排序 往往是分数衰减函数: hot_score = like_count * 0.8 + reply_count * 1.0 + create_ts / decay 在 MySQL 里做会很慢,B 站/知乎大多把热排算分移到 Redis ZSET 或 ES 脚本排序。

技术栈建议

  • 分库分表:ShardingSphere 或 MyCat
  • 缓存层:Redis
  • MQ:RocketMQ / Kafka
  • 审核:阿里绿网 / 自建敏感词 + 模型系统

场景三:超大流量系统(KV 存储 + 时间线索引)

适用场景:抖音、快手、Instagram 等亿级日活的移动平台

架构模型

评论内容存储在 TiKV、HBase、DynamoDB 等 KV 型数据库中;列表分页使用 Redis Timeline(ZSet/List)索引。

  • 写入通道 :先落 Kafka,流式计算(Flink / Spark Streaming)做审核与反垃圾,最后异步写入 HBase / TiKV

  • 读取通道:双层时间线(Timeline)模型

    • Root TL:视频 ID → 顶级评论的 ID 列表(ZSET or List,内存保 1 万条,冷数据分页补齐到 HBase)
    • Reply TL:顶级评论 ID → 子评论 ID 列表
    • 接口按「cursor = last_id」方式增量拉取;天然支持滚动上拉。

数据结构设计

text 复制代码
# 存评论
HBase: comment:{commentId} → 评论内容结构

# 根评论时间线
Redis: tl:root:{videoId} = ZSET(commentId, hot_score)

# 回复评论时间线
Redis: tl:reply:{rootId} = List(commentId1, commentId2,...)

查询流程(伪代码)

java 复制代码
List<Long> topIds = redis.zrevrange("tl:root:video123", offset, offset + 19);
Map<Long, Comment> commentMap = hbase.batchGet("comment", topIds);

点赞系统优化

  • 用户点赞写入 Redis Set(like:{userId}),前端展示批量判断
  • 定时汇总写入 ClickHouse 或 MySQL
  • 热门评论 like_count 每分钟同步

优化建议

问题场景 优化方式
评论首屏加载慢 Redis 缓存首屏 JSON,MQ 同步更新
点赞/回复不一致 所有修改走 MQ 异步聚合写库
"热评"插入后楼层错乱 楼层号稀疏生成,避免重排
评论审核敏感 写入审核表,通过后再搬迁
分页 OFFSET 性能差 id < last_id 做滚动翻页

总结

流量规模 技术方案 查询方式
中小体量 单表 + 两级 root_id MySQL 直接分页 + join
中高体量 分表 + root_id + 异步聚合 分表查询 + Redis 缓存
超大规模 HBase/TiKV + Redis Timeline 多层缓存 + KV 查询

抛开业务谈设计都是耍流氓,根据不同的规模选择最合适的方案,满足现有业务发展需求,便是优秀的架构设计。 小体量可采用通用设计模型,预留关键扩展点(如 ID 算法、root_id、MQ 链路),未来才能轻松应对流量爬坡。

最后

如果文章对你有帮助,点个免费的赞鼓励一下吧!关注公众号:加瓦点灯, 每天推送干货知识!

相关推荐
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530143 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908403 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金3 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛4 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒4 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境4 小时前
提升 Python 代码健壮性的方法大盘点
后端·python