大家好。今天咱们聊一个让无数程序员闻风丧胆的话题:高并发评论系统。
想象一下这个场景:某明星官宣恋情,微博评论区瞬间爆炸,100万人同时评论、点赞、回复…你的系统要是扛不住,用户直接原地爆炸,产品经理提刀来见!
别慌,今天我就把这套10万QPS评论中台架构的压箱底干货掏出来,手把手教你搭建一个永远扛得住的评论区。
一、先搞清楚:评论系统到底难在哪?
很多人觉得评论系统不就是CRUD吗?Naive!真实的高并发评论区藏着这些坑:
- 写入量巨大:热点事件时,评论写入QPS可能从100瞬间飙到10万+
- 读多写更多:用户刷评论的频率远超发帖,读写比例可能达到100:1
- 实时性要求高:用户发了评论必须秒现,延迟超过3秒就开始骂娘
- 数据结构复杂:评论、回复、点赞、举报、审核…一套组合拳下来头都大了
- 内容安全敏感:一条违规评论没拦住,APP直接下架,老板提头来见
二、架构设计:5层防护让评论区稳如老狗
第1层:流量入口 - 能挡多少是多少
CDN + Nginx组成第一道防线:
# 评论接口限流配置
limit_req_zone $binary_remote_addr zone=comment:10m rate=10000r/s;
limit_req_zone $binary_remote_addr zone=like:10m rate=50000r/s;
server {
# 评论写入接口严格限流
location /api/comment/post {
limit_req zone=comment burst=5000 nodelay;
}
# 点赞接口宽松一些
location /api/comment/like {
limit_req zone=like burst=20000 nodelay;
}
}
第2层:应用层 - 缓存为王,异步为皇
多级缓存架构:
// 评论列表缓存策略
@Component
public class CommentCacheService {
// L1缓存:本地Caffeine,缓存热点评论
private final Cache<String, List<CommentVO>> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
// L2缓存:Redis,缓存评论列表
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public List<CommentVO> getComments(String bizId, int page) {
String key = "comments:" + bizId + ":" + page;
// 先查本地缓存
List<CommentVO> local = localCache.getIfPresent(key);
if (local != null) return local;
// 再查Redis
List<CommentVO> redisData = (List<CommentVO>) redisTemplate.opsForValue().get(key);
if (redisData != null) {
localCache.put(key, redisData);
return redisData;
}
// 最后查数据库
List<CommentVO> dbData = commentDao.findComments(bizId, page);
redisTemplate.opsForValue().set(key, dbData, 1, TimeUnit.MINUTES);
localCache.put(key, dbData);
return dbData;
}
}
异步处理流水线:
// 评论写入异步化处理
@Service
public class CommentPostService {
@Autowired
private RabbitTemplate rabbitTemplate;
public ApiResult<String> postComment(CommentPostDTO dto) {
// 1. 基础校验(同步)
validateComment(dto);
// 2. 生成全局唯一评论ID
String commentId = snowflake.nextIdStr();
// 3. 写入Redis缓存(同步)
CommentVO comment = buildCommentVO(dto, commentId);
cacheCommentToRedis(comment);
// 4. 异步写入数据库 + 审核 + 通知
rabbitTemplate.convertAndSend("comment.exchange", "comment.post", comment);
return ApiResult.success(commentId);
}
}
第3层:数据层 - 分库分表的艺术
按业务维度分库分表:
-- 评论表分表策略
CREATE TABLE comment_0000 (
id BIGINT PRIMARY KEY,
biz_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
content TEXT,
like_count INT DEFAULT 0,
reply_count INT DEFAULT 0,
status TINYINT DEFAULT 1,
create_time DATETIME,
INDEX idx_biz_time (biz_id, create_time),
INDEX idx_user (user_id)
);
-- 按biz_id哈希分64个表
-- 评论内容全文检索用ES
-- 计数用Redis HyperLogLog
Redis计数器优化:
// 评论计数器
@Component
public class CommentCounter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void incrCommentCount(String bizId) {
// 使用Redis String结构计数
redisTemplate.opsForValue().increment("comment:count:" + bizId);
// 每100次同步一次数据库
if (getCount(bizId) % 100 == 0) {
syncToDatabase(bizId);
}
}
public long getCount(String bizId) {
String count = (String) redisTemplate.opsForValue().get("comment:count:" + bizId);
return count == null ? 0 : Long.parseLong(count);
}
}
第4层:消息队列 - 削峰填谷神器
多级消息队列架构:
# RocketMQ配置
rocketmq:
name-server: rocketmq-cluster:9876
producer:
group: comment-producer
retry-times-when-send-failed: 3
consumer:
group: comment-consumer
consume-thread-max: 100
consume-timeout: 15
max-reconsume-times: 3
# 队列分配策略
comment-topic:
write-queue-nums: 16 # 写入队列
read-queue-nums: 16 # 消费队列
优先级队列设计:
// 评论消息优先级处理
@Component
public class CommentPriorityHandler {
@RabbitListener(queues = "comment.high.priority")
public void handleHighPriority(CommentVO comment) {
// VIP用户评论优先处理
processComment(comment, true);
}
@RabbitListener(queues = "comment.normal.priority")
public void handleNormalPriority(CommentVO comment) {
// 普通用户评论
processComment(comment, false);
}
private void processComment(CommentVO comment, boolean priority) {
// 内容审核
if (contentAuditService.audit(comment)) {
// 敏感词过滤
comment.setContent(sensitiveFilter.filter(comment.getContent()));
// 保存到数据库
commentDao.save(comment);
// 更新缓存
updateCache(comment);
}
}
}
第5层:内容安全 - 三道防线保平安
实时审核流水线:
// 内容审核服务
@Service
public class ContentAuditService {
// 第一道:本地敏感词过滤
private SensitiveWordFilter localFilter;
// 第二道:AI内容审核
@Autowired
private AIAuditService aiAudit;
// 第三道:人工审核队列
@Autowired
private ManualAuditQueue manualQueue;
public boolean audit(CommentVO comment) {
// 1. 本地敏感词快速过滤
if (localFilter.containsSensitiveWord(comment.getContent())) {
return false;
}
// 2. AI审核(异步)
CompletableFuture<Boolean> aiResult = aiAudit.auditAsync(comment);
// 3. 高风险内容转人工审核
if (aiResult.get() == null) {
manualQueue.add(comment);
return false; // 先隐藏,等人工审核
}
return aiResult.get();
}
}
三、实战案例:某短视频APP评论区架构演进
阶段1:单库单表(1000QPS)
问题:用户量10万,单库MySQL直接干爆
CREATE TABLE comments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
video_id BIGINT,
user_id BIGINT,
content TEXT,
like_count INT DEFAULT 0,
create_time DATETIME
);
阶段2:加Redis缓存(1万QPS)
优化:评论列表缓存1分钟,计数器用Redis
// 缓存热点视频的评论前100条
String key = "hot_comments:" + videoId;
List<Comment> hotComments = redisTemplate.opsForList().range(key, 0, 99);
阶段3:分库分表 + MQ(5万QPS)
按video_id分64个表:
// 分表路由
String tableName = "comments_" + (videoId % 64);
引入RocketMQ:
# 写入异步化
producer:
topic: comment_post
consumer:
topic: comment_consume
threads: 50
阶段4:多级缓存 + 微服务(10万QPS)
最终架构:
用户请求 → CDN → Nginx → API网关 → 评论服务 → 缓存层 → 消息队列 → 存储层
↓
审核服务 → AI审核 → 人工审核
性能数据:
- 评论写入:10万QPS,平均延迟50ms
- 评论读取:50万QPS,P99延迟100ms
- 点赞操作:100万QPS,无压力
四、核心代码实战:评论写入全流程
@RestController
@RequestMapping("/api/comment")
public class CommentController {
@Autowired
private CommentPostService commentService;
@PostMapping("/post")
public ApiResult<String> postComment(@RequestBody CommentPostDTO dto) {
try {
// 1. 参数校验
validateParam(dto);
// 2. 用户权限校验
checkUserPermission(dto.getUserId());
// 3. 防刷校验(同一用户1分钟内最多10条)
checkSpam(dto.getUserId());
// 4. 写入评论
String commentId = commentService.postComment(dto);
return ApiResult.success(commentId);
} catch (Exception e) {
log.error("评论发布失败", e);
return ApiResult.error("评论发布失败,请稍后重试");
}
}
}
@Service
public class CommentPostService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Transactional
public String postComment(CommentPostDTO dto) {
String commentId = snowflake.nextIdStr();
// 1. 构建评论对象
Comment comment = Comment.builder()
.id(commentId)
.videoId(dto.getVideoId())
.userId(dto.getUserId())
.content(dto.getContent())
.parentId(dto.getParentId())
.status(CommentStatus.PENDING) // 待审核
.createTime(LocalDateTime.now())
.build();
// 2. 写入Redis(先给用户看到)
cacheToRedis(comment);
// 3. 发送MQ异步处理
rocketMQTemplate.asyncSend("comment-topic", comment, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("评论消息发送成功: {}", commentId);
}
@Override
public void onException(Throwable e) {
log.error("评论消息发送失败: {}", commentId, e);
// 重试机制
retry(comment);
}
});
// 4. 更新计数器
updateCommentCount(dto.getVideoId());
return commentId;
}
private void cacheToRedis(Comment comment) {
String key = "comment:" + comment.getVideoId() + ":latest";
// 使用Redis List存储最新评论
redisTemplate.opsForList().leftPush(key, comment);
redisTemplate.expire(key, 5, TimeUnit.MINUTES);
// 同时缓存单条评论详情
String detailKey = "comment:detail:" + comment.getId();
redisTemplate.opsForValue().set(detailKey, comment, 10, TimeUnit.MINUTES);
}
}
五、避坑指南:这些坑90%的人都踩过
1. 缓存雪崩
问题:Redis挂了,所有请求打到数据库
解决:
// 多级缓存 + 熔断器
@Component
public class CacheCircuitBreaker {
private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("redis");
public List<Comment> getComments(String videoId) {
return circuitBreaker.executeSupplier(() -> {
// Redis正常就走Redis
return redisTemplate.opsForList().range("comments:" + videoId, 0, 99);
}, throwable -> {
// Redis挂了走本地缓存 + 数据库
return getFromDatabase(videoId);
});
}
}
2. 消息重复消费
问题:MQ消息重复投递,评论重复出现
解决:
// 幂等性设计
@Component
public class IdempotentProcessor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean processComment(String commentId) {
String key = "processed:" + commentId;
// 使用SETNX保证幂等
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", 1, TimeUnit.HOURS);
return Boolean.TRUE.equals(result);
}
}
3. 评论列表排序
问题:按时间排序导致刷屏,按热度排序新评论看不到
解决:
// 智能排序算法
public class CommentRanking {
public double calculateScore(Comment comment) {
long timeDiff = System.currentTimeMillis() - comment.getCreateTime().getTime();
double timeDecay = Math.exp(-timeDiff / (24 * 3600 * 1000.0)); // 24小时衰减
return comment.getLikeCount() * 0.7 + comment.getReplyCount() * 0.2 + timeDecay * 0.1;
}
}
4. 大V评论特殊处理
问题:明星评论被刷赞,需要实时更新
解决:
// VIP用户评论实时推送
@EventListener
public void handleVipComment(VipCommentEvent event) {
Comment comment = event.getComment();
if (isVipUser(comment.getUserId())) {
// WebSocket实时推送
webSocketService.broadcastToFollowers(comment);
// 立即更新缓存
updateVipCommentCache(comment);
}
}
六、性能监控:让问题无处遁形
监控大盘:
# Grafana监控配置
- QPS实时监控
- 评论写入延迟
- Redis命中率
- MQ堆积消息数
- 内容审核耗时
告警规则:
- QPS > 80000 持续5分钟 → 短信告警
- Redis命中率 < 80% → 微信告警
- MQ堆积 > 10000 → 电话告警
七、总结:评论区架构的7个黄金法则
- 缓存是爹:能缓存的就缓存,不能缓存的创造条件也要缓存
- 异步是娘:同步操作能异步就异步,别让用户等
- 分库分表是刚需:数据量大就别犹豫,直接分
- 幂等是底线:重复消费、重复提交必须防
- 监控是生命线:没有监控的系统就是瞎子
- 限流是美德:该拒绝时就拒绝,保护系统最重要
- 降级是智慧:系统扛不住时,先保核心功能
记住:评论区架构没有银弹,只有不断迭代优化才能扛住10万QPS!
觉得有用的话,点赞、在看、转发三连走起!咱们下期聊短视频推荐系统架构,敬请期待~