
一篇面向中高级 Java 工程师的架构拆解文。以「Spring Cloud Alibaba Netty RocketMQ」主流开源 IM 为蓝本讲清楚微服务怎么拆、WebSocket 长连接怎么扛、群聊扩散怎么优化、私有化怎么落。文末附源码模块走读与压测参考。现成的有钉钉、企微、Slack、飞书但金融、政企、制造业、自研 PaaS这几类场景里“能私有化、能改协议、能嵌业务” 是硬约束——SaaS 版给不了。源码及演示ms.jstxym.top于是 “Spring Cloud Netty” 这套 Java 系 IM 方案在过去三年反复被验证一、为什么 2026 年还要自己搭一套 IM人才栈匹配Java 17 Spring Boot 3.x 是大部分公司主力招人维护成本低生态完整Nacos 做注册/配置、RocketMQ 扛削峰、Redis 做状态与缓存、Sentinel 限流、Gateway 做入口——全是阿里系熟脸并发天花板够参考 im-ai-server 公开指标单集群百万级在线、99.9% 消息延迟 500ms、到达率 99.99%对中大型私企够用私有化交付成熟Docker Compose → K8s Helm → 裸机 systemd三种路径都有开源范本下面这套架构基本就是沿着 HuLa-Server、im-ai-server、xigua-cloud 三家开源实现抽出来的最大公约数。二、微服务拆分IM 不是单体能扛的IM 表面看是发消息底下至少拆 6–8 个微服务才不像玩具服务职责技术点gatewayWebSocket 升级、鉴权、限流Spring Cloud Gateway 自定义全局过滤器authJWT / OAuth2、设备绑定Spring Security 6 Redis Sessionim-wsNetty长连接接入、心跳、Channel 管理Netty WebSocketServerProtocolHandlerim-router用户→ws 节点映射、跨节点转发一致性哈希 / Redis 注册表msg-persist消息落库、漫游、撤回MySQL 分库 Elasticsearch 检索group / friend群关系、好友、组织架构常规 CRUD 缓存file图片/语音/视频MinIOS3 协议presence在线状态、多端同步Redis BitMap / PubSubconsole运营后台Vue 对应管理端 APIHuLa-Server 就是按这套思路拆的gateway / auth / im / ws / presence / base / systemxigua-cloud 则更轻量但同样走了 Gateway Nacos Dubbo 的 Alibaba 路线。 一个容易踩的坑Netty WS 节点要不要注册进 Nacos要。yl-IM 的做法是Spring 服务与 Netty 服务双注册这样 gateway 才能通过服务发现拿到 WS 实例列表做路由否则只能写死 IP。三、WebSocket 接入层Gateway 到 Netty 这一段3.1 Gateway 做的事Gateway 不直接 hold 长连接它底层是 Netty 但不是给你写ChannelHandler的地方它干三件前置活WebSocket Upgrade 拦截Sec-WebSocket-Protocol里塞 token避免 URL 带 token 被日志刷出来一致性哈希路由按userId算 hash 打到固定 WS 节点减少跨节点迁移鉴权失败提前踢JWT 过期 / 设备数超限 → 直接 401不进 Netty# gateway 路由示例简化spring:cloud:gateway:routes:-id:im-wsuri:lb:ws://im-ws-servicepredicates:-Path/ws/**filters:-StripPrefix1-ImConsistentHashuserId# 自定义过滤器ImConsistentHash这个过滤器是 yl-IM 里的一致性哈希思路翻版——从Sec-WebSocket-Protocol头里解出 userId用虚拟节点算到某个 im-ws 实例再改写lb:ws://的目标。3.2 Netty WS 服务端骨架publicclassImWebSocketServer{privatefinalintport;privatefinalImAuthHandlerauthHandler;privatefinalImMessageHandlermsgHandler;publicvoidstart(){ServerBootstrapbnewServerBootstrap();b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(newChannelInitializerSocketChannel(){OverrideprotectedvoidinitChannel(SocketChannelch){ch.pipeline().addLast(newHttpServerCodec()).addLast(newHttpObjectAggregator(65536)).addLast(newWebSocketServerProtocolHandler(/ws,null,true)).addLast(newImAuthHandler())// 握手阶段 JWT 校验.addLast(newImHeartbeatHandler())// IdleState 30s/3 次.addLast(newImMessageHandler());// 业务分发}});}}IntraChat 用的是TLV 自定义协议Magic(2) MsgType(1) BodyLen(4) Body而不是裸 JSON WS好处是粘包半包由长度帧自己解且以后可以无缝切 TCP 双通道——这是对标钉钉架构时值得借鉴的一点。3.3 心跳与连接管理publicclassImHeartbeatHandlerextendsIdleStateHandler{// 读空闲 60s → 写心跳 ping → 再 30s 没响应 → 关 channelpublicImHeartbeatHandler(){super(60,0,0,TimeUnit.SECONDS);}OverrideprotectedvoidchannelIdle(ChannelHandlerContextctx,IdleStateEventevt){if(evt.state()READER_IDLE){ctx.writeAndFlush(newPingWebSocketFrame());}}}Redis 里要维护两份映射user:{uid}→{nodeAddr, channelId, deviceType, lastActive}—— 单点路由presence:onlineBitmap —— 全员在线统计 状态广播四、消息可靠投递单机到分布式的三件事4.1 消息协议 MessageDTOpublicclassMessageDTOimplementsSerializable{privateStringmsgId;// 雪花算法privateLongsenderId;privateLongreceiverId;// 单聊对方uid群聊groupId负数为群标记privateIntegerchatType;// 1单聊 2群聊 3聊天室privateIntegermsgType;// 1文本 2图片 3语音 4视频 5文件privateStringcontent;// 文本直存媒体存 MinIO keyprivateLongseqNo;// 用户维度自增去重用privateLongtimestamp;}seqNo是关键——客户端用 seqNo 去重服务端用它做离线补偿。4.2 单聊路径本地 → 同节点 → 跨节点 → 离线sender - WS节点A - msg-persist 落库 - if receiver 在 A: 直接 writeAndFlush else if receiver 在 B: Redis PubSub / RocketMQ 发 user:{uid} 队列 - B 消费 - write else: 落离线表 下次上线拉 seqNoyl-IM 用的是一致性哈希选 Netty 实例 Redis 广播兜底的双保险简单场景下够用高并发建议换 RocketMQ 的广播/广播扩展模式因为 Redis PubSub 没持久化节点崩了消息就丢。4.3 群聊扩散写扩散 vs 读扩散小群200人写扩散进群就把msgId往每个人离线表插一份。实现简单群成员变更时要补历史。大群/万人群读扩散消息只存group_msg一张表群成员上线时按lastReadSeq拉。但所有人某人还是要写扩散做推送触发。RocketMQ 延迟队列在这里派用场群消息落库后发一条delay0的投递任务消费端批量查群成员在线表在线走 WS push离线走 APNs / 厂商推送 / 离线表。五、并发优化的几个关键细节1. 一致性哈希减少跨节点同用户稳定落同一 WS 节点Channel 不用迁移。虚拟节点数建议 160–256Nacos 实例上下线时抖动最小。2. Netty 的EventLoopGroup线程模型Boss 1–2 线程Worker CPU×2业务 Handler 里不许阻塞——鉴权、落库、发 MQ 全部丢线程池或 WebFlux 异步。HuLa-Server 走的就是 Webflux 异步架构扩缩容弹性更好。3. Redis Pipeline 批量 ACK客户端收到消息后发批量 ACK[msgId1, msgId2, ...]服务端 Pipeline 一次性HDEL offline:{uid}UPDATE msg SET statusREAD。4. 消息压缩文本 1KB 的建议 Gzip图片/语音走 MinIO 外链 缩略图。IntraChat 的 TLV 协议里 Body 字段天然适合塞压缩后的二进制。六、私有化部署从 Docker Compose 到 K8s6.1 依赖清单最小集组件版本参考用途JDK17 LTS运行 Spring Boot 3.xMySQL8.x关系数据 消息主表Redis6会话、状态、缓存RocketMQ5.x消息投递、延迟队列Nacos2.4.x注册 配置MinIO最新文件Sentinel Dashboard可选限流6.2 docker-compose.yml 骨架version:3.8services:nacos:image:nacos/nacos-server:v2.4.3environment:MODE:standalonerocketmq-namesrv:image:apache/rocketmq:5.2rocketmq-broker:image:apache/rocketmq:5.2depends_on:[rocketmq-namesrv]minio:image:minio/minio:latestcommand:server /data--console-address :9001im-gateway:build:./im-gatewaydepends_on:[nacos]im-ws:build:./im-wsdeploy:replicas:3HuLa-Server 官方仓库里就有配套的docker-compose.yml与「服务端部署文档」从 Nacos 配置 → MinIO 桶 → RocketMQ Topic → WS 副本数一步步可抄。6.3 K8s 交付要点WS 服务必须用 Service HeadlessGateway 侧的lb:ws://要靠 Headless SRV 解析到 Pod IPPod 反亲和podAntiAffinity保证 WS 节点打散到不同 Node避免整机宕机丢一半长连接HPA 按packets_per_second或connection_count扩WS 的 CPU 利用率往往不准连接数才是真瓶颈私有化客户常见要求单租户独立 namespace、配置文件外部化ConfigMap Secret、日志落本地盘 可选 ELK七、源码模块走读以 HuLa-Server / im-ai-server 为参照im-server/ ├── im-gateway/ # Spring Cloud GatewayWS 路由 鉴权过滤器 ├── im-auth/ # JWT、设备绑定、敏感词 ├── im-ws/ # Netty WS 接入Channel 管理心跳 ├── im-router/ # 一致性哈希 / Redis 用户路由表 ├── im-msg/ # MessageDTO、落库、撤回、已读 ├── im-group/ # 群关系、群成员缓存 ├── im-presence/ # Redis Bitmap 在线状态 ├── im-file/ # MinIO 上传 缩略图 ├── im-common/ # DTO、枚举、ConsistentHashUtil、ProtoBuf 定义 └── docker/ # compose / k8s yaml读源码建议顺序common → ws → router → msg → group。ws 里重点看ImAuthHandler握手怎么把 JWT 换成 userId 绑 Channel和ImMessageHandlerTextWebSocketFrame 怎么转 MessageDTO 进 Disruptorrouter 里重点看一致性哈希 Redis 注册表的读写msg 里重点看落库 MQ 投递 离线补偿三段事务怎么拆。im-ai-server 还多了 AI 那一轨DeepSeek 智能对话、内容审核它的架构文档仓库im-kit-doc里画了完整的分布式 IM AI 分流图想做IM 智能助理的可以顺过去看。八、压测参考与容量估算按 im-ai-server 公开口径倒推单 WS 节点8C16G 约扛 5–8 万长连接取决于消息速率纯在线可更高消息延迟P99 500msRocketMQ 同步落盘 WS push到达率99.99%MQ 持久化 离线表兜底线性扩展WS 节点加机器 → Nacos 自动发现 → Gateway 一致性哈希重分布自家压测建议用JMeter WebSocket Sampler或Gatling先压纯心跳保活看连接上限再压50% 单聊 30% 小群 20% 大群的混合场景看 MQ 堆积。九、还能往哪儿扩端到端加密参考 Telegram Secret Chat消息仅两端解密服务器只过密文IntraChat 路线图里有这条AI 原生 IM把 DeepSeek / 混元接进群聊“助理” 直接走 LLM 回im-ai-server 已经趟了前半段Flutter / Electron 多端yl-IM 配了 Flutter 前端、HuLa 配了 Electron Vue跨端这一块开源界已经补齐合规审计金融客户要消息留痕 操作审计ES 做全文检索 时序库做审计链 这套Spring Cloud Netty RocketMQ的 IM 源码拿 HuLa-Server、im-ai-server、xigua-cloud、yl-IM 四个仓库对照着读微服务怎么拆、WS 怎么接、群聊怎么扩、私有化怎么交基本就全串起来了。真正上线时瓶颈一般不在框架而在长连接数 × 消息速率 ÷ MQ 吞吐这一组乘积——压测早做扩容策略早定。