本文由作者jhon_11分享,有大量修订和改动。
1、引言
如何设计一款高性能、高并发、高可用的im综合消息平台是很多公司发展过程中会碰到且必须要解决的问题。比如一家公司内部的通讯系统、各个互联网平台的客服咨询系统,都是离不开一款好用且维护的方便im综合消息系统。
那么,我们应该怎么样来设计一款三高特性的im系统,并能同时支持各个业务线的接入(比如:内部OA通讯、客服咨询、消息推送等等功能)有呢?
下面就由我来介绍一下我所负责的公司IM综合消息系统所经历的架构设计历程,以及架构设计过程中的一些思路和总结,希望能给你带来启发。
学习交流:
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-3954-1-1.html)
2、初版IM架构
2.1 概述
im第一版设计的初衷是公司需要一款im消息中间件用于支撑客服咨询业务。
但是,考虑到为了方便日后其他业务线也能接入消息沟通平台,所以一开始就将整个消息中心的能力需求给到中间件团队进行开发,以便除客服外的各业务线接入综合消息中心,从而实现多元的消息实时触达能力。
2.2 初版架构介绍
初版架构图如下图所示:
针对上面的架构图,我们逐个解释一下各模块的作用。
1)存储端:
在初版的架构下,存储端我们使用tidb、redis作为主要存储:
- [1] redis用于存储消息已读未读,缓存连接信息等功能;
- [2] tidb作为开源的分布式数据库,选择它是为了方便消息的存储。
2)mq消息总线:
我们使用rocketmq来实现消息总线(PS:即分布式情况下,不同im实例间通过MQ进行消息交互)。
消息总线是整个im的核心,使用rocketmq能支持十万级别的tps。基本所有服务都要从消息总线中消费消息进行业务处理。
3)zookeeper注册中心:各个服务会注册到zk中,方便服务之间内部进行调用,同样也可以暴露服务给外部进行调用。
4)link服务:
link服务主要用于接收客户端的ws(WebSocket协议)、tcp、udp等协议的连接。
同时调用用户服务进行认证,并投递连接成功的消息给位置服务进行消费,存储连接信息。
ws(WebSocket协议)过来的消息先到link再投递到消息总线。
5)消息分发服务:
消息分发服务主要用于接收消息总线推过来的消息进行处理,按照im内部消息协议构造好消息体后,又推送到消息总线中(比如会推给会话服务、消息盒子、link服务)。
6)位置服务:
存储link的(WebSocket协议)连接、tcp连接等信息,并使用redis进行缓存(key为userId),方便根据UserId查询到该用户所登录的客户端连接在哪个link上。
一个用户在相同设备只能登录一个,但可以支持多端登录。
7)用户服务:用于存储所有用户,提供认证查询接口。
8)消息盒子:存储所有消息,提供消息查询、消息已读未读、消息未读数、消息检索等功能。
9)会话服务:管理会话、群聊会话、单聊会话等功能。
2.3 整体时序图
整体架构的时序图如下:
3、初版IM架构存在的问题及思考
在上节的架构设计介绍中,我们详细分享了初版IM系统架构的设计思路以及具体流程。
那么在初版IM架构设计中还存在什么样的问题,又该如何优化呢?我们一条条来看看。
3.1 使用MQ消息总线的问题
正如上节所分享的那样,我们初版IM架构中,link服务到消息分发服务的消息使用的MQ消息总线。
初版架构设计中,link服务将消息下推给消息分发服务进行处理时,使用的是mq消息总线(通俗了说,IM集群内不同IM实例间的通信是依赖于MQ进行的消息传递),而mq消息总线必然做对有一定的时延(而且时延受制于MQ本身的系统实现和技术策略)。
举个例子:
当两个处于不同IM实例的客户端A和B聊天时,A用户发送消息到link --> 消息总线 --> 消息分发服务 --> 消息总线 --> link --> B用户。
正如上面这个例子,im消息投递流程太长了,并且这样也会大大降低系统的吞吐量。
3.2 消息落库为写扩散的问题
其实现阶段我们使用的是跟微信一样的写扩散策略(详见《企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等》)。
那么为啥微信使用写扩散不是缺陷,而对于我们的IM架构来说确是缺陷呢?
微信的技术特性:
- 1)微信号称没有存储用户的聊天记录,全是实时推送;
- 2)微信聊天记录全部会在我们手机端存储一份,两台手机终端上的聊天记录并不互通,并且互不可见。
我们的IM综合消息中心技术特性:
- 1)综合消息中心是会有拉取历史聊天记录(服务端拉取)的功能,存储了全量消息;
- 2)综合消息中心的客户端,需要支持网页版本。
综上所述:
- 1)写扩散对微信这样有移动端的富客户端版本的即时通讯产品十分友好,每个消息在消息分发的时候给处于这个会话(单聊,群聊)下的所有用户所在客户端先推送消息,没找到连接就针对这个用户写一个离线缓存消息,那么下次该用户登录进来,可以从缓存中拉取到该消息,并且清掉缓存;
- 2)写扩散对于我们这类通用综合消息平台并不友好,由于接入方大部分是网页版的客户端,所以没有缓存消息的能力,浏览器刷新就没有了任何消息,所以需要实时去服务端拉取历史消息。假设我是写扩散,在一个群聊中有五百个用户,针对这五百个用户在这个会话,我需要去写五百条消息,大大的增加了写io,并且还不能写缓存(得写数据库)。
3.3 tidb存在不稳定性和事务并发的问题
tidb是目前主流的开源分布式数据库,查询效率高、无需分库分表。
但同样的,tidb存在一些隐藏的问题:
- 1)tidb在高并发情况下,并发事务会导致事务失败,具体原因不知;
- 2)tidb排错成本高,公司很少有tidb专业运维,经常遇到不走索引的情况。
3.4 群聊、单聊冗余在同一个服务的问题
在我们初版的IM架构设计中,单聊和群聊是冗余在会话服务中的,并且冗余在同一张表的。
其实单聊、群聊从数据角度来说,还是会有些不同(比如业务属性)虽然都是会话,我们还是需要将这两个服务拆分开,细粒度的服务拆分能更好的把控整体的逻辑。
4、升级版IM架构
4.1 初始架构问题
正如前面两节分享的那样,渐渐的我们发现初版im架构有很大的不足之处。
在生产上暴露出了以下问题:
- 1)tps没达到预期,吞吐量不能满足公司业务的发展;
- 2)使用的存储中间件难以维护(主要是tidb),试错成本高,经常在生产暴露问题,并且速度越来越慢;
- 3)消息写扩散没有太大必要,并大大增加了系统io次数(原因见上一节);
- 4)一些特性无法支持,比如消息图文检索,消息已读未读。
4.2 升级版im架构介绍
本次升级后的im架构如下图所示:
如上图所示,改版后的各模块情况如下:
- 1)存储端:存储端我们改用了mysql,针对消息服务单独使用了主从mysql集群(主节点用于写消息、从节点用于消息检索)——;
- 2)mq消息总线:与第一版相比没有改动;
- 3)link服务:与第一版相比,改动了link服务到消息分发服务的消息推送方式(由MQ总线方式变更为tcp实时推送);
- 4)消息分发服务:集成了消息处理能力、路由能力,每台消息分发服务拥有所有link服务的tcp连接;
- 5)单聊服务:负责单聊会话的管理能力;
- 6)群聊服务:负责群聊会话的管理能力;
- 7)用户服务:提供用户认证,登录\注册能力。
5、详细对比针对初版IM架构的改动
升级版的IM架构,对比初始初始,具体主要是下面这些改动。
5.1 改进了不同im实例间的消息分发方式
针对初版MQ消息总结的问题,升级版架构中,我们将link到消息分发服务改为tcp实时连接,百万客户端连接同一台link机器,消息实时触达能力tps达到16万。
link到消息分发服务的改版是本次设计的亮点之一,完全消除了mq推送的时延性,并且路由简单,几乎实时触达。
举个例子:(当两个处于不同IM实例的客户端A和B聊天时)
- 1)初版架构中是:A用户发送消息到link --> 消息总线 --> 消息分发服务 --> 消息总线 --> link --> B用户;
- 2)升级版架构是:用户A --> link --> 消息分发 --> link --> 用户B。
而且:link服务到消息分发服务集群的消息推送使用轮询负载均衡的方式,保证公平,不会导致个别机器负载过高。
5.2 取消了位置服务
取消了位置服务(这里的位置不是指的IM消息里的地理位置消息哦),消息分发服务集成位置服务的能力。
消息分发服务本身业务简单,不需要再单独划分位置服务,因为会增加网络io,并且消息分发服务直连link,而让它负责路由则更加方便。
5.3 存储由tidb改成了mysql
存储端由tidb改成了mysql,增强了可维护性,消息服务使用mysql主从读写分离方式,提高了消息落库速度与检索速度的同时,也减轻数据库压力。
前面有提到过使用tidb这样维护成本高,排查问题难的分布式数据库是一件很痛苦的事情。
而我们使用mysql更加稳定,大家对mysql的学习成本相对较低。针对消息服务使用读写分离的方式,能大大提高消息的吞吐量。
5.4 实现了初版无法实现的特性功能
升级版架构中,我们实现了初版无法实现的特性功能,比如消息已读未读、红包推送、商品链接推送等功能。
新版综合消息中心加入了消息已读未读、发送红包、链接推送等功能,但这些功能带有一定的业务特性,毕竟不是所有Im都需要,可通过配置取消这些功能。
5.5 消息由写扩散改为读扩散
升级版IM架构中,消息存储由写扩散改为了读扩散。
前面我们有提到写扩散和读扩散的利弊,对于网页端IM我们更适合使用读扩散,只需要落一条消息,大大提高消息服务的吞吐量.
5.6 增加了门面服务
升级版IM架构中,我们增加门面服务 im-logic,用于暴露给第三方业务线接口调用。
初版架构中,都是im的各个服务各自暴露接口给到外部进行调用, 而升级版架中我们统一使用logic服务暴露给外部调用。
在logic服务针对调用可以做一些处理,这样不会影响到整体im的通用,不会增加im底层代码的复杂度,从而将业务逻辑与底层进行解耦。
6、优化后的效果对比
针对升级版和初版IM架构,我们也做了一些对比测试,具体的测试过程就是详细展开了。
以下是测试结果:
7、业务线接入im综合消息系统的业务划分思考
7.1 到底该如何设计高性能通用im综合消息系统
关于业务线接入im综合消息系统的业务划分,我也做了一些总结和思考,为了更形象和易于理解,我这里以客服系统以及企业微信为例来进行分析。
假如我开发了一款通用的im综合消息系统,现在有很多业务方需要接入我们,我们该如何进行业务域的清晰划分就显得尤为重要,需要在妥协与不妥协中进行平衡。
就像当前市面上开源的im消息平台来说,存在的问题主要是:要么是集成了很多的业务逻辑,要么就只是一款单纯的客服系统,再或者就是一款IM好友聊天系统,中间的业务划分并不明确。当然,这也有好处,拿来就能用,并不需要进行二次业务封装。
那么,到底如何将im设计为一款真正的高性能通用im综合消息系统呢?
通用的综合消息消息平台只需要有通用的底层能力:
以下案例假设在我已经按照上述架构设计了一版im综合消息中心。
7.2 以客服系统为例
客服系统:
客服系统不光需要实现自身业务,还需要整合im的消息能力(消费im的消息),来进行场景分析,实现会话变更、信令消息推送等逻辑。
客服系统内部需要根据im的底层支持能力进行相应的业务封装以及客服系统的客服用户池,c端用户池如何初始化到im的用户中心这些问题都是需要考虑进去的。
7.3 内部OA通信为例
内部OA通信:
员工内部OA通信系统需要集成IM好友功能,需要根据im的用户中心封装组织架构,用户权限等功能。
同时,内部通信系统需要根据im实现消息已读未读,群聊列表,会话列表拉取等功能。
8、本文小结
im的综合消息平台是一款需要高度结合业务的中间件系统,它直接与业务打交道,跟普通的中间件有根本的区别。
一款好用的im综合消息平台,直接取决于你的通用性,可扩展性以及系统吞吐能力。
希望这篇文章所分享的内容,能对大家开发im时候的思路有所启迪。
9、参考资料
[2] 从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路
[3] 瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)
[4] 阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处
[8] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等
[9] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[10] 从新手到专家:如何设计一套亿级消息量的分布式IM系统
[11] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
[12] 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路
[13] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
(本文已同步发布于:http://www.52im.net/thread-3954-1-1.html)