前言
如何设计一款高性能,高并发以及高可用的im消息沟通平台是很多公司发展过程中必须要碰到并且解决的问题,如一家公司内部的通信,各个互联网平台的客服咨询,都是离不开一款好用并且方便维护的im消息沟通系统。那么我们应该怎么样来设计一款三高特性的im系统,并能支持各个业务线接入等功能呢。
下面就由我来介绍一下我所负责im消息沟通系统所经历的设计历程;
im第一版设计
im第一版设计的初衷是公司需要一款im消息中间件用于支撑客服咨询业务,但是为了方便日后其他业务线也能接入消息沟通平台,所以将整个消息中心的能力需要给到中间件团队进行开发,各业务线接入消息沟通中心实现消息实时触达的能力。
im初版架构介绍
im消息中心初版架构图
存储端 在存储端我们使用tidb,redis作为主要存储,redis用于存储消息已读未读,缓存连接信息等功能,tidb作为开源的分布式数据库,选择它是为了方便消息的存储;
mq消息总线 我们使用rocketmq来实现消息总线,消息总线是整个im的核心,使用rocketmq能支持十万级别的tps。基本所有服务都要从消息总线中消费消息进行业务处理;
zookeeper注册中心 服务会注册到zk中,方便服务之间内部进行调用,同样也可以暴露服务给外部进行调用;
link服务 link服务主要用于接收客户端的ws,tcp,udp等协议的连接,调用用户服务进行认证,并投递连接成功的消息给位置服务进行消费,存储连接信息。ws过来的消息先到link再投递到消息总线;
消息分发服务 消息分发服务主要用于接收消息总线推过来的消息进行处理,按照im内部消息协议构造好消息体后又推送到消息总线中,比如会推给会话服务、消息盒子、link服务;
位置服务 存储link的ws连接,tcp连接等信息,并使用redis进行缓存,key为userId,方便根据UserId查询到该用户所登录的客户端连接在哪个link上。一个用户在相同设备只能登录一个,可以多端登录;
用户服务 用于存储所有用户,提供认证查询接口;
消息盒子 存储所有消息,提供消息查询,消息已读未读,消息未读数,消息检索等功能;
会话服务 管理会话,群聊会话,单聊会话等功能。
整体流程
im消息中心初版时序图
该设计存在的问题思考
在上面的架构设计中,我们详细介绍了初版系统架构的设计思路以及具体流程,那么在初版设计中存在什么样的问题,又该如何优化呢?
link服务到消息分发服务的消息推送使用消息总线。
初版架构设计中,link服务将消息下推给消息分发服务进行处理使用的mq消息总线,使用mq消息总线会有一定的时延。a用户发送消息到link --> 消息总线 -->消息分发服务 --> 消息总线 --> link --> b用户这个流程太长,并且大大降低系统吞吐量
消息落库为写扩散。
其实现阶段我们使用的微信采用的是写扩散,为啥微信使用写扩散不是缺陷,对于消息沟通中心来说确实缺陷呢?微信特性:一、微信号称没有存储用户的聊天记录,全是实时推送二、微信聊天记录全部会在我们手机端存储一份,两台手机终端上的聊天记录并不互通,并且互不可见消息沟通中心特性:一、消息沟通中心是会有拉取历史聊天记录的功能,存储了全量消息二、消息沟通中心不仅要支持客户端版本,还需要支持网页版本,并且很大概率为网页版综上1)写扩散对微信这样有客户端版本的即时通讯产品十分友好,每个消息在消息分发的时候给处于这个会话下的所有用户所在客户端先推送消息,没找到连接就针对这个用户写一个离线缓存消息,那么下次该用户登录进来,可以从缓存中拉取到该消息,并且清掉缓存。2)写扩散对于通用消息沟通平台并不友好,由于接入方大部分是网页版的客户端,所以没有缓存消息的能力,浏览器刷新就没有了任何消息,所以需要实时去服务端拉取历史消息,假设我是写扩散,在一个群聊中有五百个用户,针对这五百个用户在这个会话,我需要去写五百条消息,大大的增加了写io,并且还不能写缓存,得写数据库。
tidb存在不稳定性,以及事务并发问题
tidb是目前主流的开源分布式数据库,查询效率高,无需分库分表。同样,tidb存在一些隐藏的问题一、在高并发情况下并发事务会导致事务失败,具体原因不知;二、tidb排错成本高,公司很少有tidb专业运维,经常遇到不走索引的情况;
群聊单聊冗余在同一个服务
在初版设计中,单聊和群聊是冗余在会话服务,并且冗余在同一张表的,其实单聊群聊还是会有不同,如业务属性,虽然都是会话,我们还是需要将这两个服务拆分开,细粒度的服务拆分能更好的把控整体的逻辑。
im第二版升级设计
渐渐的我们发现初版im有很大的不足之处,在生产上暴露出了以下问题:
tps没达到预期,吞吐量不能满足公司业务的发展
使用的存储中间件难以维护,试错成本高,经常在生产暴露问题,并且速度越来越慢
消息写扩散没有太大必要,并大大增加了系统io次数
一些特性无法支持,比如消息图文检索,消息已读未读
im升级版架构介绍
im消息中心升级版架构图
存储端 存储端我们使用了mysql,针对消息服务单独使用了主从mysql集群,主节点用于写消息,从节点用于消息检索;
mq消息总线 与第一版相比没有改动;
link服务 与第一版相比改动了link服务到消息分发服务的消息推送方式;
消息分发服务 集成了消息处理能力,路由能力,每台消息分发服务拥有所有link服务的tcp连接;
单聊服务 负责单聊会话的管理能力;
群聊服务 负责群聊会话的管理能力;
用户服务 提供用户认证,登录注册能力;
针对第一版本的改动对比
将link到消息分发服务改为tcp实时连接,百万客户端连接同一台link机器,消息实时触达能力tps达到16万。
link到消息分发服务的改版是本次设计的亮点之一完全消除了mq推送的时延性,并且路由简单,几乎实时触达用户a --> link --> 消息分发 --> link --> 用户blink服务到消息分发服务集群的消息推送使用轮询负载均衡的方式,保证公平,不会导致个别机器负载过高
取消位置服务,消息分发服务集成位置服务的能力
消息分发服务本身业务简单,不需要再单独划分位置服务,增加网络io,并且消息分发服务直连link,它负责路由更加方便
存储端使用mysql,增强可维护性,消息服务使用主从读写分离方式,提高消息落库速度与检索速度,减轻数据库压力
前面有提到过使用tidb这样维护成本高,排查问题难的分布式数据库是一件很痛苦的事情,我们使用mysql更加稳定,大家对mysql的学习成本相对较低。针对消息服务使用读写分离的方式,能大大提高消息的吞吐量
实现了特性功能,消息已读未读,红包推送,商品链接推送等功能
新版消息沟通中心加入了消息已读未读,发送红包,链接推送等功能,这些功能带有一定的特性,毕竟不是所有Im都需要,可通过配置取消这些功能
消息存储写扩散改为读扩散
前面有提到,写扩散和读扩散的利弊,对于网页端我们更适合使用读扩散,只需要落一条消息,大大提高消息服务的吞吐量
增加门面服务 im-logic,用于暴露给第三方业务线接口调用
初版都是im的各个服务各自暴露接口给到外部进行调用, 我们统一使用logic服务暴露给外部调用,在logic服务针对调用可以做一些处理,这样不会影响到整体im的通用,不会增加im底层代码的复杂度。将业务逻辑与底层进行解耦
优化效果对比
业务线接入im的业务划分思考
假如我开发了一款通用的im消息沟通系统,现在有很多业务方需要接入我们,我们该如何进行业务域的清晰划分显得尤为重要,在妥协与不妥协中进行平衡
当前开源消息沟通平台存在的问题
当前开源的很多消息沟通中心其实是集成了很多的业务逻辑,要不这是一款单纯的客服系统,要不这就是一款好友聊天系统。中间的业务划分并不明确。当然,这也有好处,拿来就能用,并不需要进行二次业务封装。
如何将im设计为一款真正的高性能通用im消息沟通系统
通用的消息沟通平台只需要有通用的底层能力:
im消息中心通用能力图
以下案例假设在我已经按照上述架构设计了一版im消息沟通中心
客服系统
客服系统接入im
客服系统不光需要实现自身业务,还需要整合im的消息能力,消费im的消息,来进行场景分析,实现会话变更,信令消息推送等逻辑,客服系统内部需要根据im的底层支持能力进行相应的业务封装以及客服系统的客服用户池,c端用户池如何初始化到im的用户中心这些问题都是需要考虑进去的
内部通信
内部通信接入im
员工内部通信系统需要集成好友功能,需要根据im的用户中心封装组织架构,用户权限等功能。同时,内部通信系统需要根据im实现消息已读未读,群聊列表,会话列表拉取等功能。
总结
im的消息沟通平台是一款需要高度结合业务的中间件系统,它直接与业务打交道,跟普通的中间件有根本的区别,一款好用的im的消息沟通平台,直接取决于你的通用性,可扩展性以及系统吞吐能力。希望这篇文章能对大家开发im时候的思路有所启迪。