写点什么

一套亿级用户的 IM 架构技术干货 (下篇):可靠性、有序性、弱网优化等

用户头像
JackJiang
关注
发布于: 2021 年 03 月 22 日
一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

本文内容和编写思路是基于邓昀泽的“大规模并发 IM 服务架构设计”、“IM 的弱网场景优化”两文的提纲进行的,感谢邓昀泽的无私分享。

1、引言

接上篇《一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等》,本文主要聚焦这套亿级用户的 IM 架构的一些比较细节但很重要的热门问题上,比如:消息可靠性、消息有序性、数据安全性、移动端弱网问题等。

以上这些热门 IM 问题每个话题其实都可以单独成文,但限于文章篇幅,本文不会逐个问题详细深入地探讨,主要以抛砖引玉的方式引导阅读者理解问题的关键,并针对问题提供专项研究文章链接,方便有选择性的深入学习。希望本文能给你的 IM 开发带来一些益处。

2、系列文章

为了更好以进行内容呈现,本文拆分两了上下两篇。

本文是 2 篇文章中的第 2 篇:

一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等》(本文)

本篇主要聚焦这套亿级用户的 IM 架构的一些比较细节但很重要的热门问题上。

3、消息可靠性问题

消息的可靠性是 IM 系统的典型技术指标,对于用户来说,消息能不能被可靠送达(不丢消息),是使用这套 IM 的信任前提。

换句话说,如果这套 IM 系统不能保证不丢消息,那相当于发送的每一条消息都有被丢失的概率,对于用户而言,一定会不会“放心”地使用它,即“不信任”这套 IM。

从产品经理的角度来说,有这样的技术障碍存在,再怎么费力的推广,最终用户都会很快流失。所以一套 IM 如果不能保证消息的可靠性,那问题是很严重的。

PS:如果你对 IM 消息可靠性的问题还没有一个直观的映象的话,通过《零基础IM开发入门(三):什么是IM系统的可靠性?》这篇文章可以通俗易懂的理解它。

如上图所示,消息可靠性主要依赖 2 个逻辑来保障:

  • 1)上行消息可靠性;

  • 2)下行消息可靠性。

1)针对上行消息的可靠性,可以这样的思路来处理:

用户发送一个消息(假设协议叫 PIMSendReq),用户要给这个消息设定一个本地 ID,然后等待服务器操作完成给发送者一个 PIMSendAck(本地 ID 一致),告诉用户发送成功了。

如果等待一段时间,没收到这个 ACK,说明用户发送不成功,客户端 SDK 要做重试操作。

2)针对下行消息的可靠性,可以这样的思路来处理:

服务收到了用户 A 的消息,要把这个消息推送给 B、C、D 3 个人。假设 B 临时掉线了,那么在线推送很可能会失败。

因此确保下行可靠性的核心是:在做推送前要把这个推送请求缓存起来。

这个缓存由存储系统来保证,MsgWriter 要维护一个(离线消息列表),用户的一条消息,要同时写入 B、C、D 的离线消息列表,B、C、D 收到这个消息以后,要给存储系统一个 ACK,然后存储系统把消息 ID 从离线消息列表里拿掉。

针对消息的可靠性问题,具体的解决思路还可以从另一个维度来考虑:即实时消息的可靠性和离线消息的可靠性。

有兴趣可以深入读一读这两篇:

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

而对于离线消息的可靠性来说,单聊和群聊又有很大区别,有关群聊的离线消息可靠投递问题,可以深入读一读《IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》。

4、消息有序性问题

消息的有序性问题是分布式 IM 系统中的另一个技术“硬骨头”。

因为是分布式系统,客户端和服务器的时钟可能是不同步的。如果简单依赖某一方的时钟,就会出现大量的消息乱序。

比如只依赖客户端的时钟,A 比 B 时间晚 30 分钟。所有 A 给 B 发消息,然后 B 给 A 回复。

发送顺序是:

客户端 A:“XXX”

客户端 B:“YYY”

接收方的排序就会变成:

客户端 B:“YYY”

客户端 A:“XXX”

因为 A 的时间晚30分钟,所有 A 的消息都会排在后面。

如果只依赖服务器的时钟,也会出现类似的问题,因为 2 个服务器时间可能也不一致。虽然客户端 A 和客户端 B 时钟一致,但是 A 的消息由服务器 S1 处理,B 的消息由服务器 S2 处理,也会导致同样消息乱序。

为了解决这种问题,我的思路是通过可以做这样一系列的操作来实现。

1)服务器时间对齐:

这部分就是后端运维的锅了,由系统管理员来尽量保障,没有别的招儿。

2)客户端通过时间调校对齐服务器时间:

比如:客户端登录以后,拿客户端时间和服务器时间做差值计算,发送消息的时候考虑这部分差值。

在我的 im 架构里,这个能把时间对齐到 100ms 这个级,差值再小的话就很困难了,因为协议在客户端和服务器之间传递速度 RTT 也是不稳定的(网络传输存在不可控的延迟风险嘛)。

3)消息同时带上本地时间和服务器时间:

具体可以这样的处理:排序的时候,对于同一个人的消息,按照消息本地时间来排;对于不同人的消息,按照服务器时间来排,这是插值排序算法。

PS:关于消息有序性的问题,显然也不是上面这三两句话能讲的清楚,如果你想更通俗一理解它,可以读一读《零基础IM开发入门(四):什么是IM系统的消息时序一致性?》。

另外:从技术实践可行性的角度来说,《一个低成本确保IM消息时序的方法探讨》、《如何保证IM实时消息的“时序性”与“一致性”?》这两篇中的思路可以借鉴一下。

实际上,消息的排序问题,还可以从消息 ID 的角度去处理(也就是通过算法让消息 ID 产生顺序性,从而根据消息 ID 就能达到消息排序的目的)。

有关顺序的消息 ID 算法问题,这两篇非常值得借鉴:IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》、《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》,我就不废话了。

5、消息已读同步问题

消息的已读未读功能,如下图所示: 

上图就是钉钉中的已读未读消息。这在企业 IM 场景下非常有用(因为做领导的都喜欢,你懂的)。

已读未读功能,对于一对一的单聊消息来说,还比较好理解:就是多加一条对应的回执息(当用户阅读这条消息时发回)。

但对于群聊这说,这一条消息有多少人已读、多少人未读,想实现这个效果,那就真的有点麻烦了。对于群聊的已读未读功能实现逻辑,这里就不展开了,有兴趣可以读一下这篇《IM群聊消息的已读回执功能该怎么实现?》。

回归到本节的主题“已读同步”的问题,这显示难度又进一级,因为已读未读回执不只是针对“账号”,现在还要细分到“同一账号在不同端登陆”的情况,对于已读回执的同步逻辑来说,这就有点复杂化了。

在这里,根据我这边 IM 架构的实践经验,提供一些思路。

具体来说就是:用户可能有多个设备登录同一个账户(比如:Web PC 和移动端同时登陆),这种情况下的已读未读功能,就需要来实现已读同步,否则在设备 1 看过的消息,设备 2 看到依然是未读消息,从产品的角度来说,这就影响用户体验了。

对于我的 im 架构来说,已读同步主要依赖 2 个逻辑来保证:

  • 1)同步状态维护,为用户的每一个 Session,维护一个时间戳,保存最后的读消息时间;

  • 2)如果用户打开了某个 Session,且用户有多个设备在线,发送一条 PIMSyncRead 消息,通知其它设备。

6、数据安全问题

6.1 基础

IM 系统架构中的数据安全比一般系统要复杂一些,从通信的角度来说,它涉及到 socket 长连接通信的安全性和 http 短连接的两重安全性。而随着 IM 在移动端的流行,又要在安全性、性能、数据流量、用户体验这几个维度上做权衡,所以想要实现一套完善的 IM 安全架构,要面临的挑战是很多的。

IM 系统架构中,所谓的数据安全,主要是通信安全和内容安全。

6.2 通信安全

所谓的通信安全,这就要理解 IM 通信的服务组成。

目前来说,一个典型的 im 系统,主要由两种通信服务组成:

  • 1)socket 长连接服务:技术上也就是多数人耳熟能详的网络通信这一块,再细化一点也就是 tcp、udp 协议这一块;

  • 2)http 短连接服务:也就是最常用的 http rest 接口那些。

对于提升长连接的安全性思路,可以深入阅读《通俗易懂:一篇掌握即时通讯的消息传输安全原理》。另外,微信团队分享的《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》一文,也非常有参考意义。

如果是通信安全级别更高的场景,可以参考《即时通讯安全篇(二):探讨组合加密算法在IM中的应用》,文中关于组合加密算法的使用思路非常不错。

至于短连接安全性,大家就很熟悉了,开启 https 多数情况下就够用了。如果对于 https 不甚了解,可以从这几篇开始:《一文读懂Https的安全性原理、数字证书、单项认证、双项认证等》、《即时通讯安全篇(七):如果这样来理解HTTPS,一篇就够了》。

6.3 内容安全

这个可能不太好理解,上面既然实现了通信安全,那为什么还要纠结“内容安全”?

我们了解一下所谓的密码学三大作用:加密( Encryption)、认证(Authentication),鉴定(Identification) 。

详细来说就是:

加密:防止坏人获取你的数据。

认证:防止坏人修改了你的数据而你却并没有发现。

鉴权:防止坏人假冒你的身份。

在上节中,恶意攻击者如果在通信环节绕开或突破了“鉴权”、“认证”,那么依赖于“鉴权”、“认证”的“加密”,实际上也有可有被破解。

针对上述问题,那么我们需要对内容进行更加安全独立的加密处理,就这是所谓的“端到端加密”(E2E)。

比如,那个号称无法被破解的 IM——Telegram,实际上就是使用了端到端加密技术。

关于端到端加密,这里就不深入探讨,这里有两篇文章有兴趣地可以深入阅读:

移动端安全通信的利器——端到端加密(E2EE)技术详解

简述实时音视频聊天中端到端加密(E2EE)的工作原理

7、雪崩效应问题

在分布式的 IM 架构中,存在雪崩效应问题。

我们知道,分布式的 IM 架构中,为了高可用性,用户每次登陆都是根据负载均衡算法分配到不同的服务器。那么问题就来了。

举个例子:假设有 5 个机房,其中 A 机房故障,导致这个机房先前服务的用户都跑去 B 机房。B 机房不堪重负也崩溃了,A+B 的用户跑去机房 C,连锁反应会导致所有服务挂掉。

防止雪崩效应需要在服务器架构,客户端链接策略上有一些配合的解决方案。服务器需要有限流能力作为基础,主要是限制总服务用户数和短时间链接用户数。

在客户端层面,发现服务断开之后要有一个策略,防止大量用户同一时间去链接某个服务器。

通常有 2 种方案:

  • 1)退避:重连之间设置一个随机的间隔;

  • 2)LBS:跟服务器申请重连的新的服务器 IP,然后由 LBS 服务去降低短时间分配到同一个服务器的用户量。

这 2 种方案互不冲突,可以同时做。

8、弱网问题

8.1 弱网问题的原因

鉴于如今 IM 在移动端的流行,弱网是很常态的问题。电梯、火车上、开车、地铁等等场景,都会遇到明显的弱网问题。

那么为什么会出现弱网问题?

要回答这个问题,那就需要从无线通信的原理上去寻找答案。

因为无线通信的质量受制于很多方面的因素,比如:无线信号强弱变化快、信号干扰、通信基站分布不均、移动速度太快等等。要说清楚这个问题,那就真是三天三夜都讲不完。

有兴趣的读者,一定要仔细阅读下面这几篇文章,类似的跨学科科谱式文章并不多见:

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

弱网问题是移动端 APP 的必修课,下面这几篇总结也值得借鉴:

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

8.2 IM 针对弱网问题的处理

对于 IM 来说,弱网问题并不是很复杂,核心是做好消息的重发、排序以及接收端的重试。

为了解决好弱网引发的 IM 问题,通常可以通过以下手段改善:

  • 1)消息自动重发;

  • 2)离线消息接收;

  • 3)重发消息排序;

  • 4)离线指令处理。

下面将逐一展开讨论。

8.3 消息自动重发

坐地铁的时候,经常遇到列车开起来以后,网络断开,发送消息失败。

这时候产品有 2 种表现形式:

  • a、直接告诉用户发送失败;

  • b、保持发送状态,自动重试 3-5 次(3 分钟)以后告诉用户发送失败。

显然:自动重试失败以后再告诉用户发送失败体验要好很多。尤其是在网络闪断情况下,重试成功率很高,很可能用户根本感知不到有发送失败。

从技术上:客户端 IMSDK 要把每条消息的状态监控起来。发送消息不能简单的调用一下网络发送请求,而是要有一个状态机,管理几个状态:初始状态,发送中,发送失败,发送超时。对于失败和超时的状态,要启用重试机制。

这里还有一篇关于重试机制设计的讨论帖子,有兴趣可以看看:完全自已开发的IM该如何设计“失败重试”机制?》。

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》一文中关于消息超时与重传机制的实现思路,也可以参考一下。

8.4 离线消息接收

现代 IM 是没有“在线”这个状态的,不需要给用户这个信息。但是从技术的层面,用户掉线了还是要正确的去感知的。

感知方法有几条:

  • a、信令长连接状态:如果长时间没收到到服务器的心跳反馈,说明掉线了;

  • b、网络请求失败次数:如果多次网络请求失败,说明”可能“掉线了;

  • c、设备网络状态检测:直接检测网卡状态就好,一般 Android/iOS/Windows/Mac 都有相应系统 API。

正确检测到网络状态以后,发现网络从”断开到恢复“的切换,要去主动拉取离线阶段的消息,就可以做到弱网状态不丢消息(从服务器的离线消息列表拉取)。

上面文字中提到的网络状态的确定,涉及到 IM 里网络连接检查和保活机制问题,是 IM 里比较头疼的问题。

一不小心,又踩进了 IM 网络保活这个坑,我就不在这里展开,有兴趣一定要读读下面的文章:

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

8.5 重发消息排序

弱网逻辑的另一个坑是消息排序。

假如有 A、B、C  3 条消息,A、C 发送成功,B 发送的时候遇到了网络闪断,B 触发自动重试。

那么接收方的接收顺序应该是 A B C 还是 A C B 呢?我观察过不同的 IM 产品,处理逻辑各不相同,这个大家有兴趣可以去玩一下。

这个解决方法是要依赖上一篇服务架构里提到的差值排序,同一个人发出的消息,排序按消息附带的本地时间来排。不同人的消息,按照服务器时间排序。

具体我这边就不再得复,可以回头看看本篇中的第四节“4、消息有序性问题”。

8.6 离线指令处理

部分指令操作的时候,网络可能出现了问题,等网络恢复以后,要自动同步给服务器。

举一个例子,大家可以试试手机设置为飞行模式,然后在微信里删除一个联系人,看看能不能删除。然后重新打开网络,看看这个数据会不会同步到服务器。

类似的逻辑也适用于已读同步等场景,离线状态看过的信息,要正确的跟服务器同步。

8.7 小结一下

IM 的弱网处理,其实相对还是比较简单的,基本上自动重试+消息状态就可以解决绝大部分的问题了。

一些细节处理也并不复杂,主要原因是 IM 的消息量比较小,网络恢复后能快速的恢复操作。

视频会议在弱网下的逻辑,就要复杂的多了。尤其是高丢包的弱网环境下,要尽力去保证音视频的流畅性。

9、本文小结

《一套亿级用户的 IM 架构技术干货》这期文章的上下两篇就这么侃完了,上篇涉及到的 IM 架构问题倒还好,下篇一不小心又带出了 IM 里的各种热门问题“坑”,搞 IM 开发直是一言难尽。。。

建议 IM 开发的入门朋友们,如果想要系统地学习移动端 IM 开发的话,应该去读一读我整理的那篇 IM 开发“从入门到放弃”的文章(哈哈哈),就是这篇《新手入门一篇就够:从零开发移动端IM》。具体我就不再展开了,不然这篇幅又要刹不住车了。。。(本文同步发布于:http://www.52im.net/thread-3445-1-1.html

10、参考资料

[1] 大规模并发IM服务架构设计

[2] IM的弱网场景优化

[3] 零基础IM开发入门(三):什么是IM系统的可靠性?

[4] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[5] IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

[6] 即时通讯安全篇(二):探讨组合加密算法在IM中的应用

[7] 微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解


用户头像

JackJiang

关注

还未添加个人签名 2019.08.26 加入

开源IM框架MobileIMSDK、BeautyEye的作者。

评论

发布
暂无评论
一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等