超时、重传、确认、去重——一条「你好」的六个报文
《即时通讯架构核心技术》第 1 篇:微信为什么不丢消息。
有水友问我说,总感觉微信不丢消息,它是怎么做到的?
之前做过几十年 IM 架构,今天和大家聊聊消息的可靠投递。
IM 系统中,报文分几种类型?
答,三种:
1. 请求报文(Request)
2. 应答报文(Acknowledge)
3. 通知报文(Notify)

R:客户端主动发送给服务器的报文;
A:服务器被动应答客户端的报文,一个 A 对应一个 R;
N:服务器主动发送给客户端的报文;
路人架构师如何设计消息投递流程?
一个没做过 IM 系统的路人架构师,他可能会这么设计消息投递流程:用户 A 给用户 B 发送”你好”:

1. client-A 向 im-server 发送 msg:R;
2. im-server 回复 client-A 一个 msg:A;
3. 如果此时 client-B 在线,则 im-server 向 client-B 发送 msg:N;
如果 client-B 不在线,im-server 存储离线消息。
上述消息投递流程存在什么问题?
流程图中会发现,发送方 client-A 收到 msg:A 后,只能说明 im-server 成功接收到了消息,并不能说明 client-B 接收到了消息。
在若干场景下,可能出现 msg:N 包丢失,例如:
1. 服务器崩溃,msg:N 包未发出;
2. 网络抖动,msg:N 包被网络设备丢弃;
3. client-B 崩溃,msg:N 包未接收;
结论是悲观的:接收方 client-B 是否有收到 msg:N,发送方 client-A 完全不可控。
那怎么办呢?
应用层的消息可靠投递,必须通过应用层的超时、重传、确认来保证。
首先,加入应用层的确认机制。
要想让发送方 client-A 确保接收方 client-B 收到了消息,必须让接收方 client-B 回复 client-A 一个消息的确认。这个应用层的确认流程,与消息的发送流程类似:

4. client-B 向 im-server 发送 ack:R;
5. im-server 回复 client-B 一个 ack:A;
6. im-server 向 client-A 发送 ack:N;
至此,发送”你好”的 client-A,在收到了 ack:N 报文后,才能确认 client-B 真正接收到了”你好”。
你会发现,一条”你好”的发送,分别包含上下两个半场,即 msg 的 R/A/N 三个报文,ack 的 R/A/N 三个报文,这是 IM 系统中消息投递的核心。
还可能存在什么问题?
复杂的网络环境下,msg:N,ack:N 这两个报文都可能丢失(服务器奔溃、网络抖动、客户端奔溃),此时 client-A 都收不到期待的 ack:N 报文,即 client-A 不能确认 client-B 是否收到”你好”,但这两个报文的丢失对应的业务影响又大有不同:
1. msg:N 包丢失,业务结果是 client-B 没有收到消息;
2. ack:N 包丢失,业务结果是 client-B 收到了消息,只是 client-A 不知道而已;
结论仍然是悲观的:client-A 无法知晓具体是哪种情况。
那怎么办呢?
接下来,要加入超时与重传
client-A 发出了 msg:R,收到了 msg:A 之后,在一个期待的时间内,如果没有收到 ack:N,client-A 会尝试将 msg:R 重发。
可能 client-A 同时发出了很多消息,故 client-A 需要在本地维护一个等待 ack 队列,并配合 timer 超时机制,来记录哪些消息没有收到 ack:N,以定时重发。

一旦收到了 ack:N,说明 client-B 收到了”你好”消息,对应的消息将从”等待 ack 队列”中移除。
消息的重传会引入什么新的问题?
超时与重传机制可能导致 client-B 收到重复的消息。
那怎么办呢?
最后,client-B 要引入消息的去重机制。
发送方 client-A 生成一个消息去重的 msgid,保存在”等待 ack 队列”里,同一条消息使用相同的 msgid 来重传,供 client-B 去重,而不影响用户体验。
也就是说,系统层面,client-B 其实收到了很多消息,而产品体验层面,用户并不知道。系统,就是在背后默默保证你体验的那个人!
总结
1. IM系统通过超时、重传、确认、去重的机制来保证消息的可靠投递;
2. 一个”你好”的发送,包含上半场 msg:R/A/N 与下半场 ack:R/A/N 的 6 个报文;
3. 等待 ACK 队列,是超时重传的关键,重传的消息 msgid 相同;
4. 无法做到系统层面的不丢不重,只能做到业务层面的不丢不重;
检验一下
在线消息的可靠投递解决了。那没打开微信的时候,好友发来的消息会不会丢?——第 2 篇,离线消息的存储与 ACK。
知其然,知其所以然。思路比结论更重要。