即时消息:消息收发架构

IM可以看作是一门可以融入到各种业务系统中,为业务系统提供“实时交互”能力的技术模块。比如:

  • 某App想要增加一个互动模块,支持用户点对点的实时聊天功能。那么,我们就可以相应的通过一些IM SDK的方式,快速的把即时消息的技术引入到已有的业务系统中
  • 同样,一个传统的视频网站如果想要自己的视频增加弹幕公民也可以通过引入即时消息技术

所以,从某种程序上来看,随着移动网络的快速发展和资费的快速下降,即时消息技术也越来越多的被广泛应用到各种业务系统中,用于提升用户实时互动的能力。

那么,接下来,我们就一起从即时消息更细化的实现角度来看一看,给一个已有系统增加即时消息功能,大致上都有哪些具体工作。
在这里插入图片描述

如果为原有的业务系统增加实时消息模块,在不需要重建账户体系的前提下,整体上大概包括:

  • 指定好消息内容和未读数的存储
  • 建立比原业务系统更加高效实时的消息收发通道
  • 依托第三方辅助通告来提升消息到达率

下面我们分别来看一下各部分大体需要做的工作都包括哪些。

消息存储

即使消息系统中,消息作为互动的载体,是必不可少的要素之一。

  • 一般来说,大部分即时消息系统为了便于查看历史消息或者用于暂存离线消息,都需要对消息进行服务端存储。
  • 因此,我们先看一看,这些互动过程产生的消息在服务端应该怎么存储或者暂存

消息索引和消息内容

下面以点对点消息的存储为例分析:

  • 点对点消息的参与方有两个:消息发送方和消息接收方。收发双方的历史消息都是相互独立的。相互独立的意思是:假设发送方删除了某一条消息,接收方仍然可以获取到这条消息
  • 所以,从库表的设计上分析,这里需要索引表中收发双方各自有一条自己的索引记录:一条是消息发送方的发件箱索引,另一条是消息接收方的收件方索引
  • 由于收发双方看到的消息内容是一致的,因此还需要一个独立的消息内容表
  • 消息内容表用于存储消息维度的一些基本信息。比如消息ID、消息内容、消息类型、消息产生时间等。收发双方的两个索引表通过同一个消息ID与这个内容表进行关联

这里假设张三给李四发送一条消息,消息存储在 MySQL,或者类似的关系型数据库中,那么上面涉及的两张表大致如下:

(1)内容表
在这里插入图片描述
(2)索引表
在这里插入图片描述
比如张三给李四发了一条“你好”的消息,那么这个动作会向内容表存储一条消息。这条消息内容是这样的:ID 为 1001,消息内容是“你好”,消息类型是文本消息,还有当时消息创建的时间。

并且,它同时会往索引表里存储两条记录。

一条是张三的索引:内容有会话对方的 UID(李四的 UID),是发件箱的索引(也就是 0),同时记录这条消息的内容表里的消息 ID 为 1001。

另一条是李四的索引:内容有会话对方的 UID(张三的 UID),是收件箱的索引(也就是 1),同样也同时记录这条消息的内容表里的消息 ID 为 1001。

联系人列表

有了消息和索引之后,一般IM系统还需要一个最近联系人列表,来让互动双方快速查找需要聊天的对象,联系人列表一般还会携带两人最近一条聊天消息用于展示。

这里需要理解的是,和消息索引表的存储逻辑相比,联系人列表在存储上有以下区别:

  • 联系人列表只更新存储收发双方的最新一条信息,不存储两人所有的历史消息
  • 消息索引表的使用场景一般用于查询收发双方的历史聊天记录,是聊天会话维度;而联系人表的使用场景用于查询某一个人最近的所有联系人,是用户全局维度。

在库表的设计上,联系人列表的存储实际和消息索引表类似,只不过消息索引表在接收到消息时,大部分情况都是插入操作,而联系人列表很多时候是更新操作。

(1)最近联系人列表
在这里插入图片描述
还是刚才那个例子,张三给李四发完消息后,除了在内容表和索引表插入记录,还会更新各自的最近联系人表,这里需要分别更新张三的最近联系人表和李四的最近联系人表。

比如更新张三的最近联系人表,如果和李四之前没有聊天记录,那么新插入一条联系人记录。联系人的对方 UID 为李四的 UID,和这个联系人最新的一条消息 ID 是 1001。

如果张三和李四之前已经有过聊天记录,那么只需要更新张三和李四的最新的一条聊天消息 ID 为 1001,同样的办法再更新一次李四的联系人列表。

以上就是消息存储部分最重要的三个表,消息内容表、消息索引表、联系人列表。它们大致的存储结构,我们就设计好了。

消息收发通道

设计好消息的存储结构后,接下来,我们需要考虑的是:

  • 如何将消息发出去,以及怎么把消息投递给接收方。
  • 这里逻辑上涉及了两条通道:一条是消息发送通道,一条是消息接收通道。
  • 发送方通过发送通道将消息从本地发送到IM服务端,IM服务端通过接收通道包消息投递给接收方

消息发送通道

发送通道的实现上有很多方式,比如:

  • IM服务端提供一个HTTP协议的API接口,客户端需要发送消息时,调用这个接口把消息发送给IM服务端
  • 客户端和IM服务端维护一个TCP长连接,客户端有消息发送时,会以私有协议来封装这条要发送的消息,然后通过这个TCP长连接把消息发给IM服务端

在这里插入图片描述

所以,发送通道的实现相对比较简单,终点止于:IM服务端提供消息发送的API,发送方可以通过任意方式调用这个API,把消息发出去即可

消息接收通道

对于我们最常见的非P2P模式的IM系统来说,由于有一条消息要投递给某个接收方这个事件,接收方并没有办法能实时知道,只有IM服务端收到发送方发出的消息能实时感知到,因此消息投递这个动作一般是由IM服务端触发的(这里,我们不去讨论由接收方通过轮询获取消息的模式)。

目前业界在消息接收通道的实现上较多采用的方式是下面这样的。
在这里插入图片描述

  • IM服务端的网关服务和消息接收方设备之间维护一条TCP长连接(或者websocket长连接),借助TCP的全双工能力,也就是能够同时接收和发送数据的能力。当有消息需要投递时,通过这条长连接实时把消息从IM服务端推送给接收方
  • 对于接收方不在线(比如网络不通、App没打开等)的情况,还可以通过第三方手机操作系统级别的辅助通道,把这条消息通过手机通知栏的方式投递下去
  • 这里简单解释一下,常见的第三方操作系统级别的辅助通道。比如苹果手机的 APNs(Apple Push Notification Service)通道、Android 手机的 GCM 通道,还有各种具体手机厂商(如小米、华为等)提供的厂商通道。
  • 这些通道由于是随机厂商来维护的,只要手机网络可通,因此可以在我们的App没有打开的情况下,也能把消息实时推送下去
  • 不过,这些第三方操作系统级别的辅助通道这存在一些问题,因此大部分情况下也只是作为一个辅助手段来提升消息的实时触发能力
  • 因此,对于消息接收通道,重点在于需要在IM服务端和接收方之间,维护一个可靠的长连接。什么叫做可靠的长连接呢,这里的可靠有如下两种情况:
    • IM服务端和接收方能够较为精确地感知这个长连接的可用性,当由于网络原因连接被中断时,能够快速的感知并进行重连等恢复性操作
    • 通过这个长连接投递的消息不能出现丢失的情况,否则会比较影响用户体验

当然,两条通道在实际的实现上,可以是各自独立存在的,也可以合并在一条通道中。

消息未读数

现在我们有了消息的收发通道和消息的存储,用户通过发送通道把消息发到IM服务端,IM服务端对消息内容、收发双方的消息索引进行存储,同时更新双方的最近联系人的相关记录,然后IM服务端通过和消息接收方维护的接收通道,将消息实时推送给消息接收方。

如果消息接收方当时不在线,还可以通过第三方操作系统级别的辅助通道,来实时的将消息通过手机通知栏等方式推送给接收方

整体上来看,一条消息从发送、存储、接收的生命之旅基本上就比较完整了,但对于即时消息的场景来说,还有一个比较重要的功能,会对双方在互动积极性和互动频率上产生比较大的影响,这就是消息的未读性提醒

用过 QQ、微信的用户应该都有一个比较明显的感知,很多时候为了避免通知栏骚扰,会限制掉 App 在通知栏提醒权限,或者并没有注意到通知栏的提醒,这些情况都可能会让我们无法及时感知到“有人给我发了新的消息”这个事情。

那么作为一个重要的补救措施就是消息的未读提醒了。就我个人而言,很多时候是看到了 QQ 或者微信 App 的角标,上面显示的多少条未读消息,才打开 App,然后通过 App 里面具体某个联系人后面显示,和当前用户有多少条未读这个数字,来决定打开哪个联系人的聊天页进行查看。

上面通过未读消息提醒来查看消息的环节中涉及两个概念:一个是我有多少天未读消息,另一个是我和某个联系人有多少条未读消息

因此,我们在消息未读数的实现上,一般需要针对用户维度有一个总未读数的计数,针对某一个具体用户需要有一个会话维度的会话未读的计数

那么,这两个消息未读数变更的场景是下面这样的:

  • 张三给李四发送一条消息,IM 服务端接收到这条消息后,给李四的总未读数增加 1,给李四和张三的会话未读也增加 1;
  • 李四看到有一条未读消息后,打开 App,查看和张三的聊天页,这时会执行未读变更,将李四和张三的会话未读减 1,将李四的总未读也减 1。

这个具体的未读数存储可以是在IM服务端(比如QQ、微博),也可以是在接收方的本地端上存储(微信),一般来说,需要支持“消息的多终端漫游”的应用需要在IM服务端进行未读存储,不需要支持“消息的多终端漫游”可以选择本地存储即可

小结

上面我们从一条消息“产生、存储、接收”的整个生命周期出发,较为系统地从实现的角度上对消息系统的几个关键部分进行了讲述。可以简单地总结为下面几点。

  • 消息的发送方通过发送通道来把消息提交到 IM 服务端。
  • IM 服务端接收到发送的消息后,会进行消息的存储以便于后续历史消息的查看,消息的存储从实现上可以分为:消息内容存储、消息索引存储、最近联系人列表存储。
  • IM 服务端接收到发送的消息后,还会针对接收方进行未读数的变更,以提醒用户查看未读的消息,消息未读数的实现上一般分为:用户维度的总未读和会话维度的会话未读。
  • IM 服务端进行完消息存储和未读变更后,会通过接收通道把消息推送给接收方,接收通道一般是通过 IM 服务端和消息接收方之间维护的长连接来实现,还会使用第三方操作系统级别的辅助通道,来提升“自建的长连接不可用“时,实时触达的能力。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值