从仿微信IM实战剖析长连接、消息可靠性与音视频通话链路设计

从仿微信IM实战剖析长连接、消息可靠性与音视频通话链路设计 简介即时通讯IM系统是移动互联网应用的基础设施其核心在于稳定可靠的长连接和高效的消息分发机制。本文从工程师实战视角出发围绕IM系统的通信层设计深入剖析基于Netty自定义TCP协议的长连接方案对比WebSocket在不同网络环境下的优劣并讲解消息幂等、离线补拉、未读数与会话列表等关键机制。同时结合位置服务、机器人回调以及实时音视频通话等高频业务场景介绍了Redis GEO、WebRTC、STUN/TURN与SFU的工程落地经验。针对弱网、高并发、NAT穿透失败等典型问题本文提供了一套可复用的排查方法论。无论您是正在自研IM系统还是评估私有化沟通工具的技术风险都能从中获得从架构选型到问题定界的完整参考理解消息不丢、不重、秒达背后的工程权衡。1. 项目概述与整体设计思路做聊天IM精仿微信这半年我一直在折腾这么一套东西。从单聊、群聊、朋友圈到摇一摇、附近的人、收藏、扫码、机器人、名片最后还加了实时音视频通话几乎把微信移动端的主要交互都复刻了一遍。这篇东西适合谁看大概两类一类是正在做类似IM项目的开发同学想知道功能拆开之后每一块到底怎么做另一类是准备在团队内部做私有化沟通工具的技术负责人拿这套思路去评估工作量和风险。我先把结论放在前面聊天界面是最容易的真正难的是长连接可靠性和音视频的链路质量这两块如果前期没有设计好后面会一直被坑。1.1 仿微信IM系统到底解决了什么问题很多人看到仿微信这三个字第一反应是套壳、做UI。其实把一个IM做到真正像微信能用的程度要解决的是一整套通信问题消息能不能秒达、会不会丢、客户端断网重连之后能不能补拉消息、群聊里几百人同时发消息会不会把服务搞挂、朋友圈的拉取会不会越翻越慢、摇一摇和附近的人背后实际上是位置与随机匹配逻辑而实时音视频通话又涉及NAT穿透和流媒体转发。这些问题单拎出来哪一个都能写长篇组合在一起就是一个全栈项目。回到项目本身我的目标不是做一个能上架的微信克隆版而是验证一条完整的技术链路移动端App 接入层长连接 业务服务 多媒体存储 位置服务 音视频网关。这样一套链路既可以用在企业私有化部署场景里也可以作为教学项目把“IM”这个抽象概念落到代码上。很多团队想要一套内部沟通工具但直接拿开源的Matrix或OpenIM又觉得定制成本高自己从头写又怕坑太多。我这套东西正好卡在中间功能形状贴近微信技术选型不依赖特定商业服务所有模块都能拆开替换。1.2 技术选型与整体架构演进技术栈的选型我反复纠结过三轮。第一轮想得比较简单后端用Spring Boot长连接直接上WebSocket客户端用Flutter一套代码跨Android和iOS。但做到后面发现群聊消息推送和音视频信令对吞吐量的要求不是普通HTTP轮询能扛住的Flutter在音视频这块又绕不开原生插件于是调整成后端Java Netty做长连接接入层业务逻辑用Spring Boot消息队列用RocketMQ做削峰和异步解耦缓存和离线数据用Redis持久化用MySQL。音视频通话没有自己做全套WebRTC服务端而是把WebRTC核心信令集成在IM服务里媒体转发用LiveKit组件这样工作量可控穿透能力也有保障。客户端这边我最终没有用Flutter承接所有功能因为扫码、摇一摇、实时音视频采集这些功能跨端框架往往要写一堆Platform Channel反而比原生更麻烦。所以我做了折中聊天核心界面用Flutter实现涉及硬件调用的扫码、摇一摇、音视频通话用Android原生Activity和iOS ViewController通过桥接层互调。这套混合架构对于一个人独立开发来说是最省力的UI组件复用度高底层能力又不会被跨端框架锁死。架构演进上我踩过最重的坑是早期把消息直接通过HTTP接口轮询拉取导致服务端压力巨大客户端也频繁出现消息延迟。后来改成Netty长连接架构变为客户端 - Nginx LB - Netty Gateway - RocketMQ - 业务服务 - MySQL/Redis。Netty Gateway负责维持连接、透明转发消息包、代理身份校验业务服务不持有连接状态只处理业务逻辑。这样的好处是Netty层可以水平扩展就算某个Gateway节点宕机客户端重连到其他节点只要Redis里的session信息还在消息链路就不会断。2. 通信层与消息系统设计2.1 长连接方案为什么最终没有只用WebSocket开发前期我确实用WebSocket写过一版长连接调试工具非常方便浏览器也能直接连。但到了生产化验证时发现两个问题一是移动网络环境下WebSocket在切换基站时经常发生“假死”连接没有主动断开但消息就是发不出去需要额外的应用层心跳和重连机制维护二是WebSocket基于HTTP协议升级在服务端需要维护大量HTTP Upgrade状态遇到超长连接的资源控制不够精细。所以后期我改成了以Netty自定义TCP私有协议为主同时保留WebSocket端口作为Web端和调试入口。这里要提醒一句并不是说WebSocket不能用很多成熟IM也有WebSocket接入方案。我的建议是如果你的用户场景主要是Wi-Fi环境、Web端和移动端共存用WebSocket可以省去很多协议解析工作如果目标用户大量使用移动网络、对弱网体验要求高还是老老实实用自定义TCP协议或者至少加上更强的应用层心跳策略。我在TCP协议里做的数据帧结构很简单4字节魔数 1字节版本号 1字节消息类型 4字节消息体长度 N字节消息体消息体统一用Protobuf序列化。之所以不用JSON直接裸传是因为长连接场景下频繁序列化大JSON对CPU和带宽都不友好Protobuf体积小、解析快线上问题也容易定位。2.2 消息模型与通知消息协议消息协议是整个IM系统的核心前期没设计好后期加一个功能就要改协议。我给出的消息模型是这样设计的message Message { int64 msg_id 1; // 消息唯一ID全局递增由服务端生成 int64 from_uid 2; // 发送者ID int64 to_id 3; // 单聊接收者ID群聊群ID int32 chat_type 4; // 1单聊2群聊 int32 msg_type 5; // 1文本2图片3位置4名片5语音6视频7系统8机器人 bytes content 6; // 消息内容按msg_type不同对应不同PB结构 int64 client_msg_id 7; // 客户端生成的临时ID用于幂等去重 int64 timestamp 8; // 客户端发送时间 int32 status 9; // 消息状态0正常1撤回2删除 }客户端发送消息时先生成一个client_msg_id内容结构包括客户端时间戳和正文。服务端收到后先做幂等检查如果Redis里没有这个client_msg_id就继续处理否则直接返回重复响应。这样即使客户端超时重传也不会产生两条重复消息。client_msg_id保存周期我设置为7天这个时间和离线消息保留时间保持一致避免用户清理历史记录后仍能重试成功。2.3 单聊和群聊的消息收发流程单聊的流程相对简单客户端A发消息到接入层接入层校验后投递到消息队列业务消费者写库、刷新会话时间、计算未读数再通知接入层向在线用户B推送。A发送成功后客户端会收到服务端回执回执里携带服务端生成的msg_id客户端用它替换本地临时ID同时本地状态从“发送中”变成“已发送”。如果服务端回执返回失败客户端会触发重试间隔按1秒、3秒、5秒递增最多重试3次。群聊比单聊绕一些难点在于消息需要分发给所有群成员。我的方案是“写扩散”和“读扩散”结合成员数量少于200人的群直接写扩散也就是给每个在线成员推送一份消息到自己的会话流里成员数超过200人的大群则采用读扩散成员进入群聊页面时主动拉取群消息表平时只更新群未读计数。这种组合方式写起来不复杂但能有效避免上万人群聊时写量爆炸的问题。群聊消息的已读回执我没有做全量记录只统计了已读人数因为微信本身也只显示群成员是否已读不做逐人详情这个数据用Redis计数器就能维护。2.4 消息可靠性与幂等机制消息可靠性是IM最容易被低估的部分。做过IM的人都知道丢一条消息比发不出去严重得多用户体验是直接“人没了”。为了保证不丢消息我的做法是三级确认第一级客户端发起消息后等待服务端回执回执超时则重试。第二级服务端保存成功后再向接收方推送接收方收到后返回ACK。第三级如果接收方不在线或者ACK超时服务端把消息写入Redis离线队列并标记接收方会话的未读计数等接收方上线后主动拉取。这套机制保证了消息至少被接收方拉取到一次但不保证不重复所以配合client_msg_id去重最终实现“至少一次不重复落地”。实际写代码时有一个容易踩的坑离线队列不能把全部消息都存在Redis里并无限期保留内存扛不住。我给Redis离线队列里的每条消息加了TTL默认7天同时消息全文在MySQL保留30天。接收方上线拉离线消息时只拉最近7天内的离线消息。更早的消息用户需要通过聊天记录翻页拉取这样既保证了即时体验又不会让Redis包揽所有历史存储。3. 核心业务功能模块实现3.1 单聊与群聊的会话列表和未读数设计会话列表看起来是个简单的列表实际上要做对细节。微信的会话列表是按最后一条消息时间倒序排序的而且会话的未读数要能实时更新。我的实现是每个用户维护一份Redis ZSETkey为conversation_list:{uid}member为会话ID单聊是对方uid群聊是群IDscore为最后一条消息的发送时间戳。收到新消息时更新ZSET的score如果没有这个会话就加进去。列表页读取时先从ZSET取出前20个会话ID再从Redis哈希表里批量取会话缩略信息会话类型、最后消息、时间、未读数等。并发量高的情况下Redis ZSET依然是性能瓶颈的常见点所以我做了分片把用户按uid散到32个分片Redis实例上实测十万在线用户下写ZSET的压力是可以接受的。未读数不能只存在服务端还要在客户端本地有个缓存。我的做法是服务端在推送新消息时带上unread_count增量客户端本地自增。用户点击某个会话进入聊天页后客户端上报一个批量已读请求服务端把该会话未读数清零。这里要注意并发场景如果用户同时打开多个会话未读数不能互相干扰所以客户端上报时只能针对当前打开的会话ID进行清零其他会话的未读保持不变。3.2 朋友圈动态的时间线与权限控制朋友圈是另一个容易低估的模块。它的核心不是发动态而是朋友圈的时间线是“好友关系动态发布”的复杂结果集。我的表结构比较简单动态表保存内容正文、发布用户ID、可见范围、地理位置和发布时间动态媒体表保存图片或视频的URL和排序评论表保存评论内容、评论者、回复对象点赞表保存动态ID和点赞用户ID。这里最关键的一点是朋友圈拉取必须做分页而且分页要基于“时间游标”不能基于传统的页码。因为用户发布动态是动态变化的如果用页码分页刷着刷着会出现重复或漏项。权限控制上我实现了三种可见范围公开、仅好友、私密。公开和私密都容易判断仅好友有一个隐藏坑朋友圈的动态在好友列表里可见但这个动态发布时好友关系可能已经被删除了。所以每次拉取朋友圈时我都需要去重新校验当前用户与动态发布者的好友关系而不是信任发布时打上的标签。如果好友数量比较多这个校验会比较吃性能我采用了布隆过滤器先过滤掉大部分“非好友”再对剩余少数做精确查库实测性能影响很小。时间线构建我用了“推拉结合”普通用户朋友圈数据量不大每次刷新的前几页直接读取自己好友的动态列表按时间聚合对于粉丝特别多的大V账号则把动态主动推送到关注者的粉丝时间线Redis队列中。在这个项目里用户量级还不是特别大所以主要用了拉取模式推模式留了扩展接口。3.3 收藏与扫码的通用方案收藏功能本质上是一个“资源引用管理器”。收藏的不只是文字或图片还包括语音、位置、名片、文件等甚至收藏一条聊天记录时会包含多条子消息。所以我没有为每种消息类型单独建表而是用了一张收藏表加一张收藏内容表内容表里存content_type和content_id具体内容仍然通过消息ID去消息表里查找。这样做的好处是收藏展示时高度统一坏处是如果原消息被删除收藏内容也需要额外做兜底。我在收藏时会把关键内容做一次快照文本、图片URL、卡片信息避免原消息删掉后收藏页变成死链。扫码模块我分成了两个场景。第一个场景是App内扫一扫调用系统相机识别二维码第二个场景是扫码枪输入比如仓库、门禁场景。App内的二维码内容我定义了一套自定义协议比如im://uid/12345表示查看用户im://group/join?gid6789表示加群im://url/http%3A%2F%2Fxxx表示打开链接。服务端下发一个“二维码内容生成接口”客户端用ZXing生成二维码图片扫到后通过解析器分发到对应页面。这里有个坑如果二维码内容里带中文或URL必须做URL编码否则扫描后解析会乱码。扫码枪我踩过更大的坑下面问题排查部分专门说。3.4 名片与位置消息名片消息看起来只是发一张卡片其实背后是一套“用户信息摘要”。发送名片时客户端会把对方用户的头像、昵称、签名、用户ID打包成一个结构体放进消息的content字段。接收方点击名片会先拉起一个用户信息页再通过接口获取最新的用户资料。这里必须注意名片快照里的昵称头像可能过期所以点击后一定要去服务端重新拉取不能用名片里的静态数据直接显示全套信息。位置消息同样简单但要注意经纬度的精度和坐标系。国内地图服务一般用GCJ-02坐标系而WebRTC定位接口返回的是WGS-84坐标两者互换有偏移。我在发给好友位置时客户端已经选择了地图选点可以直接把GCJ-02坐标显示为地图缩略图。但如果是从GPS原始数据里直接取到的坐标必须先转成GCJ-02再发否则位置缩略图和真实位置相差几十米。服务端只负责存储和转发不做坐标系转换转换统一放在客户端做避免不同端算法不一致。4. 特色功能摇一摇、附近的人、机器人、实时音视频4.1 摇一摇的事件匹配算法摇一摇的核心是“同一时间窗内随机配对”。客户端侧我用Android的SensorManager和iOS的CoreMotion监听加速度计检测到一段连续快速变化超过阈值的加速度后触发一次摇动事件然后调用服务端的匹配接口。服务端收到请求后把用户加入Redis的摇一摇池子池子的key可以按时间片切分比如每30秒一个桶用户在30秒内提交的摇动会被放入同一个桶。匹配规则很简单如果桶里有其他用户随机取一个返回给对方如果桶里已经等待了超过一定时间我设置为10秒则开始新的桶。这里有一个体验细节微信摇一摇是“同时摇动才能配对”所以客户端接到接口响应后会有一个短暂的转场动画让用户感觉是在实时搜索。另外摇一摇的结果页只能展示用户基本信息不暴露详细位置和通信方式要让用户决定是否发起打招呼。摇一摇高并发下Redis读写压力不大但要注意防止恶意刷接口。我在接口上做了限流每个用户每分钟最多摇10次超过直接返回操作频繁。还有两个坑一是iOS系统对传感器读取有系统级抖动不能只用线性加速度大于一个固定值判断我用的是峰值与均方根结合检测更稳二是摇一摇期间用户可能误触手机产生多次摇动客户端要做防抖同一个用户1秒内的多次摇动只算一次。4.2 附近的人基于Redis GEO附近的人要解决的问题是“给定一个经纬度找出半径范围内的其他在线用户”。我直接使用了Redis GEO这样不用自己算球面距离Redis内部用geohash和有序集合实现查询性能很好。实现时用户打开附近的人页面时上报一次当前位置服务端把用户ID和经纬度写入geo:nearby_users并设置150秒过期。用户刷新列表时通过GEORADIUS查询以自己为圆心、半径10公里内的用户按距离升序返回。这里有两个实际经验。第一附近的人不需要实时更新所以上报位置时可以做节流5分钟内的重复上报忽略减轻写压力。第二用户关闭附近的人时必须调用接口删除自己的位置记录防止别人还能搜到“幽灵用户”。另外经纬度的精度也影响隐私我返回给客户端的距离只显示到几百米精确到米会让人有被跟踪的感觉。附近的人列表页还要做“只看女/只看男”的筛选这需要在Redis GEO里存用户信息时附带一个profile键但Redis GEO的member本身不能存附加字段所以我是用geo:nearby_users:{sex}分集合存储性别筛选时只查对应集合省去了逐用户回查数据库的开销。4.3 机器人消息的集成方式机器人模块是我这个项目里比较有意思的设计。机器人本质上是另一种类型的用户但它没有人类操作界面而是通过服务端到服务端的回调来响应消息。我定义了三种机器人客服机器人、群管理机器人、智能问答机器人。接入方式上我提供了一个“机器人接入后台”让开发者填一个回调URL、一个Secret Token以及这个机器人能处理的消息类型。当用户发送给机器人账号的消息落库后消息消费者会异步POST到回调URL机器人服务收到请求后可以返回一个或多个回复消息IM服务再把回复投递给用户。在实际场景里要注意机器人回调不能阻塞IM主流程。我最初是同步等待HTTP响应经常遇到机器人服务性能抖动导致消息延迟后来改成异步回调IM服务先确认消息已收到机器人服务在自己的队列里处理通过另一个“主动下发消息”接口把回复发回IM。这样即使机器人服务挂了IM本身不受影响。机器人的另一个重要应用是群管理。我在群里内置了一个小助手机器人新成员入群时自动发送欢迎语群里出现违禁词时自动撤回并提醒全体成员这个功能也是通过机器人指令实现的。这里需要一个“消息钩子”机制任何群消息在推送给其他成员之前先经过机器人处理器机器人可以决定放行、撤回或改写。钩子机制很容易造成性能瓶颈所以我只对包含机器人或者指定前缀如“/cmd”的消息做全量处理其余消息直接透传。4.4 实时音视频通话的关键链路实时音视频通话是这个项目里技术含量最高、也最让我掉头发的一块。用WebRTC做一对一音视频通话大体的流程是发起方A创建offer通过IM信令通道发给BB创建answer回传给AA和B交换ICE候选地址协商成功后媒体数据直接通过P2P传输。信令通道我可以复用已经做好的长连接系统只是消息类型加一个SIGNALING类型承载SDP和候选信息。关键问题在于很多用户处于复杂NAT后面P2P直连失败率不低必须部署STUN/TURN服务器。我给TURN服务器的建议是如果项目规模不大直接用coturn开源方案单机撑几百路没问题。我的部署拓扑是STUN服务放在公网TURN服务同样放在公网当ICE协商发现需要中继时媒体流量会经过TURN转发。TURN服务器的带宽是整个音视频项目最大的开销一路720P视频通话大约需要1Mbps到2Mbps的上行/下行带宽如果是多人会议这个数字会成倍增加所以早期我限制一对一通话分辨率不超过720P多人会议不超过360P。在客户端实现上我用LiveKit作为SFU方案它内置了音视频转发、房间管理和录制功能省去自己写WebRTC服务端的复杂逻辑。我把LiveKit的业务API和IM系统做了联动用户点击视频通话时IM服务先创建一个会议房间返回房间名和token给双方双方客户端通过LiveKit SDK加入同一个房间。这样通话过程的信令和房间管理由IM服务负责媒体流完全走LiveKit两条链路互相独立稳定性高很多。如果在没有公网IP的内网环境测试一定要确保客户端能访问到TURN服务器的3478端口和UDP转发端口范围否则会发现既连不通又推不了流。5. 高频问题与排查实录5.1 消息延迟或丢失的排查思路消息延迟是IM最容易被用户感知的问题。我遇到最多的情况是客户端已经显示发送成功但对方很久才收到。排查时应该按下面顺序来先看接入层日志确认消息有没有被Gateway接收到。如果Gateway没有收到可能是客户端断网重连后没有上报离线消息拉取或者重连后没有重新订阅会话这时让客户端在重连成功后主动调用一次“同步增量消息”接口把断线期间的消息一次性拉下来。如果Gateway接收到了但消费者处理慢就要看消息队列的堆积情况我当时遇到过一次RocketMQ消费线程池配置过小消息一多就积压调大消费线程并发数后明显缓解。需要特别留意的是如果只有某一个人的消息延迟大概率是这个人的长连接被服务端误踢了。我的Gateway里有一个心跳超时踢线下线机制以前把超时时间设得比较短导致网络上偶尔丢一个心跳包就把用户踢下线客户端重连后又没有成功恢复session消息就只能走离线队列。这个问题的解法是把心跳超时时间放宽同时在Gateway收到重连请求时主动验证Redis里的token如果验证通过就销毁旧session并建立新session避免产生幽灵连接。5.2 音视频通话的NAT穿透失败音视频通话常见的问题是“一个能发起通话但对方一直听不到声音”或者“显示已连接但画面卡住”。第一种情况多半是麦克风权限没开启但更隐蔽的原因是WebRTC在ICE协商时没有成功收集到有效的host candidate只收集到了srflx candidate导致媒体流在经过NAT时无法到达对端。排查NAT穿透问题时我习惯在客户端日志里打印完整ICE候选列表如果发现对方只收到host类型的candidate说明STUN服务器没有正常工作。要检查STUN服务是否通过UDP 3478端口可访问以及防火墙是否放行了UDP端口范围。如果确定需要TURN中继但客户端没有拿到relay型的candidate基本都是TURN服务器的认证配置或者端口没有打通。我在项目中用coturn配置了用户名为随机token的方式每次呼叫前由IM服务生成一个短时有效的TURN凭证这样既安全又不用在客户端内置长固定账号。另外还有一个很隐蔽的问题如果App运行在HTTPS网页里WebRTC可能因为浏览器权限策略而无法获取摄像头或麦克风权限这需要在页面里先申请设备权限。而对于Android App如果targetSdkVersion较高必须在代码里动态申请RECORD_AUDIO和CAMERA权限申请时机最好放在用户点击音视频通话按钮之前不要在进入视频页之后再弹窗否则会出现黑屏。5.3 扫码功能在手机和扫码枪上的兼容问题手机扫码的坑主要集中在相机分辨率和对焦上。我在Android上用CameraX的ImageAnalysis如果分析图像的分辨率设置过高会频繁触发性能问题导致二维码识别率下降如果分辨率设置过低又扫不出小码。实测下来选择1280x720的预览分辨率配合ZXing的InvertedScanner兼容性最好。iOS上用AVFoundation输出视频帧要注意在识别成功之后关闭session不然扫码页面会一直盯着二维码提示“成功”非常尴尬。扫码枪的兼容问题更邪门。我这里遇到过一个案例霍尼韦尔的扫码枪连接Windows电脑后在Web页面扫码输入到输入框里的条形码后面总是多一个回车导致判断出错。原因是扫码枪出厂默认配置为在扫码成功后发送一个回车键。解决方案有两种一种是用扫码枪的配置手册扫一个“关闭回车后缀”的配置码让扫码枪只输出原始条码内容另一种是在前端接收扫码枪输入时监听keydown事件当捕获到Enter键时把之前输入的字符串作为条码内容提交并阻断默认的回车换行行为。第二种方法更通用不依赖具体硬件。另外还要注意输入框的focus位置如果页面有多个输入框扫码枪输入可能噼里啪啦弹到随机输入框里必须给扫码输入框设置独立焦点区域或者在全屏模式下统一接收。5.4 机器人回调超时和群消息风暴机器人回调超时是接入第三方AI服务时最容易出的问题。第三方接口响应普遍在几百毫秒到几秒不等如果IM服务同步等待机器人返回用户体验会被拖垮而且如果机器人服务崩溃IM服务还可能堆积大量线程。我的解决方案是IM服务收到发给机器人的消息后立即返回“已收到”并把消息内容写入Kafka的机器人消息主题机器人服务自己写一个消费者去消费处理完再调用IM服务“发送消息”接口回复。这一层解耦是必须的一开始偷懒做了同步调用线上机器人一旦遇到慢查询整个IM会话都会卡住。群消息风暴则是另一个经典问题。当某个大群里有多个机器人互相触发时会有几分钟内产生几千条消息的风险。我的做法是在机器人回调接口加了一层“每分钟发言次数限制”每个群每小时最多允许机器人发送10条消息超出部分自动降级为静默处理。同时在群聊推送层如果检测到同一个群在1秒内有超过100条消息就丢弃非人的普通通知只保留系统消息和人的消息避免把用户手机直接震爆。6. 写在最后一些个人的实践心得这套仿微信IM系统做到现在最深的体会不是哪个功能有多难而是所有功能背后都有一套“看起来简单、做起来坑多”的细节。聊天窗口、朋友圈、扫码这些功能单独看都是很常规的开发任务但组合在一起并让它们在弱网、高并发、多端设备下稳定跑起来才是真正考验架构能力的地方。如果让我重新做一遍我会在项目启动时就把“消息ID生成”“离线消息存储”“TURN服务器部署”这三件事再提前做重一点。消息ID生成要设计成可拆分、可路由的不要等量大了再换策略离线消息存储要有独立的存储引擎规划不要把Redis当垃圾桶TURN服务器最低也要两台双机房互备因为音视频一旦断流比消息延迟更让用户暴躁。最后分享一个实操中小技巧调试长连接相关问题时别急着看客户端代码先用命令行工具模拟一个合法连接抓包确认服务端真的发来了数据再排查客户端。我有很多次以为自己Netty写错了结果发现是防火墙把端口拦了顺手就把客户端和服务端日志一起看反而绕了弯路。这套东西后续我打算再补一个Web管理端用来查看在线人数、消息量、音视频通话质量指标慢慢把它从“能用”打磨成“好维护”。如果你的项目也正在做类似方向希望这篇内容能帮你避开一部分我已经踩过的坑。本文还有配套的精品资源点击获取