You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 22 | 答疑解惑:不同即时消息场景下架构实现上的异同
你好,我是袁武林。
随着专栏最后一个进阶篇模块的更新后,咱们的即时消息专栏课程,到这里就要告一段落了。首先,感谢你在这段时间里对专栏的持续关注,也非常高兴看到你一直在积极地思考和学习。
在专栏的讨论区,同学们也都十分活跃,都在热情地留言和互动讨论,留下了很多比较典型和有意义的问题。
有一些问题可能限于篇幅的原因我没有详细展开那么今天我就摘录出比较有代表性的5个问题来做一下集中的整理和回复。也希望通过这种方式能够帮助你对这些知识点有更清晰的理解和认识。
## 第一问:消息的服务端存储和本地存储
在[第1讲“架构与特性一个完整的IM系统是怎样的”](https://time.geekbang.org/column/article/127872)中,有很多同学都问到了这个问题:**即时消息系统实现中,消息一定需要在服务端的存储服务里进行存储吗?**
首先呢,关于服务端存储的问题,我们需要更多地考虑存储成本和数据安全。
一方面,如果消息不在服务端存储,服务器只是作为消息的中转路由服务,那么,相应的消息存储成本就会低很多,在数据安全性方面也更好一些。
另外,这个问题也和产品定位有关。
比如,如果你的产品定位上不需要支持消息多终端同步(比如微信),那么像核心的消息等这些数据,就可以不在服务端进行存储。不过,用户的好友关系等数据信息还是需要存储在服务端的。
你还需要考虑的一点是,即使是不需要支持多终端消息同步的产品,大部分即时消息系统也是支持离线消息的,这种情况其实也需要在服务端对离线消息进行暂存,虽然可能只是暂存较短时间。
除此之外,消息是否需要在服务端存储,你还需要考虑国内监管机制是否允许的问题。如果监管有要求,那么我们所有的消息数据,都需要在服务端存储一定的时间供监管调看,只是这里存储的使用方不是普通用户,而是监管部门。
## 第二问:长连接消息推送的实现
在[第3讲“轮询与长连接如何解决消息的实时到达问题”](https://time.geekbang.org/column/article/128942)中,我留下了一个思考题:**TCP长连接的方式是怎么实现“当有消息需要发送给某个用户时能够准确找到这个用户对应的网络连接”**
对于这个问题,不少同学给出的答案都很棒,比如 @王棕生、@小可等几位同学。这个问题由于本身比较重要,提问的同学也不少,所以我这里也专门来回答一下。
首先,用户在长连建立后,需要再执行一个**“上线”操作**。
这个“上线”操作主要的工作就是:
* 将当前登录用户的信息提供到服务端,服务端长连接收到这个用户信息后,在服务端维护一个“用户” -> “连接”维度的映射,这个映射可以存到中央资源里或者网关机的本地内存中;
* 当有消息需要推送给这个用户时,负责消息下推的服务查询这个中央的“用户” -> “连接”维度的映射,来获取该用户的连接,通过这个连接将消息进行下推;
* 或者将消息下发给所有网关机,由网关机来查询本地维护的这个映射,是否有该用户的连接在本机,如果有,就通过当前网关机维护的这个连接来进行消息下推;
* 当用户断连下线的时候,再从这个中央的“用户” -> “连接”维度的映射或者网关机本地删除掉这个映射。
## 第三问应用层ACK的必要性
在课程的[第4讲“ACK机制如何保证消息的可靠投递”](https://time.geekbang.org/column/article/129751)中,我留下的思考题是:**有了TCP协议本身的ACK机制为什么还需要业务层的ACK机制**
这个问题大家讨论得也比较多有几位同学也都比较正确地讲出了这两种ACK的差异性比如@小伟、@恰同学少年、@阳仔、@王棕生等同学都回答得很棒。我这里也简单说一下这个问题的答案。
这是因为虽然TCP协议本身的ACK机制能够保证消息在正常情况下传输层数据收发的可靠性但在连接异常断开等场景下也可能存在数据丢失的风险。比如TCP的发送缓冲区和接收缓冲区里的数据都可能会丢失。
另外即使消息从TCP传输层成功给到了应用层也并不能保证应用层数据收发的可靠性因为应用层在接收到传输层的数据后也可能发生其他异常。
比如手机Crash了或者突然没电关机了又或者客户端在将消息写入本地数据库时发生异常失败了这些情况都可能会导致消息即使成功在TCP层被ACK了但在业务层上仍然会被丢失。
但是业务层的ACK机制是在应用层接收到数据并且成功执行完必要的存储等逻辑后再ACK给服务端所以能够更好地保障消息收发在业务层的真正可靠性。
## 第四问:离线消息下推的优化
在[第9讲“分布式一致性让你的消息支持多终端漫游”](https://time.geekbang.org/column/article/136020)中,有不少同学还问到:**在离线消息如果特别多的情况下,我们应该采取什么方式来下发这些消息呢?**
对于长时间没有登录或者消息接收频繁的用户来说,当这些用户下线一段时候后再上线,经常会面临大量的离线消息下推的问题。
一种比较好的优化方案是:对多条离线消息采取批量打包+压缩的方式来进行下推。
通过这种方式能够大幅降低传输的数据大小也能有效减少大量离线消息下推后的ACK包数量。
但是面对上万条的离线消息,只单纯地采用批量+压缩的方式还是不够的,这种情况下,我们还可以采用**“推拉结合”**的方式来进行优化。
用户上线后对于服务端离线消息的下推可以只推送用户最近N条消息而对于后续要推送的消息我们可以让用户通过向上翻页来触发自动拉取客户端再从服务端来获取当前聊天会话剩下的消息。
另外,在实际的离线消息场景里,我们还需要考虑到离线消息存储成本的问题。
绝大部分IM系统并不会一直缓存用户的离线消息而是一般会按照条数或者时间周期来保留用户最近N条离线消息或者最近多长时间内的离线消息。
## 第五问:群聊和直播互动消息下推的区别
在[第10讲“自动智能扩缩容直播互动场景中峰值流量的应对”](https://time.geekbang.org/column/article/137000)一课中,有同学对这个问题感到疑惑:**群聊场景和直播互动场景在消息推送时,实现上不一样的地方在哪?**
@淡蓝小黑同学也在留言中问到:
> 文中提到【通过这个优化,相当于是把直播消息的扇出从业务逻辑处理层推迟到网关层】和 您回复我的【业务层在把消息扇出给到网关机时,就已经具体到接收人了,所以不需要通知网关机来进行变更。】这两条不冲突吗?(我看唯一的区别是文中说的是直播间场景,您回复我的是群聊场景,还是说群聊和直播间是用不同的模式扇出消息的?)
> 其实我想问的是,用户加入直播间,网关机本地维护【本机某个直播间有哪些用户】这个数据的话,当用户离开直播间,加入另一个直播间时,业务处理层还要通知网关层更新本地维护的那个数据,有可能会出现数据不一致的情况,导致用户加入新直播间了,但由于网关层数据没有更新,用户收到不到新直播间的消息。
这是个好问题!对此,我们可以先想一想群聊和直播的使用场景。
对于直播场景来说,房间和房间之间是隔离状态。也就是说,每次用户进入到一个新房间,消息推送层面只和用户当前进入的房间有关联,与其他房间就没有关系了。
所以,每次用户进入新房间时,都需要通过“加入房间”或者“切换房间”等这些携带用户信息和房间信息的信令,来告诉网关机自己当前连接的是哪个房间。
正是通过这些“加入房间”和“切换房间”的信令,网关机才能够在服务端标记这个“用户 -> 房间 -> 连接”的映射关系。当这些网关机收到某一条直播间的消息时,就能够根据这个映射关系,找到对应的房间用户的连接。
换言之,对于直播间消息来说,当我们的消息从业务层给到网关机时,服务端只需要按房间维度来下发消息就可以了。所以**在直播互动场景中,消息的扇出是可以推迟到网关机层的。**
但是对于群聊来说,群和群之间并不是隔离状态。
对于任何群的消息,服务端都需要通过一条长连接通道来进行消息的下推,并没有一个所谓的“进入群聊”来切换多个群的行为。
所以在App打开建立好长连后客户端发出的“上线”这个操作只是会上报一个当前用户信息不需要、也没有办法再告知自己当前进入了哪个群因而在服务端网关机也只会建立一个“用户” -> “连接”的映射。
因此,**在群聊场景中当某一个群有一条消息发出时我们需要在业务层将这条消息从群维度扇出成UID维度再下发给网关机**。
那么,我们为什么不能在网关机本身查询了群成员的相关信息后,再扇出消息呢?
这个操作在实现上当然也是可以的。不过咱们前面提到,为了解耦网关机和业务层逻辑,我们尽量不在网关机做业务维度的逻辑。
因此这里建议对于群聊消息来说,我们还是在业务层扇出后,再提交到网关机进行处理。
## 小结
正如我在开篇词里讲到,即时消息技术实际上是众多前后端技术的紧密结合,涉及到的知识面也是非常广泛的,而且随着业务形态的不断变化和升级,相应的架构设计和技术侧重点上也各有异同。
因此,形而上学的方式往往很难真正做到有的放矢。但我相信,只要我们从问题和业务形态的本质出发,经过不断地思考和实践,就能够在掌握现有即时消息知识的基础上,找到最适合自身业务的架构和方案。
好了,今天的答疑就到这里,如果你还有问题,可以继续给我留言,我们一起讨论。感谢你的收听,我们下期再见。