即时通讯 2022-07-21

2022-07-21  本文已影响0人  9_SooHyun

即时通讯首先需要解决两个最基本的问题:

如何收发消息:

发送消息:
A发送到【服务器】,【服务器】存储消息并发送通知到B
其中,通知B靠的是,B和【服务器】间保持的一个长链接;或者靠操作系统的消息Push系统,如苹果自身的apns通道

接收消息:
B收到通知后,主动上【服务器】拉取消息

如何保障消息不丢:

保障消息不丢,根本思路是收发双方依赖某种机制沟通对齐——收发对齐机制

最朴素的思想是,发送方发一条,接收方接收并确认收到,发送方收到确认后,继续发送下一条,loop...
这是最简单的ack机制,服务器传送一条消息,客户端回一个ack包(非序号,仅仅是“说一声收到”),但是这样的话:

因此,需要更优秀的收发对齐机制

有一个很典型的作业可以抄:tcp协议。tcp协议依赖【序列号】就实现了消息有序重排和ack机制

再透视一下tcp协议的机制,这里关注的是数据传送(data transfer)阶段:
通常在每个TCP报文段中都有一对【序号和确认号】。TCP报文发送者称自己的字节流的编号为序号(sequence number),称接收到对方的字节流编号为确认号

  • 接收方通过序号重排字节流,
  • 发送方通过对端发来的确认号来判断【对端是否正常接收已发送的字节流,并选择接下来需要发送的数据范围
tcp data transfer

从tcp的机制我们可以看到,收发双方利用序号的【有序性】和【可比较性】,就可以实现消息的可靠传输。有序性保证了消息可重组,而可比较性给收发双方对齐数据提供了依据

因此,我们可以在服务端和客户端都各自维护一个sequence number
1:根据服务器和手机端之间sequence的差异,可以很轻松的实现增量下发手机端未收取下去的消息
2:对于在弱网络环境差的情况,丢包情况发生概率是比较高的,此时经常会出现服务器的回包不能到达手机端的现象。由于手机端只会在确切的收取到消息后才会更新本地的sequence(即作为一个事务操作),所以即使服务器的回包丢了,手机端等待超时后重新拿旧的sequence上服务器收取消息,同样是可以正确的收取未下发的消息。
3:由于手机端存储的sequence是确认收到消息的最大sequence,所以对于手机端每次到服务器来收取消息也可以认为是对上一次收取消息的确认。一个帐号在多个手机端轮流登录的情况下,只要服务器存储手机端已确认的sequence,那就可以简单的实现已确认下发的消息不会重复下发,不同手机端之间轮流登录不会收到其他手机端已经收取到的消息

如果我们把若干次消息收取合在一起看,它就是一个长期的、慢动作式的tcp模式


进阶:

业务问题-群消息过载
技术问题-当前通信架构下,需要按顺序拉取消息,中间夹带的大量用户不关心的消息无法“忽略”
设想方案-接收消息但不提醒。点进去才拉取消息(lazy模式)

客户端属于前台在线时,通过长链接【实时被动】接收服务端推送的摘要;
客户端处于后台状态时,不做任何操作;
后台转前台时,【主动】拉取一次服务端的摘要

点击进群,才去拉消息本身

那为什么这里推送的是摘要而不是消息本身呢?

因为设置成按需拉取的消息,本身就很大几率不会被用户在意,推送消息本身和消息摘要,对用户而言基本无差别。但是对于服务端而言,可以节省大量流量
消息摘要-极致节省带宽资源,本质上也是个lazy模式:

通过消息模版标准化消息摘要,推送时只推送消息摘要,而在用户点击进入聊天时才会去拉取消息本身

每个群需要维护一个时间有序的存储,所以最简单最直接的存储方案应该是完全读扩散:即同一群只存一份消息序列,所有群成员共享同一份消息
但兼容不了旧版客户端的sync协议(所有消息必须要在同一个条时间线上),因此需要考虑写扩散。但“按序拉取”在消息确认下发2小时后即可删除,而按需拉取消息需要保存3天,会产生3天的存储堆积,而前者只会产生2小时的堆积

于是读写扩散结合模式:
一个群只存储一份消息本体;然后只对消息索引做写扩散,分别插入到群成员的时间线上,索引是轻量级的

所以为什么群大小会有限制?
因为当前的存储模式还是【索引写扩散】模式:消息本体落盘一份,消息索引扩散n份。如果群很大,n很大,存储压力也会很大。如果一个群只存储一份消息本体,然后读扩散,那群大小的限制基本就解除了

上一篇下一篇

猜你喜欢

热点阅读