IM 技术演进阶段
IM v1.0 阶段(简单、可用)
实现功能
- 用户/客服接入
- 消息收发
- 咨询列表管理
设计原理
- 通过「消息转发模块」中消息轮询协程操作 Redis 缓存进行消息分发
- 使用 Protobuf 解决数据传输问题
- 使用 Java NIO 的开源框架 Netty 异步非阻塞、事件驱动、高性能、高可靠、高可定制性的网络应用程序
技术选型
- (v1.0 选型)基于 Scoket 原生:代表框架 CocoaAsyncSocket
- (v2.0 选型)基于 WebScoket :代表框架 SocketRocket
- 基于 MQTT:代表框架 MQTTKit
- 基于 XMPP:代表框架 XMPPFramework
两种方案设计
- 集中式:简单的单通、双通,延迟率要求不高的消息发送。
- 分布式:实时的双通、多通,延迟率高要求的视频/语音通话。
问题
FAQ:TCP 选型,实现通讯信息的真正加密,复杂场景下更灵活的通信。
问题 1:分/黏包
- 分包(IP分片传输导致):指接受方没有接受到一个完整的包,只接受了部分。
- 黏包(TCP协议本身合并机制):指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
解决:
- 方案一:包头长度字段
- 方案二:包尾分隔标识
应用:消息头长度为2个字节,所以消息包的最大长度需要小于65536个字节,netty会把消息内容长度存放消息头的字段里,接收方可以根据消息头的字段拿到此条消息总长度。
问题 2:数据丢失
- 数据丢失,写 db 之前 App crash,虽然数据在网络层可靠抵达了,但没存进 db,下次用户打开 App 消息自然就丢失了,如果不在业务层再增加可靠性保障,网络层面不会重发,那么意味着这条消息对于 Receiver 永远丢失
解决:
- 方案一:应用层 Ack 消息(消息回调机制)
- 方案二:应用层 Seq ID(连续性编号)
IM v2.0 阶段
开发要求
- 提升服务业务功能
- 增加可选配置,例如自动回复、FAQ
发展历程
- 第一阶段:短轮询 Polling(频繁的异步 JavaScript 和 XML (AJAX) 请求来实现轮循)
- 第二阶段:长轮询 Long polling(由服务端决定回执的长轮询机制)
- 第三阶段:基于 AJAX、Flash Socket 等流实现
- 第四阶段:WebSocket HTTP 5.0 全双工
区别整理
- WebSocket 是应用层的协议,Socket 是传输层的抽象
- WebSocket 是全双工的 HTTP 通讯协议
设计原理
- 建立一条可复用、可检索的消息线路(通过 HTTP 协议进行一个握手的动作,然后单独建立一条 TCP 的通信通道进行数据的传送)
- 分配服务优化:平均、权重、排队、AI
- 消息服务配置:风险检测、离线存储、消息队列、更新未读数、自动回复
保障性机制
- 方案一:应用层 Ack (确认字符) 消息
- 方案二:应用层 Seq ID(每个 Message 分配一个 Seq ID,这个 Seq ID 对于单个用户的接受消息队列来说是连续的)
心跳机制
定时心跳包
每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。 ① 前台时,8秒发送一个心跳包; ② 切换到后台时,30秒发送一次,根据自己的实际情况修改一下即可。心跳包用于维持长连接以及检测长连接是否断开等。
智能心跳包
1)App 前台时:五次延迟测试确定网络情况良好,进入长心跳模式;在网络波动较大的情况,使用短心跳,保证收取消息相对及时。 2)App 后台时:先用几次最小心跳维持长链接,进入后台自适应心跳计算。(尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。)
重连流程
- 重连被触发时,如果该次连接成功,退出重连
- 反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为6次),并对重连次数计数,如果超过就会退出重连
- 休眠预设的时间后再次进行重连操作
重连触发条件分为三种:
- 主动连接不成功(主动连接Socket,如果连接失败,会触发重连机制)
- 网络被主动断开(正常建立连接,操作过程中,网络被断开,通过系统广播触发重连)
- 服务器没响应,心跳没回值(服务端心跳预设时间内没回值,客户端认为服务端已经断开,触发重连)
发送消息类别
- 一类是 IM 相关数据的请求(HTTP),例如:历史消息列表,会话列表等
- 二类是 IM 消息的发送(Socket),主要是文字消息,包括上传到服务器的资源文件链接等
- 三类是辅助通信类型(混合),一般封装进 SDK 内部,例如:心跳包、回执、鉴权等
对应后端 API 设计:2.1 保活心跳包(C->S) HeartbeatPackage【13】 2.2 响应心跳包(S->C) XXX【??】 2.3 探查客户端是否活跃(S->C) XXX【??】 2.4 拉取离线消息(C->S) ClientRequestMessage【13】 2.5 响应离线消息(S->C) Protobuf【22】 2.6 发送聊天消息(C->S) ChatMessage【10】 2.7 响应发聊天消息(S->C) Protobuf【29】 2.8 接收聊天通知(S->C) ChatMessage【10】 2.9 接收代办通知(S->C) TodoMessage【17】 2.10 漫游和搜索聊天历史(C->S) ClientRequestMessage【13】 2.11 响应漫游和搜索聊天历史(S->C) Protobuf【30】 2.12 获取好友列表(C->S) ClientRequestMessage【13】 2.13 响应好友列表(S->C) Protobuf【18】 2.14 获取圈子列表(C->S) ClientRequestMessage【13】 2.15 响应圈子列表(S->C) Protobuf【20】 2.16 获取代办列表(C->S) ClientRequestMessage【13】 2.17 响应代办列表(S->C) Protobuf【23】
业务缓存策略
(先查数据库后展示,没有再请求再显示)
1)首次会话:先查数据库 -> 显示UI -> 网络请求 -> 算出未读数据存入数据库 -> 显示UI 2)拉取历史数据:先查本地数据 -> 根据字段 msgid 是否为 0 请求数据并存储 -> 显示UI 3)单条消息发送:发送信息 -> 标记为插入失败 -> 插入数据库 -> 发送信息(成功接收回执:变为成功状态存储,发送失败或没有接收到回执:进入重发流程)-> 显示UI
IM v3.0 阶段
实现功能(服务拆分)
- 业务服务:客服群组/成员管理、搜索服务、质检服务、工单服务、优惠券、订单服务、自动回复、FAQ
- 用户服务:用户状态、行为轨迹(画像)、评价服务
- IM 服务:单聊、群聊、通知、私信、联系人、文件服务、风控服务、敏感词过滤
- 数据服务:采集、分析、统计
功能模块主要分为(现代 IM 系统中的消息系统架构——实现篇:www.infoq.cn/article/N6s…)
- 消息存储:读、写、持久化,单行写入,批量读取,多维检索、全文检索的模糊查询
- 关系维护:人与人的关系、人与群的关系以及人与会话的关系
- 即时感知:会话池方案,消息存储 > 第二类:同步库 > 新消息即时统计
- 多端同步:会话的未读消息数需要在应用服务侧维护、对自己的信息不计数在最新消息摘要中需要做更新
分页设计
- 规则:从数据库拉取
- 首次:sequenceId 范围 + 倒序
- 翻页:第一次请求的最小 sequencId 发起第二次请求
需求分析
- 可能在有些需求中,消息还会区分平台,比如ios/andoid/mac/windos等
- 关于信息的更新:某一个人的个人头像or昵称发生了变化,这时候,我们对应的消息页面的UI,朋友列表等需要更新。这种类型的通知,一般是通过tcp推送,后台通过查找该用户的uid然后查找到他的会话跟好友列表然后进行推送。
- 关于消息的设计:目前我所见是2种,一种是类型微信的常见,具有离线消息的概念,但是一旦客户端确认接收到,服务器将不会保存,也就是不能跨设备保存用户记录,一般用tcp实现。还有一种是类似钉钉那种,有一个同步消息的概念,这种情况下一般使用Tcp+Http实现。他们的逻辑相差还是比较大,会另外写一篇文章描述。