企业微信作为一款办公协同的产品,聊天消息收发是最基础的功能。消息系统的稳定性、可靠性、安全性尤其重要。
消息系统的构建与设计的过程中,面临着较多的难点。而且针对toB场景的消息系统,需要支持更为复杂的业务场景。
针对toB场景的特有业务有:
1)消息鉴权:关系类型有群关系、同企业同事关系、好友关系、集团企业关系、圈子企业关系。收发消息双方需存在至少一种关系才允许发消息;
2)回执消息:每条消息都需记录已读和未读人员列表,涉及频繁的状态读写操作;
3)撤回消息:支持24小时的有效期撤回动作;
4)消息存储:云端存储时间跨度长,最长可支持180天消息存储,数百TB用户消息需优化,减少机器成本;
5)万人群聊:群人数上限可支持10000人,一条群消息就像一次小型的DDoS攻击;
6)微信互通:两个异构的im系统直接打通,可靠性和一致性尤其重要。
整体架构分层如下。
1)接入层:统一入口,接收客户端的请求,根据类型转发到对应的CGI层。客户端可以通过长连或者短连连接wwproxy。活跃的客户端,优先用长连接发起请求,如果长连失败,则选用短连重试。
2)CGI层:http服务,接收wwproxy的数据包,校验用户的session状态,并用后台派发的秘钥去解包,如解密失败则拒绝请求。解密成功,则把明文包体转发到后端逻辑层对应的svr。
3)逻辑层:大量的微服务和异步处理服务,使用自研的hikit rpc框架,svr之间使用tcp短连进行通信。进行数据整合和逻辑处理。和外部系统的通信,通过http协议,包括微信互通、手机厂商的推送平台等。
4)存储层:消息存储是采用的是基于levelDB模型开发msgkv。SeqSvr是序列号生成器,保证派发的seq单调递增不回退,用于消息的收发协议。
发送方请求后台,把消息写入到接收方的存储,然后push通知接收方。接受方收到push,主动上来后台收消息。
不重、不丢、及时触达,这三个是消息系统的核心指标:
1)实时触达:客户端通过与后台建立长连接,保证消息push的实时触达;
2)及时通知:如果客户端长连接不在,进程被kill了,利用手机厂商的推送平台,推送通知,或者直接拉起进程进行收消息;
3)消息可达:假如遇到消息洪峰,后台的push滞后,客户端有轮训机制进行兜底,保证消息可达;
4)消息防丢:为了防止消息丢失,只要后台逻辑层接收到请求,保证消息写到接收方的存储,失败则重试。如果请求在CGI层就失败,则返回给客户端出消息红点;
5)消息排重:客户端在弱网络的场景下,有可能请求已经成功写入存储,回包超时,导致客户端重试发起相同的消息,那么就造成消息重复。为了避免这种情况发生,每条消息都会生成唯一的appinfo,后台通过建立索引进行排重,相同的消息直接返回成功,保证存储只有一条。
扩散读
即:每条消息只存一份,群聊成员都读取同一份数据。
优点:节省存储容量。
缺点:
① 每个用户需存储会话列表,通过会话id去拉取会话消息;
② 收消息的协议复杂,每个会话都需要增量同步消息,则每个会话都需要维护一个序列号。
扩散写
即:每条消息存多份,每个群聊成员在自己的存储都有一份。
优点:
① 只需要通过一个序列号就可以增量同步所有消息,收消息协议简单;
② 读取速度快,前端体验好;
③ 满足更多ToB的业务场景:回执消息、云端删除。
同一条消息,在每个人的视角会有不同的表现。例如:回执消息,发送方能看到已读未读列表,接受方只能看到是否已读的状态。云端删除某条群消息,在自己的消息列表消失,其他人还是可见。
缺点:存储容量的增加。
企业微信采用了扩散写的方式,消息收发简单稳定。存储容量的增加,可以通过冷热分离的方案解决,冷数据存到廉价的SATA盘,扩散读体验稍差,协议设计也相对复杂些。
1)每个用户只有一条独立的消息流。同一条消息多副本存在于每个用户的消息流中;
2)每条消息有一个seq,在同个用户的消息流中,seq是单调递增的;
3)客户端保存消息列表中最大seq,说明客户端已经拥有比该seq小的所有消息。若客户端被push有新消息到达,则用该seq向后台请求增量数据,后台把比此seq大的消息数据返回。
高峰期系统压力大,偶发的网络波动或者机器过载,都有可能导致大量的系统失败。im系统对及时性要求比较高,没办法进行削峰处理。那么引入一些柔性的策略,保证系统的稳定性和可用性非常有必要。
具体的做法就是启动过载保护策略:当svr已经达到最大处理能力的时候,说明处于一个过载的状态,服务能力会随着负载的增高而急剧下降。如果svr过载,则拒绝掉部分正常请求,防止机器被压垮,依然能对外服务。通过统计svr的被调耗时情况、worker使用情况等,判定是否处于过载状态。过载保护策略在请求高峰期间起到了保护系统的作用,防止雪崩效应。
解决方案思路就是:尽管失败,也返回前端成功,后台保证最终成功。
文章图片
为了保证消息系统的可用性,规避高峰期系统出现过载失败导致前端出红点,做了很多优化。
具体策略如下:
1)逻辑层hold住失败请求,返回前端成功,不出红点,后端异步重试,直至成功;
2)为了防止在系统出现大面积故障的时候,重试请求压满队列,只hold住半小时的失败请求,半小时后新来的请求则直接返回前端失败;
3)为了避免重试加剧系统过载,指数时间延迟重试;
4)复杂的消息鉴权(好友关系,企业关系,集团关系,圈子关系),耗时严重,后台波动容易造成失败。如果并非明确鉴权不通过,则幂等重试;
5)为了防止作恶请求,限制单个用户和单个企业的请求并发数。例如,单个用户的消耗worker数超过20%,则直接丢弃该用户的请求,不重试。
优化后,后台的波动,前端基本没有感知。
系统稳定性设计2:系统解耦
由于产品形态的原因,企业微信的消息系统,会依赖很多外部模块,甚至外部系统。
例如:与微信消息互通,发送消息的权限需要放到ImUnion去做判定,ImUnion是一个外部系统,调用耗时较长。
再如:金融版的消息审计功能,需要把消息同步到审计模块,增加rpc调用。
再如:客户服务的单聊群聊消息,需要把消息同步到crm模块,增加rpc调用。为了避免外部系统或者外部模块出现故障,拖累消息系统,导致耗时增加,则需要系统解耦。
我们的方案:与外部系统的交互,全设计成异步化。即时通讯聊天软件开发可以咨询蔚可云。
思考点:需要同步返回结果的请求,如何设计成异步化?
例如:群聊互通消息需经过ImUnion鉴权返回结果,前端用于展示消息是否成功发送。先让客户端成功,异步失败,则回调客户端使得出红点。
文章图片
如果是非主流程,则异步重试保证成功,主流程不受影响,如消息审计同步功能。那么,只需要保证内部系统的稳定,发消息的主流程就可以不受影响。
系统稳定性设计3:业务隔离
企业微信的消息类型有多种:
1)单聊群聊:基础聊天,优先级高;
2)api 消息:企业通过api接口下发的消息,有频率限制,优先级中;
3)应用消息:系统应用下发的消息,例如公告,有频率限制,优先级中;
4)控制消息:不可见的消息。例如群信息变更,会下发控制消息通知群成员,优先级低。
群聊按群人数,又分成3类:
1)普通群:小于100人的群,优先级高;
2)大 群:小于2000人的群,优先级中;
3)万人群:优先级低。
业务繁多:如果不加以隔离,那么其中一个业务的波动有可能引起整个消息系统的瘫痪。
重中之重:需要保证核心链路的稳定,就是企业内部的单聊和100人以下群聊,因为这个业务是最基础的,也是最敏感的,稍有问题,投诉量巨大。
【im即时通讯开发(消息模型、万人群、已读回执、消息撤回功能)】其余的业务:互相隔离,减少牵连。按照优先级和重要程度进行隔离,对应的并发度也做了调整,尽量保证核心链路的稳定性。