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个避坑小贴士
最后,给大家分享几个我踩过的坑和总结的经验:
-
不要过度依赖数据库:未读消息系统的读写比例通常是10:1,Redis才是主力。
-
小心缓存穿透:对于不存在的用户ID,要做好缓存空值,避免攻击。
-
异步更新是关键:能异步的操作尽量异步,比如未读数字同步到数据库。
-
监控告警要到位:设置Redis命中率、消息队列积压、数据库延迟等关键指标的告警。
-
容量规划要留余量:按照业务增长速度,提前做好扩容准备,不要等系统崩溃了再应急。
总结
设计一个支持50万QPS的未读消息系统,核心不是用多高端的技术,而是理解业务特点,合理利用缓存、消息队列、分库分表等技术,分层次解决问题。
关注我,不迷路,持续分享后端技术干货。
点赞、评论、转发,是我创作的最大动力!
公众号:服务端技术精选
标题:50万QPS未读消息系统设计:从崩溃到丝滑的实战之路
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/21/1766304277936.html