评论系统扛不住10万QPS?这5个架构绝招让评论区永不崩溃!

评论系统扛不住10万QPS?这5个架构绝招让评论区永不崩溃!

大家好,我是被评论区搞秃头的架构师老王。今天咱们聊一个让无数程序员闻风丧胆的话题:高并发评论系统

想象一下这个场景:某明星官宣恋情,微博评论区瞬间爆炸,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个黄金法则

  1. 缓存是爹:能缓存的就缓存,不能缓存的创造条件也要缓存
  2. 异步是娘:同步操作能异步就异步,别让用户等
  3. 分库分表是刚需:数据量大就别犹豫,直接分
  4. 幂等是底线:重复消费、重复提交必须防
  5. 监控是生命线:没有监控的系统就是瞎子
  6. 限流是美德:该拒绝时就拒绝,保护系统最重要
  7. 降级是智慧:系统扛不住时,先保核心功能

记住:评论区架构没有银弹,只有不断迭代优化才能扛住10万QPS!

觉得有用的话,点赞、在看、转发三连走起!咱们下期聊短视频推荐系统架构,敬请期待~


版权声明:本文为原创文章,转载请注明出处。评论区架构咨询请联系微信:architect_wang


标题:评论系统扛不住10万QPS?这5个架构绝招让评论区永不崩溃!
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/21/1766304273904.html

    0 评论
avatar