先交代一个背景:我做这个消息转发子服务的时候,项目已经跑了大半年,最初的版本里转发逻辑是直接写在网关里的。当时觉得天经地义——用户连上 WebSocket 网关,A 给 B 发消息,网关查一下 B 在哪个连接上,直接推过去,完事。直到网关从 1 台扩到 8 台,线上开始出现"为什么 B 明明在线却收不到消息"的工单,我才意识到一个问题:当系统从单机长连接变成微服务架构下的多网关集群时,"找 B 在哪儿"这件事本身,已经复杂到必须有一个独立的子服务来专门负责。
这篇就当是复盘。我会把消息转发子服务的设计动机、完整链路、核心代码、以及我在实际联调和压测中踩过的坑全部展开讲。适合正在做即时通讯相关功能的团队参考,也适合刚接触 Spring Cloud 微服务、想知道消息模块该怎么拆的人。内容不绕弯子,直接说方案和理由。
1. 消息转发子服务到底在 IM 架构里扛什么活
1.1 从网关内转发拆出来的真正原因
很多人觉得"转发"不就是把消息从 A 的网关推到 B 的网关吗,为什么非要多一个服务?我先说说在网关里直接转发会遇到什么。
网关这个节点的本质是有状态的长连接管理。一个 Netty 进程挂了,它上面挂的几万个连接就全断,所以网关必须是无状态可水平扩展的——连接被路由到哪个网关,是由负载均衡决定的,用户自己不知道也不关心。但如果把转发逻辑写在网关里,问题就来了:A 和 B 可能不在同一个网关实例上,网关 A 根本不知道 B 连在哪个实例。
早期有人用广播解决:A 的网关广播"这条消息给 B",所有网关各自查本地连接,谁有 B 谁推。听起来简单,但一旦群里几十条消息、几百个在线成员,广播风暴能把整个集群打垮。而且广播只能解决"在线转发",解决不了离线消息存储、多端同步、消息回执、重复消息去重这些一连串问题。
所以拆出单独的消息转发子服务,本质原因是:网关只做"连接管理和字节流收发",把消息的"路由决策 + 投递调度 + 可靠性兜底"集中到一个独立服务里。这样网关可以继续做薄,转发服务独立扩缩容,聊天的业务逻辑也不和长连接生命周期耦合。
1.2 转发服务要负责的事情清单
拆出来之后,转发服务不是简单的"查路由然后推消息",它实际承担了这些职责:
- 会话路由:维护"用户 ID -> 当前连接的网关实例 + 连接 ID"的映射关系,并在连接建立/断开时更新。
- 在线投递:把消息推到接收方当前所在的网关,由网关写回连接。
- 离线补偿:接收方不在线时,消息进入离线存储,登录后再拉取。
- 多端同步:同一个账号在手机、PC、Web 多端在线时,消息要按设备维度 fan-out。
- 消息回执:接收端 ack 后更新投递状态,超时未 ack 的需要重试。
- 幂等去重:网络抖动导致生产者重发时,消费者侧不能重复落库、不能重复推送。
- 顺序保证:同一个会话内的消息,到达顺序不能乱。
这一串职责如果堆在网关里,网关代码会膨胀到没法维护。拆成独立服务后,每个职责都能用独立的模块去实现、测试、压测,出问题也容易定位。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 链路剖析:一条消息从发送端到接收端的完整旅程
2.1 会话路由表:转发服务的心脏
转发服务要工作,第一件事是知道"谁在哪个网关"。我这边用 Redis 存路由表,结构很简单:
code复制route:user:{userId} -> { gatewayId: "gw-3", connId: "conn-8f3a...", device: "android" }
key 是用户维度,value 是当前有效的连接信息。这里有个关键细节:连接信息不是登录时写一次就完事的。用户的网络会切换、网关会重启、连接会被服务端主动断开,路由表必须跟随连接生命周期实时变更。
我的做法是两段式更新:
- 客户端连接上网关后,网关向转发服务上报
connect事件;转发服务写 Redis。 - 客户端断开、或者网关心跳发现连接死了,网关上报
disconnect事件;转发服务删 Redis 记录。
网关上报用的是内部 MQ,不直接调转发服务的接口。因为网关可能在同一瞬间有大量连接变化(比如网关重启,几万连接同时断开),直接 HTTP 调用会把转发服务打挂,走 MQ 可以削峰。同时每一条 Redis 记录都带 TTL,比如 90 秒,网关侧每 30 秒续期一次。这样即使 disconnect 事件丢失,记录也会自动过期,不会出现"用户早断了,路由表还指向旧连接"的脏数据。
2.2 在线、离线、多端三种形态的处理差异
消息进入转发服务后,第一件事是查路由表,但结果不一定只有"有连接"和"没连接"两种,实际会分三档:
在线且单端。路由表只有一条记录,直接把消息投递给对应网关。网关根据 connId 找到连接,写回客户端。这是最简单的情况。
在线但多端。路由表里有手机、PC 两条记录。转发服务要把消息复制成两份,分别投递到两个网关。这里需要注意:不同端的推送通道可能不同,手机端可能走 WebSocket,也可能走 APNs/厂商推送,而 PC 端只走 WebSo
