单体架构 IM 系统核心业务功能实现

在上一篇技术短文(单体架构的 IM 系统设计)中,我们讨论了在 "用户规模小、开发人员少、开发时间短" 的业务背景下,采取 "怎么简单怎么做,怎么快怎么来" 的研发策略,于是设计了 单体架构的IM系统,并分析了 "通讯协议、编程语言和数据库" 的技术选型。我们快速复习一下单体架构的 IM 系统,见下图。

单体架构 IM 系统的每个组件描述如下:

  1. 客户端是一个业务组件,嵌入在游戏 APP 中运行;

  2. 客户端通过 http 这种非常简单的短连接的协议方式访问后端的 server;

  3. 一个 server 程序实现了后端所有的业务逻辑,不过是多个 server 节点集群化部署的(这就是典型的单体架构);

  4. server 节点直接访问数据库和缓存;在数据库中分别创建消息表、离线消息表、联系人表和用户表,消息表用来存储用户的历史记录消息,离线消息表用来存储用户不在线时收到的消息; 缓存用来记录用户是否在线的状态。

在该单体架构中,前端与后端的通讯协议是 http,也就是说所有的业务流程动作都是由前端触发的,server 端只是被动响应即可;从这一点出发也足以看出,为了降低 server 逻辑的复杂性,选择 http 通讯协议的必要性。基于该架构,我们讨论一下 IM 核心业务逻辑的实现,包括:用户状态维护、点对点消息收发、云消息。

一、 用户状态维护

用户状态是什么状态呢?就是用户的 "在线" 和 "离线" 状态。很多同学可能会有疑惑,不是基于 "长连接协议" 的客户端才会有 "在线" 和 "离线" 这一回事吗? http 是无状态化的短连接,难道也有 "在线" 一说? 是的,客户端在线与否,其实与通讯协议并不是强相关的,协议仅仅是传递数据的方式而已,甚至我们用 http 比用 tcp 更能精准表达出用户的状态。

通过 http 协议表达用户的在线状态,这一块技术实现应该非常成熟了;大家不妨思考一下,当我们登录163邮箱,把浏览器关闭,10分钟后再次访问 163 邮箱,是不需要重新登录的;这就是 http 实现用户在线状态的关键。

单体架构 IM 系统实现用户状态维护的核心逻辑,见下图。

  • 登录
    • 客户端向 server 端发送 http 登录请求;

    • 因为客户端是嵌入在游戏 APP 中运行的,游戏会有登录逻辑,所以 server 只需要调用游戏侧的登录服务进行鉴权校验即可;

    • 登录成功后,server 向 redis 中写入 <uid, {type, cmd, time}> 这样一个 kv 的 session 数据,并设置该 session 的有效期 10秒; type 描述客户端的类型,cmd 描述客户端请求server的接口,time描述写记录的时间。

后续,客户端访问 server 端其他所有接口时,server 读 redis ,如果对应的 session 不存在,说明用户未登录或登录已失效,需要重定向引导客户端重新登录。

  • 心跳

    • 客户端向 server 端周期性(2秒)发送 http 心跳请求;

    • server 延长 redis 中对应 session 的有效期,并修改 session 的 cmd 和 time 属性。

  • 登出

    • 客户端向 server 端发送 http 登出请求;

    • server 直接删除 redis 中对应的 session 数据。

在该单体架构的 IM 系统中,对用户状态的维护,就是对 redis 缓存中 session 数据的维护; 在客户端访问 server 其他接口时,server 也会对 session 的有效期和 cmd、time 属性进行修改。

二、 点对点消息收发

消息收发是 IM 系统最核心的功能;基于 http 协议的单体架构 IM 系统的 "点对点消息收发" 逻辑见下图。

  • 消息收发
    • client1 向 server 端发送 http 消息请求;

    • server 端向数据库中分别写入 "离线表" 和 "云消息表";(离线表和云消息表在同一个数据库中,通过数据库保证其事务性)

    • client2 向 server 端周期性(2秒)发送 http 拉取消息请求;(拉取消息复用心跳请求)

    • server 端从数据库 "离线表" 中读取 client2 相关记录后返回;

    • client2 再次发送拉取消息请求时,携带上次已经成功拉取的消息msgid,server 端删除 "离线表" 中相关记录。

整个消息的收发逻辑并不复杂,【发消息】和【收消息】动作完全由客户端触发,server 端被动响应即可;我们把这样的消息收发模型叫做 【信箱模型】。很明显,信箱模型的实现非常简单,缺点是消息的及时性不高,取决于客户端的心跳动作。

在整个消息的收发逻辑中,有两个细节点需要注意:"离线表" 通常在消息接收方离线时存储其消息,而在该实现逻辑中, server 端不管 client2 是否在线都会直接将消息持久化在 "离线表" 中, 这样处理的原因在于防止 clien2 没有及时拉取消息而造成消息丢失,提高了消息的可靠性; 再一个,当 client2 从 "离线表" 中拉取消息时,不能立刻将其删除,必须在下次拉取时进行删除,这也是消息可靠性的体现。

三、 云消息

所谓 "云消息",指存储在云端的消息,这样的消息可以被反复拉取,用来查看历史记录和实现消息漫游;在上述的消息收发逻辑中,每次客户端发消息时,server 端都会在 "云消息表" 中保存一条记录,所以客户端随时随地都能实现对云消息的读取。见下图。

  • 云消息
    • client 向 server 端发送 http 拉取历史消息请求;

    • server 端从云消息表中读取相关记录返回。

最后,总结文中关键:

1、"信箱模型" 由客户端主动向服务端发送请求,服务端被动响应即可;该模型实现简单,但作为 IM 系统来说,消息的实时性不高;

2、 基于 "信箱模型" 的单体架构 IM 系统,用户的在线状态通过在 redis 中保存有有效期的 session 来实现,并通过客户端的周期心跳实现 session 的持续有效;

3、 基于 "信箱模型" 的单体架构 IM 系统,发消息的逻辑是发送方向 "离线表" 和 "云消息表" 写入消息记录,收消息的逻辑是接收方从 "离线表" 中读取消息记录; 云消息表用来实现消息的历史记录读取和消息漫游。

在该单体架构的 IM 系统中, server 端完全是无状态化的,在 server 节点负载较高时,可以通过增加 server 节点轻松实现集群扩容,应对不断增长的同时在线用户数。

大家思考一下:在该架构中,消息的实时性不高,应该如何优化呢?优化时需要解决什么问题呢?

相关推荐
ktkiko1118 天前
前后端本地启动
java·项目开发·im系统
棕生2 个月前
分层架构 IM 系统之架构演进
im系统·分层架构·业务分离·mq解耦
棕生3 个月前
单体架构 IM 系统之 Server 节点状态化分析
单体架构·im系统·无状态化·长轮询消息收发逻辑·网络编程模型
棕生3 个月前
单体架构 IM 系统之长轮询方案设计
单体架构·im系统·信箱模型·http 长轮询·定时器方案·时间轮方案
棕生3 个月前
单体架构的 IM 系统设计
技术选型·单体架构·im系统
冰 河1 年前
自己手写了一套高性能分布式IM即时通讯系统,出去面试嘎嘎聊,都把面试官整不会了!
分布式·微服务·面试·程序员·im系统
忆梦九洲1 年前
微服务概述之单体架构
微服务·云原生·架构·单体架构·mvc模型