2)消息处理 :Processor 消费到 IM 发送事件首先做按接收者的地域分布(DTIM 支持跨域部署, Geography,Geo)做消息事件分流,将本域用户的消息做本地存储入库(消息体、接收者维度、已读状态、个人会话列表红点更新),最后将消息体以及本域接收者列表打包为 IM 同步事件通过异步队列转发给同步服务。
3)消息接收 :同步服务按接收者维度写入各自的同步队列,同时查取当前用户设备在线状态,当用户在线时捞取队列中未同步的消息,通过接入层长连接推送到各端。当用户离线时,打包消息数据以及离线用户状态列表为 IM 通知事件,转发给通知服务的 PNS 模块,PNS 查询离线设备做三方厂商通道推送,至此一条消息的推送流程结束。
4.3、存储模型设计
了解 IM 服务最快的途径就是掌握它的存储模型。
业界主流 IM 服务对于消息、会话、会话与消息的组织关系虽然不尽相同,但是归纳起来主要是两种形式:写扩散读聚合、读扩散写聚合。
DTIM 对 IM 消息的及时性、前后端存储状态一致性要求异常严格,特别对于历史消息漫游的诉求十分强烈,当前业界 IM 产品对于消息长时间存储和客户端历史消息多端漫游都做得不尽如人意,主要是存储成本过高。因此在产品体验与投入成本之间需要找到一个平衡点。
采用读扩散:在个性化的消息扩展及实现层面有很大的约束。
采用写扩散带来的问题也很明显:一个群成员为 N 的会话一旦产生消息就会扩散 N 条消息记录,如果在消息发送和扩散量较少的场景,这样的实现相比于读扩散落地更为简单,存储成本也不是问题。但是 DTIM 会话活跃度超高,一条消息的平均扩散比可以达到 1:30,超大群又是企业 IM 最核心的沟通场景,如果采用完全写扩散所带来存储成本问题势必制约钉钉业务发展。
在同步服务中,采用以用户为中心,将所有要推送给此用户的消息汇聚在一起,并为每个消息分配唯一且递增的 PTS(即位点,英文术语Point To Sequence),服务端保存每个设备推送的位点。
通过两个用户 Bob 和 Alice,来实际展示消息在存储系统中存储的逻辑形态。例如:Bob 给 Alice 发送了一个消息”Hi! Alice“,Alice 回复了 Bob 消息”Hi! Bob“。
当 Bob 发送第一条消息给 Alice 时,接收方分别是 Bob 和 Alice,系统会在 Bob 和 Alice 的存储区域末尾分别添加一条消息,存储系统在入库成功时,会分别为这两行分配一个唯一且递增的位点(Bob 的位点是 10005,Alice 的位点是 23001);入库成功之后,触发推送。比如 Bob 的 PC 端上一次下推的位点是 10000,Alice 移动端的推送位点是 23000,在推送流程发起之后,会有两个推送任务,第一是 Bob 的推送任务,推送任务从上一次位点(10000) + 1 开始查询数据,将获取到 10005 位置的”Hi“消息,将此消息推送给 Bob 的设备,推送成功之后,存储推送位点(10005)。Alice 推送流程也是同理。Alice 收到 Bob 消息之后,Alice 回复 Bob,类似上面的流程,入库成功并分配位点(Bob 的位点是 10009,Alice 的位点是 23003)。
比如:此时 Bob 登录了手机(该设备之前登录过钉钉),同步服务会获取到设备登录的事件,事件中有此设备上次接收数据的位点(比如 10000),同步服务会从 10000 + 1(位点)开始查询数据,获取到五条消息(10005~10017),将消息推送给此台手机并更新服务端位点。此时,Bob 手机和 PC 上的消息一致,当 Alice 再次发送消息时,同步服务会给 Bob 的两台设备推送消息,始终保持 Bob 两个设备之间消息数据的一致性。
最常见的问题:就是设备离线重新登录,期间该用户可能会累计大量未接收的消息数据,比如几万条。如果按照上面的方案,服务端在短时间会给客户端推送大量的消息,客户端 CPU 资源极有可能耗尽导致整个设备假死。
其实对于 IM 这种场景来说:几天甚至几小时之前的数据,再推送给用户已经丧失即时消息的意义,反而会消耗客户移动设备的电量,得不偿失。又或者节假日大群中各种活动,都会有大量的消息产生。
对于以上情况:同步服务提供 Rebase 的方案,当要推送的消息累计到一定阈值时,同步服务会向客户端发送 Rebase 事件,客户端收到事件之后,会从消息服务中获取到最新的消息(Lastmsg)。这样可以跳过中间大量的消息,当用户需要查看历史消息,可以基于 Lastmsg 向上回溯,即省电也能提升用户体验。
还是以 Bob 为例:Bob 登录了 Pad 设备(一台全新的设备),同步服务收到 Pad 登录的事件,发现登录的位点为 0,查询从 0 开始到当前,已经累计 1 万条消息,累计量大于同步服务的阈值,同步服务发送 Rebase 事件给客户端,客户端从消息服务中获取到最新的一条消息“Tks !!!”,同时客户端从同步服务中获取最新的位点为 10017,并告诉同步服务后续从 10017 这个位置之后开始推送。当 Bob 进入到和 Alice 的会话之后,客户端只要从 Lastmsg 向上回溯几条历史消息填满聊天框即可。