概述

我实现了一个供游戏接入的IM系统,这是我为数不多,历时超过5年,持续开发的系统。大部分的坑都是自己埋的,总结一下经验和教训。

好的设计

session 抽象和 msg 表设计

session 抽象

对于IM系统,会话是最核心、顶层的概念。会话有如下属性:

  1. 参与者。
  2. 消息历史。
  3. 会话信息。

下图中红框部分即会话。
xxx.png

举例:

  1. 用户A to B的私聊。
  2. 世界,地区等群组。
  3. 用户A的系统通知。
  4. 用户A的客诉会话,可以在不同客服之间移交。

msg 表设计

消息表只需要一个索引:{ session_id: 1, msg_id: 1 },其中 session_id 是分片key。高qps的im业务,都可以用这个索引和分片key,不需要按时间切片,单分片可以承载百亿级消息,表设计可以无限横向拓展。
对于复杂的查询需求,如,按发送来源,发送目标,消息内容等,另外建表,按业务场景选择数据库和分片模型。

ensure 声明式接口

考虑im系统是供游戏接入的,数据必须和游戏保持一致,比如,如果用户A从工会a退出,并进入b工会,用户A所在的游戏工会群必须是 a -> null -> b,每次调用对应下表的一行:

唯一键群组id
user:A unique_key:guilda
user:A unique_key:guildnull
user:A unique_key:guildb

考虑接口调用失败的可能性,我们要求接入方在登录时同步全量信息。在一次线上故障中,IM系统的数据库崩溃了半小时,无需做数据修复。

user 抽象

im系统的用户来自外部,如游戏角色,游戏账号,客服。这些用户由外部系统分配角色id,通过前缀区分用户类型,通过声明式接口将用户同步至im系统。因为游戏角色 is a user,客服 is a user,所有user的接口逻辑都可以共享。

支持迁移的 actor 模型框架

迁移是不停服更新,水平伸缩的前置特性,实现时参考了mongodb和redis,因为k,v数据库的数据迁移,可以很容易代入到actor模型中。
实现无损迁移的关键思想是:

  1. 一个entity,只可能处于三种状态:源节点,迁移中,目标节点,在一个特定区段迁移完成前,我们把消息发到源节点,而源节点在迁移开始时进入转发模式,将消息路由到目标节点。
  2. 节点周期性同步路由信息,如果路由信息一直同步不到,节点将崩溃,只要节点崩溃的时间短于源节点转发保持的时间,可以保证消息一定不会发往错误的节点。

坏的设计

消息id不支持因果一致性

消息id有如下特性:

  • 全局唯一。
  • 可按操作时间排序。

因为数据库是mongodb,使用了mongodb的object-id。object-id是全局唯一的,也是全序的。但是:

  • 时间戳只精确到秒。
  • 默认生成规则不能保证消息id排序和操作顺序一致。
    对消息id a进行引用,回复消息id b。b一定在a之后。假设这2个请求几乎是同时的,只有按时间片下的counter递增才能保证全序。
    可以参考:雪花id算法

登录协议过重

登录时,会将用户加入到群组的在线列表。并下发群组的信息。这个导致登录请求会产生数十条群组调用。
更好的做法是将登录拆分为多个阶段。在最开始的,消耗最小的登录阶段限流。
并由客户端逐个群组上线,开始接收群组消息,以及返回对应群组信息,这样,即时用户有海量的群组,也可以分段加载,避免单条协议有太多的消耗。对通过了限流的用户提供稳定的服务。

客服会话历史不能简单迁移

最开始客服会话是 玩家账号 user to 客服 user 模型。但突然有会话迁移的需求。所以正确的客服会话应该是绑定在玩家账号 user 上的,即 session_id 为 《账号A客户服务》。这样,可以简单地转移玩家账号,并保有所有的聊天记录。

最近会话使用了文档模型,限制了最大数量

最初我认为游戏的最近游戏im是可以限制数量的,最近会话只是用户身上的一个数组,其实并不然,头部玩家在特定玩法下,很容易在下线期间收到成百上千的私聊。所以应该将用户的最近会话单独建表:

  • user_id
  • session_id
  • update_ts
  • ...
    从而可以记录所有用户曾经聊过天的会话。

im网关,而不是统一的接入层

im是唯一一个使用了长链接的业务,因为长链接,被其它业务用于实时推送。这个导致实时推送能力对im有接入依赖。应该将im的长连接上移到网关层。

端口选择错误

开始随便选择了一个默认端口12345。和特洛伊木马选择了同一个端口,部分用户因IT管理员的安全策略无法链接,为此增加了备用端口。
建议参考常见端口,在避开知名端口的情况下,选择较小的端口号:
常见端口

参考

https://zh.wikipedia.org/wiki/%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%...
https://zh.wikipedia.org/wiki/TCP/UDP%E7%AB%AF%E5%8F%A3%E5%88...
https://www.phodal.com/blog/developer-as-a-services/


enjolras1205
77 声望9 粉丝