微信为什么不丢消息:超时、重传、确认、去重(1/31)

超时、重传、确认、去重——一条「你好」的六个报文

即时通讯架构实践第 1 课封面:即时通讯01:微信为什么不丢消息:超时、重传、确认、去重

超时、重传、确认、去重——一条「你好」的六个报文

《即时通讯架构核心技术》第 1 篇:微信为什么不丢消息。

有水友问我说,总感觉微信不丢消息,它是怎么做到的?

之前做过几十年 IM 架构,今天和大家聊聊消息的可靠投递。

IM 系统中,报文分几种类型?

答,三种:

1. 请求报文(Request)

2. 应答报文(Acknowledge)

3. 通知报文(Notify)

R/A/N 三种报文

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 队列与超时重传

一旦收到了 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. 无法做到系统层面的不丢不重,只能做到业务层面的不丢不重;

检验一下

检索练习 · 三种报文
IM 系统中 R/A/N 三种报文,分别是?

检索练习 · msg:A 的含义
发送方 client-A 收到了 msg:A,能说明什么?

检索练习 · 超时重传的关键
超时重传机制的关键数据结构是什么?

检索练习 · 去重
重传导致 client-B 收到重复消息,靠什么机制保证用户体验?

在线消息的可靠投递解决了。那没打开微信的时候,好友发来的消息会不会丢?——第 2 篇,离线消息的存储与 ACK。

知其然,知其所以然。思路比结论更重要。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

京ICP备2025137728号-3