50万QPS未读消息系统设计:从崩溃到丝滑的实战之路

50万QPS未读消息系统设计:从崩溃到丝滑的实战之路

大家好,今天想跟大家聊一个看似简单,实则能让整个系统崩溃的问题——如何设计一个能扛住50万QPS的站内未读消息系统

为什么说它重要?想想看,现在哪个App没有消息通知?用户登录后看到的小红点、未读数字,背后都是这个系统在支撑。如果设计不好,轻则消息延迟,重则整个服务雪崩。

一、未读消息系统的3大“坑王”

先别急着写代码,咱们得先搞清楚这个系统的核心挑战。我见过太多团队一开始觉得“不就是存个数字吗”,最后被现实狠狠教育。

1. 高并发读写:秒杀级别的写入压力

用户的每一次点击、每一条新消息,都会触发未读消息的更新。在大用户量的平台,这就是持续不断的秒杀场景

比如某电商平台,大促期间每秒新增消息量能达到50万条。如果直接写数据库,分分钟把MySQL打趴下。

2. 数据一致性:不能多也不能少

未读消息的数字必须绝对准确。多了,用户会疑惑;少了,用户会错过重要信息。

我之前遇到过一个坑:用户明明读了消息,但未读数字没减。一查才发现,是缓存更新不及时,导致脏数据。

3. 实时性要求:用户盯着小红点呢

用户对未读消息的实时性要求极高。你想想,刚发的消息,用户等着看回复,结果未读数字半天不更新,体验得多差?

二、能扛50万QPS的架构设计

说了这么多坑,那到底怎么设计才能扛住50万QPS?我结合自己参与过的社交平台架构改造案例,总结了这套方案。

1. 第一层:Redis前置缓存,扛下90%的压力

未读消息系统的核心是读多写少,所以我们用Redis做前置缓存:

  • 用Hash结构存储每个用户的未读消息数:user:unread:{user_id} -> {type: count}
  • 写操作先更新Redis,再异步同步到数据库
  • 读操作直接从Redis取,毫秒级响应

这样一来,90%的读请求都被Redis扛住了,数据库压力大减。

2. 第二层:消息队列削峰,保护数据库

就算用了Redis,突发流量还是可能冲垮系统。这时候就需要消息队列来削峰:

  • 写请求先发送到Kafka
  • 消费者集群平滑消费,控制写入数据库的速率
  • 就算Redis挂了,消息也不会丢,保证了数据可靠性

我之前做的社交平台,就是靠Kafka把写入峰值从50万QPS削到了10万QPS,数据库终于能正常喘气了。

3. 第三层:分库分表,解决存储瓶颈

随着用户量增长,单表数据会越来越大,查询和更新都会变慢。我们采用了按用户ID哈希分库分表

  • 分16个库,每个库分64张表
  • 用户ID % 16 确定库,用户ID / 16 % 64 确定表
  • 这样每个表的数据量控制在千万级,性能保持稳定

4. 第四层:定时任务+冷热数据分离,优化存储

未读消息有个特点:新消息访问频繁,旧消息很少被查看。我们采用了:

  • 热数据(3个月内)存在MySQL,保证实时性
  • 冷数据(3个月以上)迁移到TiDB,降低存储成本
  • 定时任务异步清理已读超过半年的消息

三、实战案例:某社交平台的未读消息系统改造

光说理论不够,给大家讲个真实案例。我之前参与的某社交平台,未读消息系统从日活100万到1000万的演进过程。

1. 初始阶段:MySQL单表

一开始用户量小,直接用MySQL单表存储,结构如下:

CREATE TABLE `user_unread` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL,
  `message_type` int(11) NOT NULL,
  `count` int(11) NOT NULL DEFAULT 0,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_type` (`user_id`,`message_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

日活100万时还能勉强支撑,但到了300万,MySQL的CPU直接飙到100%,查询延迟从10ms升到了500ms+。

2. 优化阶段:引入Redis缓存

我们紧急引入Redis,把未读数字缓存起来。但刚开始没考虑到并发问题,出现了缓存一致性问题。

后来我们改用Lua脚本原子更新Redis,同时用Kafka异步同步到数据库,解决了一致性问题。

3. 高可用阶段:分库分表+集群架构

随着用户量突破1000万,我们又做了分库分表,同时搭建了Redis集群、Kafka集群,确保系统高可用。

改造后,系统能轻松扛住50万QPS的写入压力,未读消息的更新延迟控制在100ms以内,数据库的CPU使用率稳定在30%左右。

四、5个避坑小贴士

最后,给大家分享几个我踩过的坑和总结的经验:

  1. 不要过度依赖数据库:未读消息系统的读写比例通常是10:1,Redis才是主力。

  2. 小心缓存穿透:对于不存在的用户ID,要做好缓存空值,避免攻击。

  3. 异步更新是关键:能异步的操作尽量异步,比如未读数字同步到数据库。

  4. 监控告警要到位:设置Redis命中率、消息队列积压、数据库延迟等关键指标的告警。

  5. 容量规划要留余量:按照业务增长速度,提前做好扩容准备,不要等系统崩溃了再应急。

总结

设计一个支持50万QPS的未读消息系统,核心不是用多高端的技术,而是理解业务特点,合理利用缓存、消息队列、分库分表等技术,分层次解决问题

关注我,不迷路,持续分享后端技术干货。
点赞、评论、转发,是我创作的最大动力!
公众号:服务端技术精选


标题:50万QPS未读消息系统设计:从崩溃到丝滑的实战之路
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304277936.html

    0 评论
avatar