# 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**: 2026-07-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: IM, 微服务, Grpc, 直播
## README
# MIMO-企业级即时通讯系统(MIMO-Enterprise IM System)
## 项目概述
本项目是一套**企业级即时通讯(IM)系统**,采用微服务架构设计,支持多端接入、实时消息推送、群组聊天、企业组织架构同步等核心功能。
### 项目背景
公司原先所属直播平台项目使用第三方 IM 服务,随着业务快速增长,用户量与消息量急剧攀升,**仅 IM 服务一项的年度第三方费用即达到数百万元人民币**。同时,直播平台每逢营销活动,消息分发量、用户同时在线数、接口调用频次、单个房间内最大成员数量等核心指标均会大幅飙升,而第三方服务的服务规则与成本控制策略对调用方形成了显著约束,包括限流、限频次调用等,直接制约了正常业务的推进与扩展。
此外,原先平台主要面向**海外东南亚市场**,当地基础网络设施波动较大,对业务稳定性构成持续挑战。依赖第三方服务时,服务质量响应及问题处理存在明显滞后,且平台方处于被动地位,难以自主把控服务等级与故障恢复时效。
基于以上痛点,团队决定**自建企业级 IM 系统**,通过服务拆分、多级负载均衡、多层数据存储架构以及完善的监控体系,实现:
- **成本可控**:消除高额第三方服务费,降低长期运营成本
- **弹性扩展**:自主掌控服务容量,灵活应对营销活动带来的流量洪峰
- **质量保障**:针对海外网络环境优化长连接稳定性,缩短故障响应与恢复时间
- **业务解耦**:摆脱第三方服务规则约束,按需定制消息策略与功能迭代
系统通过微服务化设计支撑海量并发长连接与复杂的直播互动场景,和企业级系统整合,同时保留与第三方系统集成能力,兼顾开放性与自主性。
---
## 系统架构
### 整体架构图
### 架构分层说明
#### 1. 客户端接入层
- **多端覆盖**:支持 Android、iOS、Web 三端接入
- **双协议接入**:
- **TCP 长连接**:通过 LVS 负载均衡接入后端 Comet 服务,维持实时在线状态与消息实时推送
- **HTTP 短连接**:通过 Spring Gateway 处理管理操作及第三方业务调用
#### 2. 网关与流量调度层
| 组件 | 职责 |
|------|------|
| **Spring Gateway** | HTTP 入口网关,承担请求路由、鉴权、限流等功能 |
| **LVS** | 四层负载均衡,负责 TCP 长连接流量分发 |
| **3rd Business Server** | 第三方业务服务器 HTTP 接入,支持与企业现有系统集成 |
#### 3. 服务注册与配置中心
- **Eureka 集群**:服务注册与发现,所有微服务自动注册实现服务间调用
- **配置中心**:集中管理各服务配置信息,支持动态配置下发
#### 4. 核心业务服务集群
服务间通过 **gRPC** 进行高性能内部通信:
| 服务 | 职责 |
|------|------|
| **Comet** | 长连接网关(目前主要采用websocket,实际上,可以扩展并支持任意协议接入),维护海量客户端 TCP 连接,负责消息收发。
(1) 维护物理连接的管理,包括read、write、idle等
(2) 维护集群环境下,同一账户信息多次连接下的会话唯一性
(3) 维护所有上行指令到Logic服务的透传 |
| **Logic** | 业务逻辑处理中心,处理消息路由、用户状态、业务规则。
(1) 无状态化所有的处理上下文
(2) 用户有效性认证以及客户端SDK采集信息的处理
(3) 对连接会话引入逻辑会话,并统一基于逻辑会话的active管理
(3) 维护房间功能以及房间成员功能
(4) 维护消息的接收与分发功能,包括点对点消息、点对房间消息
(5) 维护消息的接收确认机制以及离线消息消费机制
(6) 维护用户访问权限功能,包括基于房间维度的禁言和禁入、基于平台维度的全局禁言和禁入、基于用户维度的私信发送、基于用户和房间维度的交互频次限制
(7) 对外部业务系统提供HTTP接口服务 基于系统级,提供不同日期维度的metric信息。比如点对点上行消息数量、点对点下行消息数量、房间上行消息数量、房间下行消息数量、房间创建数量、新增用户数量、登陆用户数、活跃用户数等 |
| **Push** | 推送服务,负责离线推送与消息下发 |
| **System** | 提供附件/背包功能,以及其他与具体业务关联性不强的功能集合 |
| **Chat** | 上层应用业务整合,比如单聊、群聊、应用层鉴权、用户角色、消息以及附件功能、企业端OA整合(用户组织结构)等具体业务 |
| **Admin** | 管理后台服务,提供接入端信息查询、多维度Metric查询的操作界面 |
#### 5. 数据存储与消息中间件
| 组件 | 用途 |
|------|------|
| **Redis** | 数据缓存 + 基于 Pub/Sub 的消息广播(Comet 节点间消息扩散) |
| **MongoDB** | 主要持久化存储,存储聊天记录、用户资料等文档型数据 |
| **Kafka** | 消息队列,用于消息存储与中转,实现削峰填谷、异步处理 |
| **Elasticsearch** | 全文检索与消息云存储,支持历史消息搜索 |
| **OSS** | 对象存储,用于附在文本文件、图片、多媒体文件等。 |
#### 6. 统一日志与运维监控
- **ELK 栈(LogStash + Elasticsearch + Kibana)**
- 各服务日志汇聚至 LogStash
- Elasticsearch 存储日志,Kibana 提供可视化查询与监控
- 实现全链路日志追踪与故障排查
---
## 业务功能
系统支持以下核心业务能力:
- **好友功能**
- **企业组织结构同步**
- **群组功能**
- **文件上传功能**
- **聊天记录功能**
- **信息检索功能**
- **群组机器人功能**
---
## 技术栈
| 层级 | 技术选型 |
|------|----------|
| 接入网关 | Spring Gateway, LVS |
| 服务注册 | Eureka |
| 配置中心 | Spring Cloud Config |
| 服务通信 | gRPC + protobuf |
| 数据缓存 | Redis |
| 持久化存储 | MongoDB |
| 消息队列 | Kafka |
| 全文检索 | Elasticsearch |
| 日志监控 | ELK (LogStash + Elasticsearch + Kibana) |
| 客户端 | Android, iOS, Web |
---
## 项目结构
mimo/
├──mimo-comet/ # 长连接网关服务
├──mimo-logic/ # 业务逻辑处理服务
├──mimo-system/ # 背包/附件服务,以及部分弱业务性功能
├──mimo-push/ # 推送服务
├──mimo-chat/ # 聊天业务服务
├──mimo-admin/ # 管理后台服务
├──mimo-gateway/ # Spring Gateway 网关
├──mimo-discovery/ # 服务注册中心
├──mimo-config/ # 配置中心
├──mimo-common/ # 公共组件与工具
├── proto/ # gRPC 协议定义文件
├── docs/ # 文档与架构图
└── README.md
---
## 客户端效果图
>
>
>
>
>
>
>
>
>
>
>
>
>
---
## 项目实现难点分析
> **(此章节会根据作者心情和精力情况,持续补充,当成博客看,也行)**
>
> - 海量长连接的维持与心跳管理
> - 消息可靠投递机制(至少一次 / 精确一次)
> - 消息顺序保证与乱序处理
> - 群聊消息的扩散与扇出优化
> - 历史消息的分页加载与搜索性能
> - 多端消息同步策略
> - 服务高可用与故障转移设计
> - 通讯层协议制定
# 应用协议制定
## 认证与授权
- 登入消息
- 登出消息
## 房间操作集
- 创建、加入、离开、销毁、基于用户罗列已加入的所有房间
## 上行指令
- 点对点消息
- 点对房间消息
- 心跳消息
- 查询待确认消息
## 下行响应指令(对应于上行指令)
- 接收确认消息(发送方需要根据该消息以确认服务端是否已经收到)
- 登陆成功消息
- 用户所加房间列表消息
- 待确认列表消息
- 指令错误响应消息(上行指令错误或者受限制时,服务端返回错误详情)
## 下行分发消息
- 分发到客户端接收的业务消息,一般客户端会收到点对点消息、房间分发消息。
# 服务端响应码定义
| 分组 | 响应码 | 消息描述 | 备注 |
| -------- | ------ | :------------------------------------------- | ------------------------------------------------------------ |
| 系统 | 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接口实现
# 实现方案的选择与思考
## 连接路由
业内的做法,大多是有二种:
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;
```
## 消息确认
对于大多数的IM工具应用,消息确认机制是为保证“接受-发送”双方的信息可达性,所以,对于分发至客户端的消息,往往都需要客户端明确对下发的消息进行Ack,即确认。服务端才会将对应消息删除。
- 点对点消息
即 用户A 发送给 用户B。此类消息,系统会明确要求 用户B对消息的“ACK",否则,断线重连时,或者客户端主动查询时,还会重复推送。
- 群消息
即用户A在群内做消息广播,群内其他人需要对消息做确认,否则就会在上线后,继续推送
- 房间分发消息
对于直播场景,特别是直播间内,消息分发是一个高频行为。特别是各种打榜型的营销活动,服务端会推送大量的IM消息(营销、校正等)至直播间内,此时如果客户端对该场景的消息都要逐一应答,在自身下行带宽挤占的情况下,上行带宽以及手机的处理消耗是很高昂的。故,目前针对直播间内消息分发,服务端不做任何的“ack”要求,同时客户端也不需要对此类消息发送“ACK”。
## 读扩散还是写扩散
结合自身业务,目前在系统上,对房间的最大成员数量限制在1万(可以配置,参考RoomDefinitionInfo)。
故简单粗暴的采用:写扩散。毕竟大多数中小型项目,直播间内也很难存在大量超过1万成员数量的可能性 。
当然,如果像很多头部项目,动不动就几十上百万用户挤在同一个直播间内的,大多数都是采用读扩散+写扩散的方式并行,同时,还会应用很多分桶投递等方式,这个后续有机会再细写。