开源IM项目OpenIM第二版对于客户端架构进行了局部重构,解决了消息触发时序等bug,也梳理了内部模块。目前已经接近尾声,本文重点讲解SDK架构,以便大家深入了解OpenIM,并希望大家能深度参与开发。很多开发者有个误区,认为IM的挑战主要在服务端,当然服务端有其挑战,包括性能、压力、时延等,但优秀的IM架构需要服务端和客户端完美配合,比如消息对齐机制,本地缓存和后台数据同步,app多端如何实时同步。
github 6.5K star 具体地址:
OpenIM Corporation
github.com/OpenIMSDK
客户端重点问题总结:
(1)如何确保消息有序性;
(2)如何确保消息百分百可达;
(2)如果确保本地db和服务端数据的一致性;
(3)如何高效地实现多端同步;
(4)如果确保消息即时达到;
(5)消息发送的异步性,如何确保消息发送的一致性;
本文从架构角度重点解答第1,2两个问题
客户端模块划分和协程模型
![https://pic4.zhimg.com/80/v2-e18bb88ac7e0c9c5851468debbcfc7fb_1440w.jpg](https://static.studygolang.com/220318/1353c35d85fd91a99bc41061cc5f28ed.png)
WsConn:ws连接管理器。提供函数供其他方调用,具体包括:
(1)ws连接服务端,和OpenIM服务端保持长连接;
(2)关闭ws连接;
(3)通过ws发送请求;
WsRespAsyn:ws请求-响应同步器,因为ws是异步处理,需要把请求和响应关联起来,提供函数供其他方调用(消息发送,心跳发送,拉取历史消息等)
(1)getCh:为每个请求生成一个channel和msgIncr,使用map关联起来 msgIncr->channel
(2)notifyResp:对于ws收到的每个响应,通过msgIncr找到channel,并往channel发送响应,通知响应到达;
Ws:模块对WsConn 和 WsRespAsyn功能进行整合(1)请求响应同步化,提供函数SendReqWaitResp,调用者通过ws发送请求后,等待此请求的响应达到。(2)对于接收到的推送消息,把消息写入PushMsgAndMaxSeqCh channel,触发MsgSync消息同步协程。
具体实现:ReadData协程:接收服务端ws数据,并根据收到的数据类型(心跳、推送、踢出登录、拉取历史消息等),触发不同的逻辑处理,(1)对于主动发送请求的响应,则调用WsRespAsyn的notifyResp响应触发接口;(2)对于push消息,写入PushMsgAndMaxSeqCh ,触发MsgSync消息同步协程。
MsgSync:消息同步器;包含Ws 和conversationCh 、 PushMsgAndMaxSeqCh ,启动消息同步协程,对PushMsgAndMaxSeqCh 中的读取的数据做处理,具体包括:
(1)从PushMsgAndMaxSeqCh 读取服务端最大seq:SvrMaxSeq(由heartbeat写入的),对比本地最大seq:LocalMaxSeq和服务端最大seq: SvrMaxSeq,计算出缺失的seq,从服务器拉取历史消息,放入conversationCh ,触发conversation协程处理;
(2)从PushMsgAndMaxSeqCh 读取ws推送消息(由Ws的ReadData写入的推送消息),如果消息中的seq+1==LocalMaxSeq,则写入conversationCh,触发conversation处理,否则从服务端拉取消息补齐[LocalMaxSeq+1, seq],放入conversationCh ,触发conversation协程处理;
heartbeat:心跳管理器,包括MsgSync
(1)心跳协程,从服务端定时获取最大seq:SvrMaxSeq,然后把SvrMaxSeq让入PushMsgAndMaxSeqCh ,触发MsgSync消息同步协程。
心跳和消息同步融合
![https://pic1.zhimg.com/80/v2-21ce2e8b631a5ad67e26750fd5e768a4_1440w.jpg](https://static.studygolang.com/220318/df1f35b67dfae1c8618d5c2dcbef738a.png)
在心跳逻辑中触发消息同步
(1)心跳协程每30秒通过ws从服务端获取最大seq:SvrMaxSeq;
(2)心跳协程把SvrMaxSeq写入PushMsgAndMaxSeqCh ,触发MsgSync消息同步协程;
(3)MsgSync消息同步协程从PushMsgAndMaxSeqCh 中读取SvrMaxSeq,
(4)MsgSync消息同步协程对比本地最大seq: LocalMaxSeq和SvrMaxSeq,如果有缺失,则通过ws拉取历史消息,范围为:[localMaxSeq+1,SvrMaxSeq],
(6)MsgSync消息同步协程把拉取到的缺失的历史消息写入conversationCh 中;
(7)msg-conversation消息会话协程从conversationCh 中读取缺失的历史消息,按照消息类型做业务处理,具体包括消息落地本地db,触发新消息回调,触发会话改变回调(或新增回调)
push消息触发同步
{{https://pic1.zhimg.com/80/v2-050980bb8dcd15836e7cc9913151f584_1440w.jpg(uploading...)}}
以push消息触发同步:
(1)Ws的ReadData协程收到服务端的推送消息,
(2)Ws的ReadData协程把推送消息写入PushMsgAndMaxSeqCh ,触发MsgSync消息同步协程。
(3)MsgSync消息同步协程从PushMsgAndMaxSeqCh 中读取推送消息,如果msg中的seq比本地最大seq大1,则跳过第4步,直接写入conversationCh,触发conversation处理;
(4)服务端拉取消息补齐[LocalMaxSeq+1, seq],放入conversationCh ,触发conversation协程处理;
(5)以下基本与以心跳触发同步过程一样。
总结
由于seq是按照消息的客观事件递增生成的,对于推送消息,如果比本地最大seq大1,则消息可以无缝对接。否则要么推送的是过时消息,要么推送消息和本地消息有差异,需要通过ws拉取后写入本地,并触发相应回调。至此,客户端消息和服务端消息完全同步,并保证新消息回调的有序性。
重点参考我们开发文档:https://doc.rentsoft.cn/
有疑问加站长微信联系(非本文作者))