十万大群的推送优化

十万人大群里,消息推送的难点并不是“把一条消息发给十万人”这么简单。
真正的压力来自连续放大的链路:一条群消息进入系统后,要写入消息链路,要找到应该触达的成员,要判断哪些用户在线,要把在线用户路由到对应长连接网关,还要把未在线或在线推送失败的用户交给离线推送。任何一步如果按“全量、同步、逐个处理”的方式实现,都会在大群里被迅速放大。
OpenIM 的推送优化思路不是把某个函数写得更快,而是把整条链路拆开:消息转发、在线推送、离线推送、网关触达、第三方厂商推送各自解耦,再用批量、分片、过滤和兜底把压力控制在可预期范围内。
01. 推送链路先解耦,不能让一条消息拖住全链路
在普通群里,推送可以看起来像一个连续动作:消息来了,找到人,直接推过去。
但在十万人大群里,这种连续动作会变成风险。只要某个环节变慢,前面的消息写入、后面的在线触达、离线推送都会互相影响。OpenIM 的做法是把消息写入和推送执行拆成不同阶段:消息转发服务负责把消息进入存储和推送队列,推送服务再消费推送任务,在线推送和离线推送也分成不同入口处理。
这样做的价值很直接:
- 消息写入不会被第三方推送服务拖慢。
- 在线推送和离线推送可以按各自节奏扩容。
- 推送服务出现短时抖动时, 消息主链路仍然可以继续前进。
- 后续排查问题时,可以区分是消息写入慢、在线网关慢,还是离线厂商推送慢。
对于大群来说,解耦不是架构上的“好看”,而是稳定性的前提。
02. 第一层优化:把单条推送收口成批量处理
大群消息最怕的不是单条消息,而是连续消息。
如果每条消息都独立完成一次解析、一次路由、一次网关调用、一次提交确认,那么高峰时系统会把大量时间花在重复动作上。OpenIM 会在消息转发阶段按消息队列的 key 做分片,把同一批到达的消息聚合到固定窗口里处理;进入推送服务后,再按会话和接收人范围继续做批量归并。
这让推送从“来一条处理一条”变成“按窗口处理一批”。收益主要有三点:
- 重复的解析、调度和队列处理减少了。
- 同一会话内连续消息可以合并进入同一次推送处理上下文。
- worker 的并发数量受控,不会因为瞬时消息峰值无限扩张。
这一步看起来只是批处理,但对十万大群很关键。因为大群里的压力通常不是平均压力,而是瞬时压力。批量窗口能把尖峰削平,让后面的在线路由和离线推送有稳定输入。
03. 第二层优化:按会话归并,而不是按消息散打
推送服务消费到消息后,不是直接把每条消息独立发出去,而是按会话和接收人范围组织批量。对于没有自定义推送选项的连续群消息,系统会把它们合并到同一批次里处理。
这对大群尤其重要。十万人大群里的多条消息,本质上属于同一个群会话。如果每条消息都重新走一遍完整成员查找、在线状态判断和网关分发,很多工作会重复。按会话归并后,系统可以把同一会话内的一批消息放在同一次处理上下文里,减少重复调度。
同时,单聊、通知会话和大群会话会进入不同处理路径。普通用户消息更关注接收方和发送方多端同步;大群消息更关注群成员范围、在线状态、离线过滤和网关批量触达。把路径分开后,每类会话都可以使用最适合自己的推送策略。
04. 第三层优化:先缩小接收人范围
十万人大群不是每条消息都真的需要推给十万人。
OpenIM 在进入实际推送前,会先尽量缩小接收人范围。群推送前回调可以让业务系统调整接收人;消息自身也可以指定只推给部分用户,或者在默认群成员之外补充额外用户。没有特殊规则时,系统才从群成员 ID 缓存中取出群成员列表作为默认推送范围。
这层设计给业务留下了很大空间:
- 普通群消息可以走默认群成员范围。
- 定向提醒可以只推给被影响的成员。
- 运营或系统消息可以通过回调做业务侧过滤。
- 特殊消息可以补充额外接收人,但不破坏默认链路。
大群优化的核心不是每次都推得更多,而是先判断哪些人真的应该进入本轮推送。
05. 第四层优化:在线用户只推到对应网关
在分布式 IM 系统里,用户长连接分散在多个网关节点上。
最粗暴的做法是把同一条群消息广播给所有网关,让每个网关自己判断有没有目标用户。但十万人大群下,这会让网关之间产生大量无效调用。OpenIM 的在线推送会先查询用户在线状态,并按网关维度整理在线用户:哪些用户在哪个网关上,就把消息推到哪个网关。
这样一来,在线推送的目标从“所有网关”缩小为“真正持有目标用户连接的网关”。这会明显减少无效 RPC、降低网关压力,也让推送结果更容易回收。
系统也保留了兜底路径:当在线状态不可用、网关映射不可信,或者单机部署场景下无法精确路由时,可以退回到全网关推送。也就是说,精确路由是优先路径,全量广播是安全兜底。