大促那天的故障复盘会,我至今记得监控屏上的曲线:IM 网关 CPU 直接顶着 95% 以上,消息队列积压从几百条跳到几十万条,连接数每分钟都在涨,紧接着一台接入节点触发了 OOM 自动重启。用户端反馈的表现是——消息延迟从几十毫秒变成十几秒,发出去的消息偶尔丢失,群里头像转圈半天刷不出来。
这套老掉的即时通讯源码,单机支撑个三四千长连接还算稳,但一到大促、秒杀这类瞬时流量洪峰就原形毕露。我们当时面临三个核心问题:一是消息洪峰来了之后数据库和下游系统根本扛不住,必须做削峰;二是接入层的负载均衡策略太粗,连接和请求都往几台机器上怼;三是 JVM 堆内堆外内存双双吃紧,GC 频繁到让人怀疑人生。
这篇文章就把这次调优的完整过程拆开讲,核心覆盖三条主线:高并发消息削峰、负载均衡策略、内存资源优化。我会把每一步的关键决策、参数计算过程、踩过的坑和最终压测数据都写出来。不管你是负责线上 IM 系统的运维,还是做后端架构设计的开发,或者刚入门想了解高并发系统怎么调优,这篇文章都值得你花二十分钟认真看一遍。
1. 项目背景与调优目标拆解
1.1 业务痛点:线上事故是怎么爆出来的
那个阶段我们的即时通讯系统承载了主站全量用户的聊天、群通知、系统公告三类消息。日常高峰 QPS 也就 5000 左右,但大促活动一开始,运营会在整点发大量群消息和公告,瞬时 QPS 能冲到 8 万以上。这种尖峰流量对系统是毁灭性的:
消息发送链路是同步调用的。客户端把消息发到接入网关,网关同步写 Redis 做离线存储,再同步投递给在线接收方,最后还要同步推消息给推送服务。链路上任何一个环节慢下来,整条链路就堵死。MySQL 的写入 TPS 到 3000 左右就会出现锁等待,Redis 的写吞吐虽然有上限,但网络带宽和序列化开销在大消息体面前也很脆弱。
接入层的负载均衡也是个问题。我们用的是 Nginx 四层代理 + 后端网关节点的方式,Nginx 的 upstream 配的是默认的轮询算法。四层负载只看连接数,不看每台网关节点实际的 CPU 负载和内存水位。导致的结果是:新建立的连接经常集中落在配置了高权重的机型上,而新扩容的低配机器反而长时间空闲。长连接又是小时级别的存活时间,连接一旦建立就固定在哪台机器,负载不均的问题会持续放大。
最头疼的是内存。K8s 给每个网关 Pod 限了 8GB,JVM 堆设置 4GB,剩余 4GB 留给堆外。但实际运行中,Netty 的堆外内存池、线程栈、直接缓冲区、压缩类元数据都要吃这部分空间。连接数一高,堆外内存直接撑爆,然后就是 OOM Kill。我们统计过,每次大促期间网关 Pod 平均重启 5 次以上,每次重启意味着几万条长连接要断线重连,断线风暴又反过来加剧负载不均——这是一连串的恶性循环。
1.2 调优目标与验收指标
项目启动前我们把目标量化了,有具体的数字才能判断调优到底有没有效果:
| 指标 | 调优前 | 目标值 |
|---|---|---|
| 峰值消息处理能力 | 8万 QPS 时 P99 延迟 850ms | 12万 QPS 时 P99 延迟 ≤ 100ms |
| 消息积压恢复时间 | 峰值期积压 50万条,恢复需 30 分钟 | 积压 ≤ 2万条,恢复 ≤ 3 分钟 |
| 网关节点内存水位 | 堆内 3.8GB/4GB,堆外 3.6GB/4GB | 堆内 ≤ 3GB,堆外 ≤ 2.5GB |
| Pod OOM 次数 | 单次活动 5次+ | 0 次 |
| 机器资源 | 24 个网关 Pod + 8 个消息处理 Pod | 压测验证不增加 Pod 数 |
不追求极限性能数字,我们核心目标是:在同等机器资源下扛住双倍流量,同时系统保持稳定不 OOM、不积压、不丢消息。这些目标听着朴实,做起来需要同时在三个方向上做文章。
1.3 整体架构与优化主线
调优不是单点修改,而是一条链路的全面梳理。我们的即时通讯系统逻辑上分四层:
客户端接入层:维护长连接,处理心跳、断线重连、协议编解码。这层是连接风暴的第一道防线。
消息路由层:负责消息的接收、鉴权、路由、存储。每条消息都要在这里决定是走实时通道还是离线通道。
消息处理层:消费消息队列,做消息持久化、推送、离线存储。这层用 MQ 做异步解耦。
状态管理层:用 Redis 保存用户在线状态、Session 映射关系、连接路由表。这层是全局共享的。
对应到三块优化:
消息削峰瞄准的是消息处理层——把同步写库和同步推送改成异步削峰处理,用 Kafka 做缓冲,同时通过滑动窗口限流保护下游。
负载均衡瞄准的是接入层和路由层之间的连接分配——改造成一致性哈希 + 动态权重,让连接和流量均匀分布,避免热点节点。
内存优化则贯穿所有层——从 JVM 堆内对象复用、Netty 内存池参数调整到堆外内存监控兜底。
这三块不是孤立的。削峰做不好,内存再好也会被打爆;负载不均衡,部分节点的内存优化做得再好也扛不住超额流量。所以这是一套组合拳,必须统筹实施。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高并发消息削峰:从限流到 MQ 缓冲的完整链路
2.1 削峰的核心逻辑:为什么不能靠堆机器
很多团队遇到流量洪峰第一反应就是扩容,把 Pod 数量翻倍。这在流量平缓上涨时有效,但面对瞬时尖峰,扩容的速度根本跟不上流量上涨的速度。K8s HPA 从检测到指标异常到完成扩容,冷启动至少需要 2~3 分钟,而流量洪峰是秒级就冲上来的。
更关键的是,扩机器解决不了下游瓶颈。MySQL 的写入能力不会因为你扩容网关就变强,Redis 的带宽和 CPU 也不会。真正该做的,是把同步链路改造成异步链路,消息不直接打到下游系统,而是先进队列缓冲,再由消费者按下游能承受的速率慢慢消费。这就是削峰的核心思路。
一句话概括:削峰不是让消息消失,而是让消息在队列里等一等,把时间的尖峰拉平,让系统始终工作在平稳的水位线上。
这个道理类似高速公路上的收费站。早晚高峰时车流是集中的,如果每个车道都对应一个收费员,高
