案例拆解:典型 IM 架构的取舍

架构选型没有标准答案,只有在给定约束下的取舍。本章拆四个公开可查的典型形态:单机网关加 Redis 路由、多机房就近接入、大群的消息扩散,以及自研长连接与开源协议、云服务之间的分界。文中所有数值都是量级示意,用于说明推导方式,不对应任何厂商的内部数据;涉及具体实现细节时以公开资料为准。

案例 A:单机网关 + Redis 路由

约束:DAU 十万到百万之间,峰值 CCU 十万级,团队三到五人,SLA 99.9% 即可。

客户端 ──长连接──▶ 接入网关 N 台(无状态:连接、心跳、编解码、限流)
                        │ 内网 RPC
                   逻辑服务(收发、会话、群成员、投递编排)
                        │
   Redis(路由表 + 会话缓存 + 未读数)|MySQL(消息、会话、关系)|MQ(落库与扇出)
决策点选择代价什么时候必须换
路由表Redis 单集群,key 为 uid+device 指向网关实例容量与故障域都集中在 Redis连接数超 300 万或内存吃紧 → 按 uid 分片
消息落库同步写 MySQL 成功后才回 ACK写延迟进 P99,主库压力大写入 QPS 超 2 万 → 先入 MQ 异步落库,同时补上客户端补偿逻辑
群聊读扩散,成员各自按会话拉取在线的即时性差群规模超 200 且要求即时 → 在线广播 + 对活跃成员写扩散
多端同步每设备一条同步游标存储与回执量随设备数线性增长设备数超 3 → 合并为会话级游标
在线态Redis 存最后一跳心跳时间高频写,需合并写心跳频率提高 → 改本地聚合后批量上报
部署单机房多可用区机房级故障不可用出现跨地域用户或 SLA 高于 99.9% → 多机房

要点:这个形态比想象中耐用。无状态网关加 Redis 路由表是最省心的组合,真正的瓶颈通常出现在 MySQL 写入与 Redis 单集群内存上,而不是网关转发;等到这两处告急再谈分片与多机房,能省下大量复杂度。

案例 B:多机房就近接入

约束:用户跨地域、CCU 百万级、SLA 99.95% 以上、有数据合规要求。

        ┌── GSLB:按地域与健康度解析,外加接口下发的接入点列表 ──┐
华东用户 ──▶ 单元 A(网关 + 逻辑 + Redis 路由分片 + MySQL 分片)
华南用户 ──▶ 单元 B(独立一套,单元内闭环)
海外用户 ──▶ 单元 C(延迟与合规双重考虑)
跨单元投递:接收方不在本单元 → 走消息总线转发,而不是直接写对方连接
决策点选择代价什么时候必须换
单元划分按 uid 哈希固定归属,用户不迁移扩容要成对加单元,粒度粗单单元容量不足 → 单元内再分片
会话归属按发起方 uid 所在单元双方跨单元时多一跳转发跨单元消息占比超 30% → 引入会话归属中心
路由表单元内自治,不做跨机房实时复制跨单元投递依赖消息总线需要全局在线态 → 加一层只读的在线汇总,接受秒级延迟
消息数据一个会话只落在一个单元,避免双写冲突该单元故障时会话不可写要求会话级容灾 → 会话内主备单元 + 异步复制
推送在用户归属单元发起厂商推送单元故障时推送中断推送通道独立部署,避免与 IM 单元同生共死
切换GSLB 摘除 + 客户端换接入点切换窗口内连接重建有风暴平滑性不够 → 客户端多接入点并发探测

关键结论:多机房的核心不是“部署两套”,而是把“谁是权威”写清楚。路由、会话、数据各有一个权威归属,其他副本只读或异步;否则并发写冲突会在故障切换的瞬间集中爆发,表现为“同一条消息两个版本”“两个单元都认为用户在线”。

案例 C:大群的消息扩散

维度写扩散(fan-out on write)读扩散(fan-out on read)分层混合
扩散时机发送时写 N 份收件箱接收方拉取时按会话查小群写扩散,大群读扩散 + 在线广播
存储放大N 倍(N 为群人数)1 倍按群规模分档
发送耗时随成员数上升与成员数无关与成员数无关
读耗时一次读收件箱,快需合并多个会话在线快,离线需合并
一致性每份收件箱各自维护位点,容易漏推单一真相,天然一致复杂,必须把分档规则写死
离线补拉直接读收件箱位点按会话位点增量拉取两者并用
适用群规模500 人以内千人以上全规模

超大群的分层做法:

1 万 ~ 10 万人群:
 1) 发送只落一份消息(读扩散),同时向“当前在线成员”广播
 2) 在线成员靠广播即时展示,离线成员靠上线后按位点拉取
 3) 广播限速与合并:同一会话 200ms 内的多条消息合并成一次下发
 4) 非活跃会话(成员 7 天未打开)不下发正文,只更新未读数与摘要
 5) 超出投递预算时降级为“有新消息”提示,正文由客户端拉取(见 28 章)

两个容易踩的坑:读扩散下“未读数”与“会话最后一条摘要”必须能低成本算出来,否则群列表会退化成 N 次聚合并成为新瓶颈;群成员变更后扩散范围必须重建,否则新成员看不到历史、退群成员仍在收消息。取舍判据很直接:只要“群成员数 × 消息条数”超出存储预算或发送延迟预算,就该切读扩散;反之写扩散换来的是最简单的读路径。

案例 D:自研长连接、开源协议与云服务

方案适合的场景换来什么代价
自研长连接(WebSocket/TCP + 自定义协议)需要精确控制协议、成本与多端同步语义协议可裁剪、单位成本可控、优化自由度高连接治理、推送、监控、容灾都要自建,人力是最大成本
基于 MQTT(公开规范)弱网、设备侧混合场景、已有 MQTT 生态成熟的 QoS 分级、遗嘱消息、订阅模型面向主题而非人与会话,会话、历史、已读等业务语义要自己补
基于 XMPP(公开规范)需要联邦、与第三方互通标准化扩展协议族齐全以 XML 流为核心,移动端流量与实现成本偏高
云厂商 IM 服务快速上线、团队小、需要全球节点免运维,自带推送与合规节点长期单位成本与定制自由度受限,业务逻辑仍需自研
长连接 + 厂商推送组合移动端为主的消费级产品后台保活问题交给系统通道推送延迟与到达率受厂商策略影响,需做多通道兜底

选型要回答六个问题:协议是否必须自己掌控、有没有海外合规要求、团队能否长期维护连接层、每个在线连接每月的成本能否接受、厂商推送通道要不要自建、将来迁移的退出成本有多大。

通用取舍清单

维度常见两个选项判断依据
连接归属全局路由表 vs 单元内自治用户是否跨地域、SLA 是否高于 99.9%
消息落库同步落库后 ACK vs 先入队列后异步落库能否接受“ACK 后短暂不可见”,客户端是否有补偿能力
群消息写扩散 vs 读扩散群规模分布与存储预算
在线态Redis 集中存 vs 网关本地聚合上报查询频率与一致性要求
多端同步每设备游标 vs 会话级游标平均设备数与回执链路的压力
推送厂商通道 vs 自建通道用户所在平台与到达率要求

常见踩坑

  • 把“在线人数”当成权威数据做运营决策:长连接数与真实活跃数可能差数倍(见 01 章)。
  • 用 MySQL 存路由表:写放大与主从延迟会直接表现为“消息找不到人”。
  • 群成员变更后不重建扩散范围:新成员看不到历史,退群成员继续收消息。
  • 大群实时广播不限速:一个群就能打满整个接入集群的出口带宽。
  • 多机房双写路由表却没有冲突解决:切换时出现“两个在线单元”。
  • 把 MQ 当投递主链路:MQ 故障即消息不可用,且重试时的顺序与丢失语义很难控。
  • 过早自研:在没有明确的成本或协议诉求前就自建连接层,最后维护不动,反而拖慢业务。

小结:四个案例的共同点是“先明确约束,再选形态”:小规模用无状态网关加 Redis 路由最划算,规模上来后再谈多机房,而多机房的关键是给路由、会话与数据指定唯一权威;群聊则按群规模分档,500 人以内写扩散、千人以上读扩散、超大群用在线广播加分档降级;连接层自研与采购的分界,取决于协议掌控力、单位成本与团队长期维护能力,三者缺一就不要自研。

笔记加载中…