# 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 --- ## 客户端效果图 > > > 2 > > 3 > > 4 > > 5 > > 6 > > 7 --- ## 项目实现难点分析 > **(此章节会根据作者心情和精力情况,持续补充,当成博客看,也行)** > > - 海量长连接的维持与心跳管理 > - 消息可靠投递机制(至少一次 / 精确一次) > - 消息顺序保证与乱序处理 > - 群聊消息的扩散与扇出优化 > - 历史消息的分页加载与搜索性能 > - 多端消息同步策略 > - 服务高可用与故障转移设计 > - 通讯层协议制定 # 应用协议制定 ## 认证与授权 - 登入消息 - 登出消息 ## 房间操作集 - 创建、加入、离开、销毁、基于用户罗列已加入的所有房间 ## 上行指令 - 点对点消息 - 点对房间消息 - 心跳消息 - 查询待确认消息 ## 下行响应指令(对应于上行指令) - 接收确认消息(发送方需要根据该消息以确认服务端是否已经收到) - 登陆成功消息 - 用户所加房间列表消息 - 待确认列表消息 - 指令错误响应消息(上行指令错误或者受限制时,服务端返回错误详情) ## 下行分发消息 - 分发到客户端接收的业务消息,一般客户端会收到点对点消息、房间分发消息。 # 服务端响应码定义 | 分组 | 响应码 | 消息描述 | 备注 | | -------- | ------ | :------------------------------------------- | ------------------------------------------------------------ | | 系统 | 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万成员数量的可能性 。 当然,如果像很多头部项目,动不动就几十上百万用户挤在同一个直播间内的,大多数都是采用读扩散+写扩散的方式并行,同时,还会应用很多分桶投递等方式,这个后续有机会再细写。