Skip to content

房间IM

qinwei edited this page Dec 15, 2022 · 7 revisions

房间IM介绍

房间IM是SONA平台中非常重要的一部分,可以说是整个房间系统的骨架,大部分业务功能都是基于消息来实现的。除了常见的房间内用户的聊天互动,还包含进场、礼物打赏、连麦、各种业务指令等.

SONA一共支持 4 种消息:

  • 聊天室消息

当前房间是聊天室模式,比如直播间这种,一条消息发送给房间内的所有在线用户

  • 群组消息

当前房间是群组模式,比如游戏房这种,一条消息发送给房间内的所有用户

  • 点对点消息

不论是群组模式还是聊天室模式,都可以使用点对点消息,一条消息指定发送给房间内的某个或者某些用户,未被指定的用户是收不到的

  • 广播消息

只支持聊天室模式,一条消息同时发送给多个房间,比如给所有聊天室都发送横幅等场景,就可以使用

消息发送

房间IM的消息发送入口统一在 cn.bixin.sona.api.im.RouterRoomMessageService

不论是sona自动发送的,还是业务方服务端主动发送的消息,最终都是使用这个接口发送

消息发送有2种方式:

  • 业务方服务端直接通过dubbo接口发送
  • 通过 sona-sdk 发送

服务端直接dubbo调用比较简单,这里主要介绍 通过 sona-sdk 发送的方式

1.在 t_product_config 和 t_room_config 的房间配置中,有一个 im_send_type 字段,可以用来指定客户端通过 http 还是 长连接 发送

1=短连 2=长连

2.不管是短链还是长连,最终都会将 客户端发送的消息 回调给业务方,由业务方告知 sona,此条消息是否可以发送,业务方可以在回调中对消息做风控或者消息内容的组装等。

3.业务方需要实现接口 cn.bixin.sona.api.im.callback.MessageCallbackRemoteService,并在实现类上添加注解 @DubboService(group = "sona_${productcode}")

其中 productcode 就是业务申请的产品码,sona 会根据 group 去回调不同的业务方

4.在 sona-service 的apollo 添加配置 spi.msg.productCode,必须配置后 sona 才会去回调,否则默认可以直接发送

多个业务方用 逗号 隔开,例如 "chatroom,living"

sona-service apollo 配置

消息分级

房间IM 和单聊IM 相比有着巨大的差别,最主要的也是最有技术难点的就是 消息的高并发压力,举个例子: 单聊场景下,如果两个人每 10 秒说一句话,实际上每秒的消息下推数只有 0.1;群聊或者聊天室场景,假设是一个 500 人群,如果群里每个人也是每 10 秒说一句话,实际每秒的消息下推数是 500 / 10 * 500 = 25000;那么对于一个10w 人在线的房间互动场景,如果房间里每个人也每 10 秒说一句话,实际每秒可产生的消息下推数就是 100000 / 10 * 100000 = 10 亿,这是一个非常夸张的数字。 实际上,房间一般不会有这么高的发言和互动热度,即使能达到,也会在服务端进行限流和选择性丢弃。一个是考虑服务端的承受能力基本不可能达到这个量级,另一方面,即使消息能全部推下去,客户端也处理不了每秒一万条消息的接收,对客户端来说,一般每秒接收几十条消息就已经是极限了。

  • 单个TCP 连接的处理能力有限,受网络、设备的制约
  • 房间消息使用的写扩散模式推送,容易产生消息风暴
  • 尽可能保证重要消息送达

基于以上几点, sona 对消息进行了分层管控,可以丢弃一部分不重要的消息,尽可能保证重要消息的送达 消息分级

sona 将消息分成了4个等级:

高等级消息 非常重要的消息,比如价值极高的礼物消息
中高等级 比较重要的消息,可接受较大延时送达,价值较高的礼物
中等级 一般的消息,可接受较低延时送达
低等级 不重要的消息,可直接丢弃的,普通文本消息

消息白名单

sona 增加了消息白名单,用来管理消息等级,防止业务方乱用高等级消息

  • 高等级消息需要先配置白名单方可使用
  • 接口参数中传入的消息等级不能高于白名单中配置的等级

比如打赏消息的 msgType 为 333,业务方可以在白名单中配置高等级,然后根据打赏的金额,对333类型的消息设置不同的等级,超过1万设置高等级,小于5块设置低等级

白名单

消息频控

上面也介绍过,当房间消息量太大了,不可能保证每条消息都能送达,也没有必要,比如直播间发言,丢弃一部分是完全可以接收的,所以 sona 做了消息分级和 消息全局频控,不同等级的消息频控策略不同。

频控策略

这里介绍下房间整体的一个频控策略:

  • 单房间每秒最多60条消息
  • 其中高等级消息每秒最多30条
  • 如果在这个1秒的窗口里,前面已经发了60条普通消息,再发30条高优先消息可以发出去;
  • 如果是先发30条高优先级消息,再发40条普通消息,普通消息只能发30条

频控算法

sona 是基于 redis + lua 脚本实现 令牌桶算法,达到精确的全局频控

具体算法实现在 lua 脚本

不同等级的频控配置在sona-service的apollo 上,可以动态修改,message.flow.config 和 message.delay.config sona-service apollo 配置

redis cluster 执行lua脚本时,必须保证 keys 都在同一个slot中

可以通过 Redis 中的 hashtag 来保证同一个roomId 的keys都被哈希到同一个slot中

lua 脚本执行耗时基本在 几毫秒,并且redis 故障或者超时,消息默认可以正常发送

  • h_token 高等级令牌 30/s max 30
  • token 普通令牌 60/s max 60

当前房间发送一条消息,token减1

高等级

高等级

中高等级

中高等级

中等级

中等级

低等级

低等级

群组消息、点对点消息、广播消息不走频控

消息隔离

  • 消息优先级隔离,不同等级的消息互不影响

  • 可以在 sona-service 中配置 自定义的线程池,实现不同的 productCode 之间的消息互不影响

配置 Executor 的bean, bean name 为 ${productCode}-EXECUTOR

消息必达

sona 对重要消息做了 消息必达:

  • 消息必达主要通过 业务ack+重试 实现
  • 只支持高等级

对高等级消息增加 ack 机制,发送高等级消息时,会从当前房间在线列表内选取一批高价值用户作为 ack 用户,这些用户收到需要 ack 的消息,会立即回复服务端已收到。服务端会定时检测,对没有回复ack 的客户端进行消息重发。每条消息都有唯一的消息id 标识,客户端基于消息id 做幂等。

mercury 长连针对房间消息ack 做了特殊优化,在协议层支持了房间ack。消息发到 Mercury 网关,给需要 ack 的 user 推送消息时,会在协议的header 中添加 ack标识,mercury sdk 解析到这个标识会立即回复一条 ack 消息。

消息全链路查询

为了做到每条消息都可追踪,sona 实现了房间消息的全链路日志查询,可以根据 messageId 查询出消息的发送状态,包括 发送成功、被频控、被丢弃等状态

可以通过sona 后台管理系统上查询 sona-admin