一、连接层设计如何管理百万长连接IM系统的第一道坎是连接管理。每个客户端维持一个TCP长连接百万连接意味着百万个Socket。常规BIO模型每个线程处理一个连接百万线程直接撑爆服务器。Netty的Reactor模型解决了这个问题。主线程负责Accept一组Worker线程负责IO读写业务逻辑交给异步线程池处理。Linux内核参数需要相应调整ulimit -n开到100万net.ipv4.ip_local_port_range扩大端口范围开启tcp_tw_reuse快速回收TIME_WAIT连接。二、协议设计JSON就够了有人迷信Protobuf说JSON慢。实测下来在IM场景下JSON序列化开销完全可以接受。JSON便于调试、跨语言友好、开发效率高。这套系统采用纯JSON格式一条消息的核心结构如下{ cmd: 101, seq: 12345678, from: 10001, to: 10002, type: 1, body: {text: 你好, at: [10003]} }协议层的一个核心设计是一端口多协议。Netty同一个端口同时解析自定义Socket协议、WebSocket协议和HTTP协议。通过判断前几个字节区分自定义协议以特定魔数开头GET/POST走HTTP特定握手请求走WebSocket升级。三、消息路由用户在哪台机器上分布式部署下用户A连接网关节点1用户B连接网关节点2。A发给B的消息需要从节点1路由到节点2。路由表存储在Redis中key为用户IDvalue为节点ID和连接ID。用户登录时注册路由心跳时更新最后活跃时间断开时清理。节点收到消息后查询目标路由若目标在本机则直接通过Channel写入若在其他节点则通过内部RPC转发。内部RPC复用Netty长连接池避免每次调用新建连接。四、消息同步读扩散与写扩散的选择群聊消息的存储有两种策略。写扩散是发一条群消息给每个群成员存一份读扩散是群消息只存一份成员拉取时计算自己该看到哪些。这套系统群消息采用读扩散朋友圈采用写扩散。群聊消息量大千人群发一条消息写扩散需要写入一千条记录磁盘扛不住。读扩散只需要写一条客户端本地维护已读位置拉取增量时用时间戳查询即可。已读回执单独记录到Redis格式为read_status:{group_id}:{user_id}:{last_msg_id}。发送方可查询哪些成员已读群聊场景下展示已读人数。五、离线消息与漫游三层保障不丢消息消息可靠性做了三层保障。第一层同步确认每条消息下发后客户端回复ACK服务端超时未收到则重推。第二层离线队列用户离线时消息存入Redis ZSET按时间戳排序上线后批量拉取。redis.opsForZSet().add(offline: userId, msgStr, msgTime); SetString msgs redis.opsForZSet().rangeByScore(offline: userId, 0, now);第三层漫游存储所有消息全量写入MySQL分表存储。用户换设备时可拉取最近7天或最近500条历史消息。漫游拉取接口支持分页和时间范围过滤千万级消息量的查询延迟控制在50毫秒以内。六、群聊互动成员与消息撤回成员功能在消息体中用at_uids字段记录被的用户列表。服务端收到后为每个被用户单独推送一条系统通知离线用户通过厂商推送收到提醒。消息撤回限制在2分钟内。撤回操作发送一条撤回指令服务端将原消息标记为revoked1所有在线客户端收到指令后替换本地显示。已离线的用户上线时拉取到已标记撤回的消息直接显示“对方撤回了一条消息”不展示原始内容。七、红包功能Lua脚本保证原子性红包涉及金额扣减和领取记录两个操作必须原子执行。用Redis Lua脚本实现服务端一次性将脚本传给Redis执行中间不会被其他命令打断。local remain redis.call(hget, KEYS[1], remainCount) if tonumber(remain) 0 then return -1 end if redis.call(sismember, KEYS[1]..:takers, ARGV[1]) 1 then return -2 end redis.call(hincrby, KEYS[1], remainCount, -1) redis.call(sadd, KEYS[1]..:takers, ARGV[1]) return 1红包金额采用二倍均值法随机分配每个红包的金额在剩余金额的0.01倍到剩余平均金额的2倍之间浮动。红包状态缓存在Redis异步写入MySQL做对账过期未领完的金额原路退回。八、语音会议信令与媒体分离多人语音会议不走IM的消息通道。单独部署SFU服务器负责媒体流转发IM系统只处理会议信令发起会议、邀请成员、成员入会离会、静音控制。信令通过原有WebSocket通道传输格式如{cmd:301,meetingId:mtg_123,action:invite,targetUid:10008}。接收方收到信令后直接连接SFU服务器建立WebRTC通道。IM服务器不承担任何媒体流量只处理信令转发单机负载极低。九、存储架构MySQLRedis的分层策略Redis存储热数据在线状态、未读计数、用户会话路由、最近聊天列表。MySQL存储冷数据消息漫游记录、用户资料、好友关系、红包订单。消息漫游表按用户ID哈希分16个库每个库按月份分表。查询历史消息时先计算用户所在分库再根据时间范围定位到具体月份表。离线消息从Redis拉取后立即清除不落MySQL减少不必要的写入压力。宠友IM官方演示示例https://www.chongyou.info/1/product/im.html