设备和服务端之间一直开着的一条 TCP 或 WebSocket 连接。有了它,服务端可以随时主动把消息推下去,不用等客户端来问。
术语表
本书里每个概念只用一个名字,中英文各一个。这里是它们在本书里的意思;别处常见的叫法写在“也叫”后面。只收已经发布的章节里用到的词,以后每发一章补一批。
基本概念
- 长连接long-lived connection
- ACK(确认)
接收方回给发送方的“收到了”。在本书里,服务端的 ACK 表示消息已经写进消息日志,不表示对方已经收到。
- 同步与异步的分界sync/async boundary
发送方要等的部分和不用等的部分之间的那条线。本书把它放在消息日志:写进日志之前发送方在等,之后的分发都是异步的。
- 写扩散write fan-out
发消息时给每个接收人的信箱各写一条,读的时候只看自己的信箱。写得多、读得快,适合人数不多的会话。
- 读扩散read fan-out
消息只在会话的时间线里存一份,每个人读的时候自己去取。写得少、读得多,适合大群。
- 业务层business layer
账号、好友、群、权限和审核这些规则。它在消息路径旁边:各层需要时调用它,消息不从它这里流过。
客户端
- IM SDK
装在每个 App 里的客户端库:发件箱和重试、同步、本地数据库,以及给 App 用的接口。
- 发件箱outbox
客户端里还没等到 ACK 的消息。断线或重启之后,从这里重发。
- 消息 IDmessage ID
客户端在发送之前给每条消息生成的唯一 ID,重发时不变。服务端靠它认出重发的消息。
- 同步游标sync cursor
每台设备一个,记着这台设备在自己的信箱里同步到了哪里。设备回来时,从游标之后接着拉。
- 已读游标read cursor
每个人在每个会话里读到了哪一条。存在服务端,同一个人的几台设备共用,用来算未读数和已读回执。和同步游标不是一回事。
连接层
- 网关gateway
连接层的服务器。每台持有大量长连接,负责鉴权、心跳和收发,不看消息内容。它是服务端里少数有状态的部分:重启会断开身上所有的连接。
- 接入调度locator
告诉设备该连哪台网关的服务。设备先问它,再去连网关。
- 心跳heartbeat
连接空闲时定期发的小包。用来发现已经断掉的连接,也防止运营商的设备把太久没说话的连接忘掉。
消息层(收)
- 序号(seq)
会话里每条消息的编号,由负责这个会话的消息服务连续分配。它决定会话里的顺序,也让客户端发现缺了哪一条。信箱另有一套按人递增的序号。
- 去重dedup
同一个消息 ID 再来一次时,返回第一次的结果,不再存一份。网络只能做到“至少送到一次”,去重让每条消息只显示一次。
- 按会话分片sharding by conversation
每个会话固定由一台消息服务负责。这样一个会话的序号只由一个人分配,顺序才有保证。
- 消息日志message log
持久化、只追加的队列,收和发之间的交接点。消息写进去才算收下,服务端这时回 ACK。通常建在消息队列上,比如一个按会话分区的主题。
分发层(发)
- 在线推送online push
经网关沿长连接推给在线设备。它只是提醒:推送丢了,设备靠拉取补上。
- 系统推送OS push
通过苹果 APNs、谷歌 FCM 或手机厂商的通道,发给没有在线连接的手机的通知。它属于第三方,尽力而为,不保证送达。
- 同步服务sync service
设备带着同步游标来拉时,它从信箱或时间线里把游标之后的消息一页页返回。
存储层
- 消息存储message store
长期保存的消息历史,每个会话一条时间线。分发层从消息日志读到消息后写入它,所以比 ACK 稍晚。
- 时间线timeline
一个会话的全部消息,按序号排好。读扩散时,每个人从这里读。
- 信箱inbox
每人一份的收件索引,记着“有哪些新消息要给你”,有自己按人递增的序号。写扩散时写它,设备按同步游标从它这里拉。
- 在线路由表online routes
记着每台在线设备连在哪台网关上。设备连上时由网关登记,分发时按它找到网关。