# Mimo
**Repository Path**: 44346460/Mimo
## Basic Information
- **Project Name**: Mimo
- **Description**: 针对直播场景的IM实现方案
- **Primary Language**: Java
- **License**: GPL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 11
- **Forks**: 3
- **Created**: 2020-06-20
- **Last Updated**: 2024-09-25
## Categories & Tags
**Categories**: Uncategorized
**Tags**: IM, 微服务, Grpc, 直播
## README
# 背景
- 原先所属项目使用第三方IM服务,随着业务增长,用户量与消息量增长较快,一年光IM这一块的第三方服务费用就超过了100W。
- 由于项目是直播平台,每次平台有相应营销活动,对应的消息分发量、用户同时在线数、接口调用频次、单个房间内最大成员数量等都会大幅度提升,而此时第三方服务本身的服务规则和成本控制就会对调用方有很大的约束,包括限流、限频次调用等,进而影响正常业务推进。
- 直播平台本身主要是面向海外东南亚市场,而海外的基础网络设施往往比较波动,容易对业务产生影响。而依赖于第三方服务,对于服务质量响应以及问题处理,也是滞后,且被动性太大。
基于以上原由,综合服务成本、服务质量等多方面因素,决定研发基于直播场景的IM解决方案。
# 应用协议制定
## 认证与授权
- 登入消息
- 登出消息
## 房间操作集
- 创建、加入、离开、销毁、基于用户罗列已加入的所有房间
## 上行指令
- 点对点消息
- 点对房间消息
- 心跳消息
- 查询待确认消息
## 下行响应指令(对应于上行指令)
- 接收确认消息(发送方需要根据该消息以确认服务端是否已经收到)
- 登陆成功消息
- 用户所加房间列表消息
- 待确认列表消息
- 指令错误响应消息(上行指令错误或者受限制时,服务端返回错误详情)
## 下行分发消息
- 分发到客户端接收的业务消息,一般客户端会收到点对点消息、房间分发消息。
# 服务端响应码定义
| 分组 | 响应码 | 消息描述 | 备注 |
| -------- | ------ | :------------------------------------------- | ------------------------------------------------------------ |
| 系统 | 0 | 代码正常的业务响应 | |
| 系统 | -1 | 未知错误 | 比如NPE,网络异常,数据异常等 |
| 用户模块 | 1000 | 登陆授权失败 | 有可能是账户密码错误,也有可能是账户不存在 |
| 用户模块 | 1001 | 用户被封禁 | |
| 用户模块 | 1002 | 点对点消息时,目标用户不存在 | |
| 用户模块 | 1003 | 当前用户在其他移动设备上登录,此设备被踢下线 | 同一账户在多处登录导致前一个已经登陆的人被强制下线 |
| 用户模块 | 1004 | 当前用户不在线 | 一般是服务间回写时,用户不可触 |
| 聊天室 | 2000 | 聊天室不存在 | 加入 聊天室时或者是往聊天室发消息时 |
| 聊天室 | 2001 | 加入聊天时,聊天室成员超限 | 聊天室有人数限制,由服务端调配 |
| 聊天室 | 2002 | 当前用户在聊天室中已被禁言 | 聊天室内被禁言 |
| 聊天室 | 2003 | 当前用户已被踢出并禁止加入聊天室 | 被禁止的时间取决于服务端调用踢出接口时传入的时间 |
| 聊天室 | 2004 | 尚未加入聊天室 | 一般指用户往聊天室发消息时,用户并没有保证自己已经加入了聊天室 |
| 内部服务 | 3000 | 用户locate不在本机 | 一般指向comet通讯时的用户不在本机上,目前服务间调用判断 |
| 消息 | 4000 | 私信拒绝 | 一般来说,点对点消息时,被对方加入了黑名单 |
| 消息 | 4001 | 基于用户维度上行指令过于频繁 | 一般来说,正常用户是不会发生的,主要是防止恶意狂刷消息(点对点和房间消息) |
| 消息 | 4002 | 基于房间维度上行指令过于频繁 | 一般来说,聊天室的上行消息过多, 主要也是防止恶意狂刷消息(房间消息) |
# 协议类图
- 上行指令类图

- 下行响应指令类图

- 下行分发消息类图

# 网络通讯协议
- 对Client目前采用websocket,主要是兼顾H5和原生应用
- 服务间通讯采用gRPC,一方面是为提高通讯效率,一方面是为了减少通讯时内部带宽压力。
关于gRPC以及protobuf信息,请自行从官网学习。
- IM系统对外部业务系统提供HTTP接口实现
# 技术架构
## 架构图

## Gateway服务
- 提供外部业务系统访问时的认证以及请求分发,且所有分发请求有且指向Logic服务
## Comet服务
- 维护物理连接的管理,包括read、write、idle等
- 维护集群环境下,同一账户信息多次连接下的会话唯一性
- 维护所有上行指令到Logic服务的透传
## Logic服务
- 无状态化所有的处理上下文
- 用户有效性认证以及客户端SDK采集信息的处理
- 对连接会话引入逻辑会话,并统一基于逻辑会话的active管理
- 维护房间功能以及房间成员功能
- 维护消息的接收与分发功能,包括点对点消息、点对房间消息
- 维护消息的接收确认机制以及离线消息消费机制
- 维护用户访问权限功能,包括基于房间维度的禁言和禁入、基于平台维度的全局禁言和禁入、基于用户维度的私信发送、基于用户和房间维度的交互频次限制
- 对外部业务系统提供HTTP接口服务
- 基于系统级,提供不同日期维度的metric信息。比如点对点上行消息数量、点对点下行消息数量、房间上行消息数量、房间下行消息数量、房间创建数量、新增用户数量、登陆用户数、活跃用户数等
## Push服务
- 维护消息分发的管理,包括分发重试等
## Admin服务
- 提供接入端信息查询、多维度Metric查询的操作界面
# 实现方案的选择与思考
## 连接路由
业内的做法,大多是有二种:
1. 业务上存在一个“路由”服务(模块)。该 “路由” 能够自身有效的感知后端的具体连接管理服务(比如ServerA、ServerB),当新的client接入时,先经过“路由”并按照业务或者资源规则,引导client跳转连接到serverA或者ServerB。
- 优点:可控性强,全由业务代码决定。
- 缺点:一方面需要额外的功能设计成本,一方面,client是先到"路由",之后再连接到对应的Server,相当于连接了两次。
- 图示如下:
2. 整个接入层由CometA、CometB等组成,该层的接入前端依赖于LVS(对于LVS更多的信息,可以查看阿里云官网),既第三方组件服务,依据其相关的“路由规则”,对连接分发至后端CometA、Comet等。
- 优点就是:系统本身不用考虑路由分发问题,对于自定义需求不高的项目,可以有效的降低自身的设计复杂度。
- 图示如下:
对于当前项目来说,连接路由方案使用的是后者。
具体实现可查看**Commet**服务的***WebSocketServer***以及**Login**服务的***UserServiceImpl#login***
## 连接会话管理
由于所有的Commet实例,都是对等存在,而业务上需要保证任一用户信息的连接唯一性。
当任意的client接入comet时,对应的comet实例依赖于redis的pub/sub机制,向集群中的其他comet广播。
**图示如下:**
其中对于每个comet实例,为了做自我区别,当其启动时,以uuid的方式为每个comet生成一个唯一性编号。以便于当“登陆事件”广播时,每个实例能够区分client所连接的目标“comet”是否落在当前实例。如果不是,则各个实例遍历本机寻找client的连接信息,若存在,则断开并清除。配置类信息大体如下:
```java
@Validated
@ConfigurationProperties(prefix = "mimo.comet.server")
public class LocalCometServerConfig {
/**
* 每个应用实例的ID需保持唯一性
*/
private String applicationId = RandomStringUtils.uniqueRandom();
/**
* Redis pub/sub 监听本地新的会话连接
*/
@NotEmpty
private String clusterChannel;
/**
* Redis pub/sub 监听logic session 过期时的强制断开信息
*/
@NotEmpty
private String logicExpiredChannel;
/**
* 用于获取本机的配置信息
*/
@NotNull
@Valid
private ZoneDTO zone;
private com.mimo.common.rpc.proto.UserProto.ZoneDTO protoZone;
```