本文由公众号“后盾技术汇”分享,原题“基于实际,设计一个百万级别的高可用 & 高牢靠的 IM 音讯零碎”,原文链接在文末。因为原文存在较多谬误和不精确内容,有大量订正和改变。
1、引言
大家好,我是公众号“后盾技术汇”的博主“一枚少年”。
自己从事后盾开发工作 3 年无余了,其中让我感触最粗浅的一个我的项目,就是在两年前从架构师手上接过来的 IM 音讯零碎。
本文内容将从开发者的视角登程(次要是我自已的开发领会),围绕我的项目背景、业务需要、技术原理、开发计划等主题,一步一步的与大家一起分析:设计一套百万音讯量的小规模IM零碎架构设计上须要留神的技术要点。
学习交换:
- 即时通讯/推送技术开发交换5群:215477170 [举荐]
- 挪动端IM开发入门文章:《新手入门一篇就够:从零开发挪动端IM》
- 开源IM框架源码:https://github.com/JackJiang2...
(本文同步公布于:http://www.52im.net/thread-37...)
2、我的项目背景
咱们仔细观察就能发现,生存中的任何类型互联网服务都有 IM 零碎的存在。
比方:
1)基础性服务类-腾讯新闻(评论音讯);
2)商务利用类-钉钉(审批工作流通知);
3)交换娱乐类-QQ/微信(私聊群聊 &讨论组 &朋友圈);
4)互联网自媒体-抖音快手(点赞打赏告诉)。
在这些林林总总的互联网生态产品里,即时消息零碎作为底层能力,在确保业务失常与用户体验优化上,始终表演了至关重要的角色。
所以,现如今的互联网产品中,即时通讯技术曾经不仅限于传统IM聊天工具自身,它早已通过无形或有形的形式嵌入到了各种模式的互联网利用当中。IM技术(或者说即时通讯技术)对于很多开发者来说,的确是必不好可少的畛域常识,不可或缺。
3、零碎能力
典型的IM零碎通常须要满足四点能力:高可靠性、高可用性、实时性和有序性。
这几个概念我就不具体开展,如果你是IM开发入门者,能够详读上面这几篇:
《零根底IM开发入门(一):什么是IM零碎?》
《零根底IM开发入门(二):什么是IM零碎的实时性?》
《零根底IM开发入门(三):什么是IM零碎的可靠性?》
《零根底IM开发入门(四):什么是IM零碎的音讯时序一致性?》
4、架构设计
以我的这个我的项目来说,架构设设计要点次要是:
1)微服务:拆分为用户微服务 &音讯连贯服务 &音讯业务服务;
2)存储架构:兼容性能与资源开销,抉择 reids&mysql;
3)高可用:能够撑持起高并发场景,抉择 Spring 提供的 websocket;
4)反对多端音讯同步:app 端、web 端、微信公众号、小程序音讯;
5)反对在线与离线音讯场景。
业务架构图次要是这样:
技术模块分层架构大略是这样:
5、音讯存储技术要点
5.1 了解读扩散和写扩散
5.1.1)基本概念:
咱们举个例子阐明什么是读扩散,什么是写扩散:
一个群聊“相亲相爱一家人”,成员:爸爸、妈妈、哥哥、姐姐和我(共 5 人)。
因为你最近交到女朋友了,所以发了一条音讯“我脱单了”到群外面,那么天然心愿爸爸妈妈哥哥姐姐四个亲人都能收到了。
失常逻辑下,群聊音讯发送的流程应该是这样:
1)遍历群聊的成员并发送音讯;
2)查问每个成员的在线状态;
3)成员不在线的存储离线;
4)成员在线的实时推送。
数据散发模型如下:
问题在于:如果第4步产生异样,群友会失落音讯,那么会导致有家人不晓得“你脱单了”,造成催婚的严重后果。
所以优化的计划是:不论群员是否在线,都要先存储音讯。
依照下面的思路,优化后的群音讯流程如下:
1)遍历群聊的成员并发送音讯;
2)群聊所有人都存一份;
3)查问每个成员的在线状态;
4)在线的实时推送。
以上优化后的计划,便是所谓的“写扩散”了。
问题在于:每个人都存一份雷同的“你脱单了”的音讯,对磁盘和带宽造成了很大的节约(这就是写扩散的最大弊病)。
所以优化的计划是:群音讯实体存储一份,用户只存音讯 ID 索引。
于是再次优化后的发送群音讯流程如下:
1)遍历群聊的成员并发送音讯;
2)先存一份音讯实体;
3)而后群聊所有人都存一份音讯实体的 ID 援用;
4)查问每个成员的在线状态;
5)在线的实时推送。
二次优化后的计划,便是所谓的“读扩散”了。
5.1.2)小结一下:
1)读扩散:读取操作很重,写入操作很轻,资源耗费绝对小一些;
2)写扩散:读取操作很轻,写入操作很重,资源耗费绝对大一些。
从公开的技术材料来看,微信和钉钉的群聊音讯应该应用的是写扩散形式,具体能够参看这两篇:《微信后盾团队:微信后盾异步音讯队列的优化降级实际分享》、《阿里IM技术分享(四):闲鱼亿级IM音讯零碎的牢靠投递优化实际》(留神“5.5 服务端存储模型优化”这一节)。
5.2 “音讯”所关联的对象
5.2.1)音讯实体模型:
常见的音讯业务,能够形象为几个实体模型概念:用户/用户关系/用户设施/用户连贯状态/音讯/音讯队列。
在IM零碎中的实体模型关系大抵如下:
5.2.2)实体模型概念解释:
用户实体:
1)用户->用户终端设备:每个用户可能多端登录并收发音讯;
2)用户->音讯:思考到读扩散,每个用户与音讯的关系都是 1:n;
3)用户->音讯队列:思考到读扩散,每个用户都会保护本人的一份“音讯列表”(1:1),如果思考到扩容,甚至能够开拓一份音讯溢出列表接管超出“音讯列表”容量的音讯数据(此时是 1:n);
4)用户->用户连贯状态:思考到用户可能多端登录,那么 app/web 都会有对应的在线状态信息(1:n);
5)用户->联系人关系:思考到用户最终以某种业务联系到一起,组成多份联系人关系,最终造成私聊或者群聊(1:n);
联系人关系(次要由业务决定用户与用户之间的关系),比如说:
1)某个家庭下有多少人,这个家庭群聊就有多少人;
2)在 ToB 场景,在钉钉企业版里,咱们往往有企业群聊这个存在。
音讯实体:
音讯->音讯队列:思考到读扩散,音讯最终归属于一个或多个音讯队列里,因而群聊场景它会散布在不同的音讯队列里。
音讯队列实体:
音讯队列:确切说是音讯援用队列,它外面的索引元素最终指向具体的音讯实体对象。
用户连贯状态:
1)对于 app 端:网络起因导致断线,或者用户手动 kill 掉利用过程,都属于离线;
2)对于 web 端:网络起因导致浏览器断网,或者用户手动敞开标签页,都属于离线;
3)对于公众号:无奈别离离线在线;
4)对于小程序:无奈别离离线在线。
用户终端设备:
客户端个别是 Android&IOS,web 端个别是浏览器,还有其余灵便的 WebView(公众号/小程序)。
5.3 音讯的存储计划
对于音讯存储计划,实质上只有三种计划:要么放在内存、要么放在磁盘、要么两者联合存储(据说大公司为了优化性能,沉闷的音讯数据都是放在内存外面的,毕竟有钱~)。
上面别离解析次要计划的长处与弊病:
1)计划一:思考性能,数据全副放到 redis 进行存储;
2)计划二:思考资源,数据用 redis + mysql 进行存储。
5.3.1)对于计划一:redis
前提:用户 & 联系人关系,因为是业务数据,因而对立默认应用关系型数据库存储。
流程图:
解释如下:
1)用户发消息;
2)redis 创立一条实体数据 &一个实体数据计时器;
3)redis 在 B 用户的用户队列 增加实体数据援用;
4)B 用户拉取音讯(后续 5.2 会提及拉模式)。
实现计划:
1)用户队列,zset(score 确保有序性);
2)音讯实体列表,hash(msg_id 确保唯一性);
3)音讯实体计数器,hash(反对群聊音讯的援用次数,倒计时到零时则删除实体列表的对应音讯,以节俭资源)。
长处是:内存操作,响应性能好
弊病是:
1)内存耗费微小,eg:除非大厂,小公司的服务器的贵重内存资源是耗不起业务的,随着业务增长,不想拓展资源,就须要手动清理数据了;
2)受 redis 容灾性策略影响较大,如果 redis 宕机,间接导致数据失落(能够应用 redis 的集群部署/哨兵机制/主从复制等伎俩解决)。
5.3.2)计划二:redis+mysql
前提:用户 & 联系人关系,因为是业务数据,因而对立默认应用关系型数据库存储。
流程图:
解释如下:
1)用户发消息;
2)mysql 创立一条实体数据;
3)redis 在 B 用户的用户队列 增加实体数据援用;
4)B 用户拉取音讯(下文会提及拉模式)。
实现计划:
1)用户队列,zset(score 确保有序性);
2)音讯实体列表,转移到 mysql(表主键 id 确保唯一性);
3)音讯实体计数器,hash(删除这个概念,因为磁盘可用总资源远远高于内存总资源,哪怕始终寄存 mysql 数据库,在业务量百万级别时也不会有大问题,如果是微小体量业务就须要思考分表分库解决检索数据的性能了)。
长处是:
1)抽离了数据量最大的音讯实体,大大节俭了内存资源;
2)磁盘资源易于拓展 ,便宜实用。
弊病是:磁盘读取操作,响应性能较差(从产品设计的角度登程,你保护的这套 IM 零碎到底是强 IM 还是弱 IM)。
6、音讯的生产模式
6.1 拉模式
选用音讯拉模式的起因:
1)因为用户数量太多(观察者),服务器无奈一一监控客户端的状态,因而音讯模块的数据交互应用拉模式,能够节约服务器资源;
2)当用户有未读音讯时,由客户器被动发动申请的形式,能够及时刷新客户端状态。
6.2 ack 机制
技术原理:
1)基于拉模式实现的数据拉取申请(第一次 fetch 接口)与数据拉取确认申请(第二次 fetch 接口)是成对呈现的;
2)客户端二次调用 fetch 接口,须要将上次音讯生产的锚点通知服务端,服务器进而删除已读音讯。
申请模型原理图如下:
实现计划1:基于每一条音讯编号 ACK:
1)实现:客户端在接管到音讯之后,发送 ACK 音讯编号给服务端,告知曾经收到该音讯。服务端在收到 ACK 音讯编号的时候,标记该音讯曾经发送胜利;
2)弊病:这种计划,因为客户端逐条 ACK 音讯编号,所以会导致客户端和服务端交互次数过多。当然,客户端能够异步批量 ACK 多条音讯,从而缩小次数。
实现计划2:基于滑动窗口 ACK:
1)客户端在接管到音讯编号之后,和本地的音讯编号进行比对:
- 如果比本地的小,阐明该音讯曾经收到,疏忽不解决;
- 如果比本地的大,应用本地的音讯编号,向服务端拉取大于本地的音讯编号的音讯列表,即增量音讯列表。
- 拉取实现后,更新音讯列表中最大的音讯编号为新的本地的音讯编号;
2)服务端在收到 ack 音讯时,进行批量标记已读或者删除。
这种形式,在业务被称为推拉联合的计划,在分布式音讯队列、配置核心、注册核心实现实时的数据同步,常常被采纳。
6.3 基于ack 机制的益处
第一次获取音讯实现之后,如果没有 ack 机制,流程是:
1)服务器删除已读音讯数据;
2)服务端把数据包响应给客户端。
如果因为网络提早,导致客户端长时间取不到数据,这时客户端会断开该次 HTTP 申请,进而疏忽这次响应数据的解决,最终导致音讯数据被删除而后续无奈复原。
有了 ack 机制,哪怕第一次获取音讯失败,客户端还是能够持续申请音讯数据,因为在 ack 确认之前,音讯数据都不会删除掉。
7、微服务设计
一般来说 IM 微服务,能拆分为根底的三个微服务:
1)用户服务;
2)业务服务;
3)连贯治理服务。
参考架构图:
他们分工合作如下。
用户微服务(用户设施的登录 & 登出):
1)设施号存库;
2)连贯状态更新;
3)其余登录端用户踢出等。
连贯治理微服务:
1)状态保留:保留用户设施长连贯对象;
2)剔除有效连贯:轮训已有长连贯对象状态,超时删除对象;
3)承受客户端的心跳包:刷新长连贯对象的状态。
音讯业务微服务:
1)音讯存储:进行私聊/群聊的音讯存储策略(请参看“音讯存储模型”一节);
2)音讯生产:进行音讯获取响应与 ack 确认删除(请参看“音讯生产模式”一节);
3)音讯路由:用户在线时,路由音讯告诉包到“音讯连贯治理微服务”,以告诉用户客户端来取音讯。
最初提一下音讯的路由:
微服务之间也有通信伎俩,比方业务服务到连贯治理服务,两者之间能够通过 RPC 实现实时音讯的路由告诉。
8、离线音讯推送
离线推送计划上,大家个别都会思考采纳两种计划:
1)企业自研后盾离线 PUSH 零碎;
2)企业自行对接第三方手机厂商 PUSH 零碎。
8.1 企业自研后盾离线 PUSH 零碎
技术原理:
在利用级别,客户端与后盾离线 PUSH 零碎放弃长连贯,当用户状态被检测为离线时,通过这个长连贯告知客户端“有新音讯”,进而唤醒手机弹窗题目。
弊病就是:
随着安卓和苹果零碎的限度越来越严格,个别客户端的流动周期被限度的死死的,一旦客户端过程被挪到后盾就立马被 kill 掉了,导致客户端保活特地难做好(这也是很多中小企业头疼的中央,毕竟只有微信或者 QQ 这种体量的一级市场 APP,手机零碎违心给他们留后门来做保活)。具体能够读一下《Android P正式版行将到来:后盾利用保活、音讯推送的真正噩梦》这篇。
8.2 企业自行对接第三方厂商 PUSH 零碎
技术原理:
在零碎级别,每个硬件零碎都会与对应的手机厂商放弃长连贯,当用户状态被检测为离线时,后盾将推送报文通过 HTTP 申请,告知第三方手机厂商服务器,进而通过零碎唤醒 app 的弹窗题目。
弊病就是:
1)作为利用端,音讯是否确切送达给用户侧,是未知的;推送的稳定性也取决于第三方手机厂商的服务稳定性;
2)额定进行 sdk 的对接工作,减少了工作量;
3)第三方厂商随时可能降级 sdk 版本,导致没有降级 sdk 的服务器呈现推送失败的状况,给 Sass 零碎部署带来艰难;
4)推送证书配置也要思考到保护老本。
总之,IM里离线音讯推送是个很头疼的问题(当然这里次要说是Andriod了,iOS里苹果官网的APNs就难受多了),有趣味好一读一下上面这些文章:
《全面盘点以后Android后盾保活计划的实在运行成果(截止2019年前)》
《融云技术分享:融云安卓端IM产品的网络链路保活技术实际》
《2020年了,Android后盾保活还有戏吗?看我如何优雅的实现!》
《史上最强Android保活思路:深刻分析腾讯TIM的过程永生技术》
《Android过程永生技术终极揭密:过程被杀底层原理、APP应答被杀技巧》
《Android保活从入门到放弃:乖乖疏导用户加白名单吧(附7大机型加白示例)》
《阿里IM技术分享(六):闲鱼亿级IM音讯零碎的离线推送达到率优化》
9、其它须要思考的技术要点
9.1 安全性
对于IM安全性,我集体的领会是这样:
1)业务数据传输安全性应用 https 拜访;
2)实时音讯应用SSL/TLS对长连贯进行加密;
3)应用公有协定,不容易解析;
4)内容安全性端到端加密,两头任何环节都不能解密(即发送和接收端替换相互的密钥来解密,服务器端解密不了);
5)服务器端不存储音讯。
以上要点中:IM中的长连贯安全性是比拟重要且不容易解决的,因为它须要在安全性和性能上作均衡和取舍(不能光顾着平安而损失IM长连贯的高吞吐性能),这方面能够参考微信团队分享的这篇《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》。
另外:更高安全性的场景能够思考组合加密计划,详情能够参考《探讨组合加密算法在IM中的利用》。
9.2 一致性
IM音讯一致性难题,次要是保障音讯不乱序的问题。这个话题,初学者能够读读这篇《零根底IM开发入门(四):什么是IM零碎的音讯时序一致性?》,我就不再赘述了。
解决一致性问题的切入点有很多,最常见的是应用有序的音讯惟一id,对于有序且惟一的ID生成问题,微信团队的思路就很好,能够借鉴一下《微信技术分享:微信的海量IM聊天音讯序列号生成实际(算法原理篇)》。
另外,以下几篇对于音讯有序性问题的总结也十分好,能够进行参考:
《如何保障IM实时音讯的“时序性”与“一致性”?》
《一个低成本确保IM音讯时序的办法探讨》
《一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等》
9.3 可靠性
IM里所谓的可靠性,说直白一点就是保障音讯不失落,这看似天经地义、稀松平时的技术点,在IM零碎中又是另一个很大的话题,鉴于自己程度无限,就不班门弄斧,IM初学者能够能过《零根底IM开发入门(三):什么是IM零碎的可靠性?》这篇来了解可靠性这个概念。
而后再读读《IM音讯送达保障机制实现(一):保障在线实时音讯的牢靠投递》、《IM音讯送达保障机制实现(二):保障离线音讯的牢靠投递》这两篇,基本上就能对IM可靠性这个技术要点有了比拟粗浅的意识了。
上面这几篇实战性的总结,适宜有肯定IM教训的同行们学习,能够借鉴学习一下:
《融云技术分享:全面揭秘亿级IM音讯的牢靠投递机制》
《IM开发干货分享:如何优雅的实现大量离线音讯的牢靠投递》
《从客户端的角度来谈谈挪动端IM的音讯可靠性和送达机制》
《阿里IM技术分享(四):闲鱼亿级IM音讯零碎的牢靠投递优化实际》
9.4 实时性
IM实时性这个技术点,就回归到了“即时通讯”这个技术的立身之本了,能够说,没有实时性,也就不存在“即时通讯”这个技术领域了,能够见它的重要性。对于实时性这个概念,初学者能够通过《零根底IM开发入门(二):什么是IM零碎的实时性?》这篇去学习一下,我就不啰嗦了,人家比我说的好。
笔者公司的我的项目里实时通信用计划都是采纳 WebSocket(如果你不理解WebSocket,能够读一下《WebSocket从入门到精通,半小时就够!》,以及《搞懂古代Web端即时通讯技术一文就够:WebSocket、socket.io、SSE》),然而某些低版本的浏览器可能不反对 WebSocket,所以理论开发时,要兼容前端所能提供的能力进行方案设计。
以下两篇对于实时性的同行实践性总结也不错:
《挪动端IM中大规模群音讯的推送如何保障效率、实时性?》
《阿里IM技术分享(五):闲鱼亿级IM音讯零碎的及时性优化实际》
10、我在我的项目实际中的领会
作为研发者,有两年多的工夫都在保护迭代公司的 IM 音讯零碎,以下是我自已的小小领会。
我领会到的重点难点有以下几方面:
1)业务闭环:音讯是如何写入存储、音讯是如何生产掉、在线音讯是如何实现、离线音讯是如何实现、群聊/私聊有何不一样、多端音讯如何实现;
2)解 Bug 填坑:在线音讯收不到,第三方推送证书如何配置;
3)代码优化:单体架构拆分微服务;
4)存储优化:1.0 版本的 redis 存储到 2.0 版本的 redis+mysql;
5)性能优化:未读揭示等接口性能优化。
我的项目还存在可优化的中央:
1)高可用计划之一:是部署多部连贯治理服务器,以撑持更多的用户连贯;
2)高可用计划之二:是对单部连贯治理服务,应用 Netty 进行框架层优化,让一个服务器撑持更多的用户连贯;
3)音讯量剧增时:能够思考对音讯存储作进一步优化;
4)音讯冷热部署:不同的地区会存在业务量差别,比方在某些经济发达的省份,IM 零碎面临的压力会比拟大,一些欠发达省份,服务压力会低一点,所以这块能够思考数据的冷热部署。
11、写在最初
两年前从架构师手上接过来的 IM 音讯零碎模块,让我逐渐造就了架构思维,见贤思齐,感激恩师。
IM技术是个经久不衰的畛域,但同时可间接应用的技术资产也十分匮乏,必竟传统的IM巨头们的产品通常都是私有化协定、私有化计划,很难有业界独特的计划能够间接应用(包含材料或开源代码),正是这种不通用、不准,间接导致IM技术门槛的进步。所以通常公司要搞IM的话,如果没有技术积攒,就只能从零开始造轮子。
为了扭转这种场面,也心愿搞IM开发的同学不要闷头造车,应该多多借鉴同行的思路,同时也能踊跃分享自已的教训,让IM开发不再苦楚。
以上抛砖引玉,欢送留言探讨,一起提高。
12、参考资料
[1] 新手入门一篇就够:从零开发挪动端IM
[2] 为何基于TCP协定的挪动端IM依然须要心跳保活机制?
[3] Android P正式版行将到来:后盾利用保活、音讯推送的真正噩梦
[4] WebSocket从入门到精通,半小时就够!
[5] 搞懂古代Web端即时通讯技术一文就够:WebSocket、socket.io、SSE
[6] 一套海量在线用户的挪动端IM架构设计实际分享(含具体图文)
[7] 一套原创分布式即时通讯(IM)零碎实践架构计划
[8] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实际
[9] 微信技术分享:微信的海量IM聊天音讯序列号生成实际(算法原理篇)
[10] 阿里IM技术分享(四):闲鱼亿级IM音讯零碎的牢靠投递优化实际
[11] 阿里IM技术分享(五):闲鱼亿级IM音讯零碎的及时性优化实际
[12] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
[13] 从老手到专家:如何设计一套亿级音讯量的分布式IM零碎
[14] 企业微信的IM架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等
[15] 融云技术分享:全面揭秘亿级IM音讯的牢靠投递机制
[16] 即时通讯平安篇(六):非对称加密技术的原理与利用实际
[17] 通俗易懂:一篇把握即时通讯的音讯传输平安原理
[18] 微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解
[19] 零根底IM开发入门(二):什么是IM零碎的实时性?
[20] 零根底IM开发入门(三):什么是IM零碎的可靠性?
[21] 零根底IM开发入门(四):什么是IM零碎的音讯时序一致性?
本文已同步公布于“即时通讯技术圈”公众号。
同步公布链接是:http://www.52im.net/thread-37...