本人从事后台开发工作 3 年有余了,其中让我感触最深刻的一个项目,就是在两年前从架构师手上接过来的 IM 消息系统。
本文内容将从开发者的视角出发(主要是我自已的开发体会),围绕项目背景、业务需求、技术原理、开发方案等主题,一步一步的与大家一起剖析:设计一套百万消息量的小规模IM系统架构设计上需要注意的技术要点。
我们仔细观察就能发现,生活中的任何类型互联网服务都有 IM 系统的存在。
1)基础性服务类-腾讯新闻(评论消息);2)商务应用类-钉钉(审批工作流通知);3)交流娱乐类-QQ/微信(私聊群聊 &讨论组 &朋友圈);4)互联网自媒体-抖音快手(点赞打赏通知)。
在这些林林总总的互联网生态产品里,即时消息系统作为底层能力,在确保业务正常与用户体验优化上,始终扮演了至关重要的角色。
所以,现如今的互联网产品中IM电竞,即时通讯技术已经不仅限于传统IM聊天工具本身,它早已通过有形或无形的方式嵌入到了各种形式的互联网应用当中。IM技术(或者说即时通讯技术)对于很多开发者来说,确实是必不好可少的领域知识,不可或缺。
3、系统能力典型的IM系统通常需要满足四点能力:高可靠性、高可用性、实时性和有序性。
《零基础IM开发入门(一):什么是IM系统?》《零基础IM开发入门(二):什么是IM系统的实时性?》《零基础IM开发入门(三):什么是IM系统的可靠性?》《零基础IM开发入门(四):什么是IM系统的消息时序一致性?》
1)微服务:拆分为用户微服务 &消息连接服务 &消息业务服务;2)存储架构:兼容性能与资源开销,选择 reids&mysql;3)高可用:可以支撑起高并发场景,选择 Spring 提供的 websocket;4)支持多端消息同步:app 端、web 端、微信公众号、小程序消息;5)支持在线与离线消息场景。
因为你最近交到女朋友了,所以发了一条消息“我脱单了”到群里面,那么自然希望爸爸妈妈哥哥姐姐四个亲人都能收到了。
1)遍历群聊的成员并发送消息;2)查询每个成员的在线)成员不在线)成员在线的实时推送。数据分发模型如下:
3)查询每个成员的在线)在线的实时推送。以上优化后的方案,便是所谓的“写扩散”了。
4)查询每个成员的在线)在线的实时推送。二次优化后的方案,便是所谓的“读扩散”了。5.1.2)小结一下:
从公开的技术资料来看,微信和钉钉的群聊消息应该使用的是写扩散方式,具体可以参看这两篇:《微信后台团队:微信后台异步消息队列的优化升级实践分享》、《阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践》(注意“5.5 服务端存储模型优化”这一节)。
3)用户-消息队列:考虑到读扩散,每个用户都会维护自己的一份“消息列表”(1:1),如果考虑到扩容,甚至可以开辟一份消息溢出列表接收超出“消息列表”容量的消息数据(此时是 1:n);
联系人关系(主要由业务决定用户与用户之间的关系),比如说:1)某个家庭下有多少人,这个家庭群聊就有多少人;2)在 ToB 场景,在钉钉企业版里,我们往往有企业群聊这个存在。
消息-消息队列:考虑到读扩散,消息最终归属于一个或多个消息队列里,因此群聊场景它会分布在不同的消息队列里。
IM电竞
5.3 消息的存储方案对于消息存储方案,本质上只有三种方案:要么放在内存、要么放在磁盘、要么两者结合存储(据说大公司为了优化性能,活跃的消息数据都是放在内存里面的,毕竟有钱~)。
2)消息实体列表,hash(msg_id 确保唯一性);3)消息实体计数器,hash(支持群聊消息的引用次数,倒计时到零时则删除实体列表的对应消息,以节省资源)。优点是:内存操作,响应性能好
1)内存消耗巨大,eg:除非大厂,小公司的服务器的宝贵内存资源是耗不起业务的,随着业务增长,不想拓展资源,就需要手动清理数据了;2)受 redis 容灾性策略影响较大,如果 redis 宕机,直接导致数据丢失(可以使用 redis 的集群部署/哨兵机制/主从复制等手段解决)。
2)消息实体列表,转移到 mysql(表主键 id 确保唯一性);3)消息实体计数器,hash(删除这个概念,因为磁盘可用总资源远远高于内存总资源,哪怕一直存放 mysql 数据库,在业务量百万级别时也不会有大问题,如果是巨大体量业务就需要考虑分表分库处理检索数据的性能了)。优点是:
弊端是:磁盘读取操作,响应性能较差(从产品设计的角度出发,你维护的这套 IM 系统究竟是强 IM 还是弱 IM)。
2)当用户有未读消息时,由客户器主动发起请求的方式,可以及时刷新客户端状态。
1)基于拉模式实现的数据拉取请求(第一次 fetch 接口)与数据拉取确认请求(第二次 fetch 接口)是成对出现的;2)客户端二次调用 fetch 接口,需要将上次消息消费的锚点告诉服务端,服务器进而删除已读消息。
2)弊端:这种方案,因为客户端逐条 ACK 消息编号,所以会导致客户端和服务端交互次数过多。当然,客户端可以异步批量 ACK 多条消息,从而减少次数。
如果由于网络延迟,导致客户端长时间取不到数据,这时客户端会断开该次 HTTP 请求,进而忽略这次响应数据的处理,最终导致消息数据被删除而后续无法恢复。
有了 ack 机制,哪怕第一次获取消息失败,客户端还是可以继续请求消息数据,因为在 ack 确认之前,消息数据都不会删除掉。
1)消息存储:进行私聊/群聊的消息存储策略(请参看“消息存储模型”一节);
3)消息路由:用户在线时,路由消息通知包到“消息连接管理微服务”,以通知用户客户端来取消息。最后提一下消息的路由:
随着安卓和苹果系统的限制越来越严格,一般客户端的活动周期被限制的死死的,一旦客户端进程被挪到后台就立马被 kill 掉了,导致客户端保活特别难做好(这也是很多中小企业头疼的地方,毕竟只有微信或者 QQ 这种体量的一级市场 APP,手机系统愿意给他们留后门来做保活)。具体可以读一下《 P正式版即将到来:后台应用保活、消息推送的线 企业自行对接第三方厂商 PUSH 系统
在系统级别,每个硬件系统都会与对应的手机厂商保持长连接,当用户状态被检测为离线时,后台将推送报文通过 HTTP 请求,告知第三方手机厂商服务器,进而通过系统唤醒 app 的弹窗标题。
1)作为应用端,消息是否确切送达给用户侧,是未知的;推送的稳定性也取决于第三方手机厂商的服务稳定性;
3)第三方厂商随时可能升级 sdk 版本,导致没有升级 sdk 的服务器出现推送失败的情况,给 Sass 系统部署带来困难;
总之,IM里离线消息推送是个很头疼的问题(当然这里主要说是Andriod了,iOS里苹果官方的APNs就舒服多了),有兴趣好一读一下下面这些文章:
《进程永生技术终极揭密:进程被杀底层原理、APP应对被杀技巧》《保活从入门到放弃:乖乖引导用户加白名单吧(附7大机型加白示例)》《阿里IM技术分享(六):闲鱼亿级IM消息系统的离线、其它需要考虑的技术要点
1)业务数据传输安全性使用 https 访问;2)实时消息使用SSL/TLS对长连接进行加密;3)使用私有协议,不容易解析;4)内容安全性端到端加密,中间任何环节都不能解密(即发送和接收端交换互相的密钥来解密,服务器端解密不了);5)服务器端不存储消息。以上要点中:IM中的长连接安全性是比较重要且不容易处理的,因为它需要在安全性和性能上作平衡和取舍(不能光顾着安全而损失IM长连接的高吞吐性能),这方面可以参考微信团队分享的这篇《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》。
9.2 一致性IM消息一致性难题,主要是保证消息不乱序的问题。这个话题,初学者可以读读这篇《零基础IM开发入门(四):什么是IM系统的消息时序一致性?》,我就不再赘述了。
解决一致性问题的切入点有很多,最常见的是使用有序的消息唯一id,关于有序且唯一的ID生成问题,微信团队的思路就很好,可以借鉴一下《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》。另外,以下几篇关于消息有序性问题的总结也非常好,可以进行参考:《如何保证IM实时消息的“时序性”与“一致性”?》
IM电竞
然后再读读《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》、《IM消息送达保证机制实现(二):保证离线消息的可靠投递》这两篇,基本上就能对IM可靠性这个技术要点有了比较深刻的认识了。
下面这几篇实战性的总结,适合有一定IM经验的同行们学习,可以借鉴学习一下:
《从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》《阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践》
IM实时性这个技术点,就回归到了“即时通讯”这个技术的立身之本了,可以说,没有实时性,也就不存在“即时通讯”这个技术范畴了,可以见它的重要性。关于实时性这个概念,初学者可以通过《零基础IM开发入门(二):什么是IM系统的实时性?》这篇去学习一下,我就不啰嗦了,人家比我说的好。
、SSE》),但是某些低版本的浏览器可能不支持 WebSocketIM电竞,所以实际开发时,要兼容前端所能提供的能力进行方案设计。以下两篇关于实时性的同行实践性总结也不错:
《移动端IM中大规模群消息的推送如何保证效率、实时性?》《阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践》
10、我在项目实践中的体会作为研发者,有两年多的时间都在维护迭代公司的 IM 消息系统,以下是我自已的小小体会。我体会到的重点难点有以下几方面:1)业务闭环:消息是如何写入存储、消息是如何消费掉、在线消息是如何实现、离线消息是如何实现、群聊/私聊有何不一样、多端消息如何实现;
2)高可用方案之二:是对单部连接管理服务,使用 Netty 进行框架层优化,让一个服务器支撑更多的用户连接;
4)消息冷热部署:不同的地区会存在业务量差异,比如在某些经济发达的省份,IM 系统面临的压力会比较大,一些欠发达省份,服务压力会低一点,所以这块可以考虑数据的冷热部署。
11、写在最后两年前从架构师手上接过来的 IM 消息系统模块,让我逐步培养了架构思维,见贤思齐,感谢恩师im新闻。IM技术是个经久不衰的领域,但同时可直接使用的技术资产也非常匮乏,必竟传统的IM巨头们的产品通常都是私有化协议、私有化方案,很难有业界共同的方案可以直接使用(包括资料或开源代码),正是这种不通用、不准,间接导致IM技术门槛的提高。所以通常公司要搞IM的话,如果没有技术积累,就只能从零开始造轮子。
为了改变这种局面,也希望搞IM开发的同学不要闷头造车,应该多多借鉴同行的思路,同时也能积极分享自已的经验,让IM开发不再痛苦。