文章

消息发送由简到繁到放弃

最简单的架构

纯数据库实现,维护一个 inbox 表,每条消息新建一条记录。

缺点也比较明显,接收方必须主动拉取消息列表,只能通过轮询实现,不能实现发送方发送后马上收到消息。

稍微复杂一点的架构

上一步一个重要的问题,发送方发送了一条消息后,如果接收方是在线的状态,还是需要通过轮询来拿到这条新消息,延迟也就高了。

改进方式也很简单,服务端跟客户端维持一个长连接,但接收方在线时,可以通过长连接直接推送新消息给接收方;若不在线,还是可以上线后通过拉取列表的方式同步消息。

但是这还有一些问题没能解决:

  • 如果消息发送的逻辑比较多,例如说要先校验两人间的好友关系、黑名单关系等,然后还要落库,推送等,发送方需要等所有逻辑执行完才能收到消息发送成功的回执(假设为了保证消息可靠性,发送端必须收到发送成功回执才算发送成功),期间发送方一直在转圈状态。
  • 消息吞吐量大时,对单节点性能要求过高,无法很好拓展

靠谱的架构

因此,这个地方引入一个消息队列就很有必要了。消息队列最大的作用就是能够使一些逻辑能够异步处理。

针对刚才的第一个问题,相应发送端的逻辑在校验通过后即可向发送端确认发送成功,然后将待发送的消息推入消息队列,供发送逻辑的消费者处理。如果发送逻辑失败了,还是可以通过给发送端推送一个发送失败信令,给发送失败的信息加上感叹号。

当吞吐量大需要扩展时,处理客户端长连接和发送端的两个服务可以根据各自需要横向拓展,通过消息队列解耦了两部分逻辑,只要消息队列配置合理,两边的扩容都可以做得十分优雅。

优化一下消息推送及时性

当前消息推送的逻辑如下

flowchart LR

Sender --> |client online| longConnection(长连接)
Sender --> |client offline| db(落库)

当客户端不在线时,要收到新消息,必须上线后通过信令拉取消息列表。

为了改进这一情况,可以接推送 SDK,加在发送逻辑里面,如果客户端不在线,可以推送通知,让用户主动打开客户端,接收新消息。

总结

一个 IM 的消息发送架构大致从简单到复杂有这么几个选择

  1. 最简单的,单数据库
  2. 复杂一点,数据库+长连接
  3. 稳妥方法,数据库+消息队列
License:  CC BY 4.0