本文由钉钉技术专家尹启绣分享,有订正和从新排版。
1、引言
短短的几年工夫,钉钉便迅速成为一款国民级利用,倒退速度堪称迅猛。IM 作为钉钉最外围的性能,每天须要反对海量企业用户的沟通,同时还通过 PaaS 模式为淘宝、高德等 App 提供根底的即时通讯能力,是日均千亿级音讯量的 IM 平台。在钉钉的 IM 中,咱们通过 RocketMQ 实现了零碎解耦、异步削峰填谷,还通过定时音讯实现分布式定时工作等高级个性。同时与 RocketMQ 深刻共创,一直优化解决了很多 RocketMQ 自身的问题,并且孵化出 POP 生产模式等新个性,使 RocketMQ 可能完满反对对性能稳定性和时延要求十分高的 IM 零碎。本文将为你分享这些内容。
学习交换:
- 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
-
开源 IM 框架源码:https://github.com/JackJiang2…(备用地址点此)
(本文已同步公布于:http://www.52im.net/thread-41…)2、系列文章
本文是系列文章的第 9 篇,总目录如下:
《阿里 IM 技术分享(一):企业级 IM 王者——钉钉在后端架构上的过人之处》
《阿里 IM 技术分享(二):闲鱼 IM 基于 Flutter 的挪动端跨端革新实际》
《阿里 IM 技术分享(三):闲鱼亿级 IM 音讯零碎的架构演进之路》
《阿里 IM 技术分享(四):闲鱼亿级 IM 音讯零碎的牢靠投递优化实际》
《阿里 IM 技术分享(五):闲鱼亿级 IM 音讯零碎的及时性优化实际》
《阿里 IM 技术分享(六):闲鱼亿级 IM 音讯零碎的离线推送达到率优化》
《阿里 IM 技术分享(七):闲鱼 IM 的在线、离线聊天数据同步机制优化实际》
《阿里 IM 技术分享(八):深度解密钉钉即时消息服务 DTIM 的技术设计》
《阿里 IM 技术分享(九):深度揭密 RocketMQ 在钉钉 IM 零碎中的利用实际》(* 本文)3、钉钉 IM 面临的微小技术挑战
3.1 概述钉钉作为企业级 IM 领先者,面临着微小的技术挑战。市面上 DAU 过亿的 App 里,只有钉钉是 2B 产品,咱们不仅须要和其余 2C 产品一样,反对海量用户的低时延、高并发、高性能、高可用,还需保障企业级用户在应用钉钉时可能晋升沟通协同效率。下图是概括的是钉钉的次要能力:
3.2 技术挑战 1:ToB 与 ToC 的差别
作为企业级利用,须要保障帮忙用户晋升沟通体验。ToB 的工作沟通和 ToC 的场景生存沟通存在较大差别,ToC 的 IM 产品比方微信,在有残缺的关系链后,只需满足大部分用户需要即可。然而微信的很多体验其实并不敌对:比方聊天音讯中的视频图片在固定工夫内没有关上则会无奈下载,卸载重装之后聊天记录全副失落。而 ToB 场景下:聊天记录是十分重要的内容,钉钉为保障用户音讯不失落,提供了多端同步和音讯云端存储的能力,用户任意换端都能查看残缺的聊天记录。在工作过程中,大量会议是工作效率杀手,钉钉还提供了已读、Ding 等效率套件,为工作沟通提供新选项。
3.3 技术挑战 2:平安要求高
在 ToB 的工作场景下,用户对信息安全要求十分高,信息安全是企业的生命线。钉钉提供了人和组织架构买通的工作群,用户来到组织后主动退出企业工作群,这样就很好地保障了企业信息的平安。同时,在曾经反对的全链路加密能力上提供了三方加密能力,能够最大水平保障企业用户的信息安全性。
3.4 技术挑战 3:稳定性要求高
企业用户对稳定性的要求也十分高,如果钉钉呈现故障,深度应用钉钉的企业都会受到微小影响。因而,钉钉 IM 零碎在稳定性上也做了十分深刻的建设,架构上对依赖和流量做了深刻治理,外围能力所有依赖都为双倍。比方尽管 RocketMQ 曾经十分稳固,也没有产生过故障,然而对 RocketMQ 可能呈现故障的产品仍然做了很好的爱护,应用 RocketMQ 定时音讯和沉积能力做热点治理和流量防护,让零碎面对大规模流量时能从容应对,并且建设了异地多活和可弹性扩缩容能力,疫情期间很好地保障了学生们的在线课堂。在稳定性机制上,常态化容灾演练、突袭演练、自动化衰弱巡检等也能很好地保障线上稳定性。比方波浪式流量就是在做断网演练时发现。
3.5 技术挑战 4:业务多样性
针对不同行业的业务多样性,还要尽可能地满足用户的通用性需要,比方万人群、全员群等,目前钉钉曾经做到可能反对 10 万人级别的群。更多的业务需要将依赖于咱们形象出的通用凋谢能力,将 IM 能力尽可能地凋谢给企业和三方 ISV,使得不同状态的业务都能在钉钉平台上失去满足。
4、音讯队列在钉钉 IM 零碎中的重要作用
4.1 概述在如此丰盛的企业级能力下,钉钉 IM 要与微信等 ToC 产品一样,反对亿级用户低时延沟通,零碎架构须要具备高并发、高性能、高可用的能力,挑战十分之大。IM 自身是异步化沟通零碎,与散会或者电话沟通相比,让沟通单方异步解决音讯可能缩小打断次数,晋升沟通效率。这种异步的个性和音讯队列的能力很符合,音讯队列能够很好地帮忙 IM 实现异步化解耦、失败重试、削峰填谷等能力。这里,咱们以钉钉 IM 零碎最外围的发消息和已读链路简化流程(如下图所示),来具体阐明音讯队列在零碎里的重要作用。
4.2 发消息链路钉钉 IM 零碎的发消息链路流程如下:1)处于登录状态的钉钉用户发送一条音讯时,首先会将申请发送到 receiver 利用;2)为保障发消息体验和成功率,receiver 利用只做这条音讯是否发送的校验,其余如音讯入库、接收者推送等都交由上游利用实现;3)校验实现之后将音讯投递给音讯队列,胜利后即可返回给用户;4)音讯发送胜利,processor 会从音讯队列里订阅到这条音讯,并对音讯进行入库解决,再通过音讯队列将音讯交给同步服务 syncserver 做解决,将音讯同步给在线接收者。
上述过程中,对于不在线的用户:能够通过音讯队列将音讯推给离线 push 零碎。离线 push 零碎能够对接接苹果、华为、小米等推送零碎进行离线推送。用户发消息过程中的每一步,失败后都可通过音讯队列进行重试解决。如 processor 入库失败,可将音讯打回音讯队列,持续盘旋解决,达到最终统一。同时,能够在订阅的过程中对生产限速,防止线上突发峰值给零碎带来灾难性的结果。4.3 音讯已读链路钉钉 IM 零碎的音讯已读链路流程如下:1)用户对一条音讯做读操作后,会发送申请到已读服务;2)已读服务收到申请后,间接将申请放到音讯队列进行异步解决,同时能够达到削峰填谷的目标;3)已读服务解决完之后,将已读事件推给同步服务,让同步服务将已读事件推送给音讯发送者。从下面两个链路能够看出,音讯队列是 IM 零碎里十分重要的组成部分。
5、钉钉 IM 抉择 RocketMQ 的起因
阿里外部曾有 notify、RocketMQ 两套利用消息中间件,也有其余基于 MQTT 协定实现的音讯队列,最终都被 RocketMQ 对立。IM 系统对音讯队列有如下几个根本要求:1)解耦和削峰填谷(这是音讯队列的根底能力);2)高性能、低时延;3)高可用性。对于第 3)点:要求音讯队列的高可用性方面不仅包含零碎可用性,也包含数据可用性,要求写入音讯队列时音讯不失落(钉钉 IM 对音讯的保障级别是一条都不丢)。
RocketMQ 通过屡次双 11 考验,其沉积性能、低时延、高可用已成为业届标杆,完全符合对音讯队列的要求。同时它的其余个性也十分丰盛,如定时音讯、事务音讯,可能以极低的老本实现分布式定时工作,音讯可重放和死信队列提供了后悔药的能力,比方线上零碎呈现 bug,很多音讯没有正确处理,能够通过重置位点、从新生产的形式,勘误之前的错误处理。另外:音讯队列的应用场景十分丰盛,RocketMQ 的扩大能力能够在音讯发送和生产上做切面解决,实现通用性的扩大封装,大大降低开发工作量。Tag & SQL 过滤能让上游针对性地订阅定业务须要的音讯,无需订阅整个 topic 里的所有音讯,大幅升高上游零碎的订阅压力。RocketMQ 至今从未产生故障,集群峰值 TPS 可达 300w/s,从生产到生产时延可能保障在 10 ms 以内,反对 30 亿条音讯沉积,外围指标数据体现抢眼,性能异样优良。
6、RocketMQ 的音讯必达 3 重保险
如上图所示,发消息流程中,很重要的一步是 receiver 利用做完音讯是否发送的校验之后,通过 RocketMQ 将音讯投递给 processor 做音讯入库解决。投递过程中,将提供三重保险,以保障音讯发送十拿九稳。第一重保险:receiver 将音讯写进 RocketMQ 时,RocketMQ SDK 默认会重试五次(每次尝试不同的 broker,保障了音讯写失败的概率十分小)。第二重保险:写入 RocketMQ 失败的状况下,会尝试以 RPC 模式将音讯投递给 processor。第三重保险:如果 RPC 模式也失败,会尝试将本地 redoLog 通过 Crontab 工作定时将音讯回放到 RocketMQ 外面。此外,如何在零碎异样的状况下做到音讯最终统一?Processor 收到上游投递的音讯时,会尝试对音讯做入库解决。即便入库失败,仍然会将音讯投给同步服务,将音讯下发,保障实时音讯收发失常。异常情况时会将音讯从新投递到异样 topic 进行重试,投递过程中通过设置 RocketMQ 定时音讯做退却解决,对异样 topic 做限速生产。重试写不同的 topic 是为了与失常流量隔离,优先解决失常流量,避免因为异样流量生产而导致真正的线上音讯解决被提早。另外:Rocket MQ 的一个 broker 默认只有一个 Retry 音讯队列,如果生产失败量特地大的状况下,会导致上游负载不均,某些机器打死。此外:如果零碎继续产生异样,则会一直地进行盘旋重试,如果不做限速解决,线上容易呈现流量叠加,导致整个零碎雪崩。
7、RocketMQ 的独门绝技——分布式定时工作
在几千人的群里发一条音讯,假如有 1/4 的成员同时开着聊天窗口,如果不对服务端已读服务和客户端须要更新的已读数做合并解决,更新的 QPS 会高达到 1000/s。钉钉可能反对十几万人的超大群,超大群的沉闷对服务端和客户端都会带来很大冲击,而实际上用户的需要只需实现秒级更新。针对以上场景:能够利用 RocketMQ 的定时音讯能力实现分布式定时工作。以已读流程为例(如下图所示),用户发动申请时,会将申请放入集中式申请队列,再通过 RocketMQ 定时音讯生成定时工作,比方 5 秒后批量解决。5 秒之后,RocketMQ 订阅到工作触发音讯,将队列外面所有申请都取出解决。
▲ 用 RocketMQ 实现分布式定时工作的流程原理咱们形象了一个分布式定时工作的组件,提供了很多其余实时性可达秒级的性能,如万人群的群状态更新、音讯扩大更新都接入了此组件。通过组件的定时合并解决,大幅升高零碎压力。如上图(左边局部),在一些大群沉闷的工夫点胜利地让流量降落并放弃安稳状态。
8、钉钉 IM 应用 RocketMQ 遇到的技术问题
8.1 概述 RocketMQ 的生产端策略如下:1)生产者获取到对应 topic 所有 broker 和 Queue 列表,而后轮询写入音讯;2)消费者端也会获取到 topic 所有 broker 和 Queue 列表;3)还须要要从 broker 中获取所有消费者 IP 列表进行排序(依照配置负载平衡,如哈希、一次性哈希等策略计算出本人应该订阅哪些 Queue)。
上图中:ConsumerGroupA 的 Consumer1 被调配到 MessageQueue0 和 MessageQueue1,则它订阅 MessageQueue0 和 MessageQueue1。在 RocketMQ 的应用过程中,咱们面临了诸多问题,上面咱们来逐个分享。8.2 问题 1:波浪式流量咱们发现订阅音讯集群滚动时,CPU 出现波浪式飙升。通过深刻排查发现,断网演练后进行网络复原时,大量 producer 同时复原工作,同时从第一个 broker 的第一个 Queue 开始写入音讯,生产音讯波浪式写入 RocketMQ,进而导致消费者端呈现波浪式流量。最终,咱们分割 RocketMQ 开发人员,调整了生产策略,每次生产者发现 broker 数量或状态发生变化时,都会随机选取一个初始 Queue 写入音讯,以此解决问题。另一个导致波浪式流量的问题是配置问题。排查线上问题时,从 broker 视角看,每个 broker 的音讯量都是均匀的,但 consumer 之间流量相差特地大。最终通过在 producer 侧尝试抓包得以定位到问题,是因为 producer 写入音讯时超时率偏高。梳理配置后发现,是因为 producer 写入音讯时配置超时太短,Rocket MQ 在写音讯时会尝试屡次,比方第一个 broker 写入失败后,将间接跳到下一个 broker 的第一个 Queue,导致每个 broker 的第一个 Queue 音讯量特地大,而靠后的 partition 简直没有音讯。8.3 问题 2:负载平衡维度太粗负载平衡只能到 Queue 维度,导致须要不断地关注 Queue 数量。比方线上流量增长过快,须要进行扩容,而扩容后发现机器数大于 Queue 数量,导致无论怎么扩容都无奈分担线上流量,最终只能分割 RocketMQ 运维人员调高 Queue 数量来解决。尽管调高 Queue 数量能解决机器无奈订阅的问题,但因为负载平衡策略只到 Queue 维度,负载始终无奈平衡。从下图能够看到,consumer 1 订阅了两个 Queue 而 consumer 2 只订阅了一个 Queue。
8.4 问题 3:单机夯死导致音讯沉积单机夯死导致音讯沉积,这也是负载平衡只能到 Queue 维度带来的副作用。比方 Broker A 的 Queue 由 consumer 1 订阅,呈现宿主机磁盘 IO 夯死但与 broker 之间的心跳仍然失常,导致 Queue 音讯长时间无奈订阅进而影响用户接管音讯。最终只能通过手动染指将对应机器下线来解决。8.5 问题 4:rebalanceRocket MQ 的负载平衡由 client 本人计算,导致有机器异样或公布时,整个集群状态不稳固,时常会呈现某些 Queue 有多个 consumer 订阅,而某些 Queue 在几十秒内没有 consumer 订阅的状况。因此导致线上公布的时候,呈现音讯乱序或对方已回音讯但显示未读的状况。8.6 问题 5:C++ SDK 能力缺失钉钉 IM 的外围解决模块 Receiver、processor 等利用都是通过 C++ 实现,而 RocketMQ 的 C++ SDK 相比于 Java 存在较大缺失。经常出现内存透露或 CPU 飙高的状况,重大影响线上服务的稳固。
9、钉钉 IM 与 RocketMQ 的相互促进
面对以上困扰,在通过过屡次探讨和共创后,最终孵化出 RocketMQ 5.0 POP 生产模式。这是 RocketMQ 在实时零碎里程碑式的降级,解决了大量实时零碎应用 RocketMQ 过程中遇到的问题(如下图所示)。
1)Pop 生产模式下,每一个 consumer 都会与所有 broker 建设长连贯并具备生产能力,以 broker 保护整个音讯订阅的负载平衡和位点。重云轻端的模式下,负载平衡、订阅音讯、位点保护都在客户端实现,而新客户端只需做长链接治理、音讯接管,并且通用 gRPC 协定,使得多语言比方 C++、Go、Python 等语言客户端都能轻松实现,无需继续投入力去降级保护 SDK。
2)broker 能力降级更简略。重云轻端很好地解决了客户端版本升级问题,客户端改变的可能性和频率大大降低。以往降级新个性或能力只能推动所有相干 SDK 利用进行降级公布,降级过程中还需思考新老兼容等问题,工作量极大。而新模式只需降级 broker 即可实现工作。
3)单机夯死音讯能持续被生产。新模式下 consumer 和 broker 进行网状连贯和音讯订阅,由 broker 通过负载平衡策略平均分配音讯给 consumer 进行生产,以往宕机夯死导致的 Queue 音讯沉积问题也迎刃而解。如果 broker 发现 consumer 长时间没有进行音讯 ACK,则将不再对其投递音讯,彻底解决单机夯死问题。
4)无需关注 partition 数量。
5)彻底解决 rebalance。
6)负载更平衡。通过新的订阅模式,不论上游流量如何偏移,只有不超过单个 broker 的容量下限,生产端都能实现真正意义上的负载平衡。POP 模式生产模式曾经在钉钉 IM 场景磨合得十分成熟,在对可用性、性能、时延方面要求十分高的钉钉 IM 零碎证实了本人,也证实了一直降级的 RocketMQ 是即时通讯场景音讯队列的不二抉择。
10、相干材料
[1] 古代 IM 零碎中聊天音讯的同步和存储计划探讨
[2] 企业级 IM 王者——钉钉在后端架构上的过人之处
[3] 深度解密钉钉即时消息服务 DTIM 的技术设计
[4] 钉钉——基于 IM 技术的新一代企业 OA 平台的技术挑战(视频 +PPT)
[5] 企业微信的 IM 架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等
[6] IM 零碎的 MQ 消息中间件选型:Kafka 还是 RabbitMQ?
(本文已同步公布于:http://www.52im.net/thread-41…)