作者 |尹启绣 – 阿里云智能钉钉技能专家
背景
最近几年,钉钉迅速成为一款国民级运用。IM 作为钉钉最中心的功用,每天需求支撑海量企业用户的交流,一起还经过 PaaS 方式为淘宝、高德等 App 供给根底的即时通讯才能,是日均千亿级音讯量的 IM 渠道。
咱们经过 RocketMQ 完结了体系解耦、异步削峰填谷,还经过守时音讯完结分布式守时使命等高档特性。另外,过程中也与 RocketMQ 深化共创,不断优化处理了许多问题,并且孵化出 POP 消费形式等新特性,彻底处理了RocketMQ 负载粒度只能到Queue等级、rebalance导致时延等问题。
钉钉作为企业级 IM 领先者,面临着巨大的技能挑战。市面上 DAU 过亿的 App 里,只需钉钉是 2B 产品,咱们不只需求和其他 2C 产品相同,支撑海量用户的低时延、高并发、高功能、高可用,还需确保企业级用户在运用钉钉时能够提升交流协同功率。
因此,钉钉供给了许多竞品没有的功用,比方音讯必达是钉钉的代名词。Ding 和已读很好地提升了咱们在企业中的交流功率。音讯多端同步、音讯云端存储使得用户不论在哪里登录钉钉都能看到一切前史音讯。工作场景下,信息安满是企业的生命线,咱们供给了非本安排的人不能加入战略,用户退出企业即主动退出工作群,很好地保证了企业的信息安全。
一起,在钉钉官方现已支撑全链路加密的根底上,还支撑用户自己设置密钥的三方加密,进一步提高了体系的信息安全性。
稳定性方面,企业用户对稳定性的要求也十分高,假如钉钉呈现毛病,深度运用钉钉的企业都会遭到巨大影响。因此,钉钉 IM 体系在稳定性上也做了十分深化的建设,架构支撑异地多活和可弹性伸缩容,中心才能一切依靠都为双倍。并树立了流量防护,制定了单测和集成测试规范以及常态化的容灾演练机制。比方波浪式流量便是在做断网演练时发现。
针对不同职业的业务多样性,咱们会尽或许地满意用户的通用性需求,比方万人群、全员群等,目前钉钉现已能够支撑 10 万人等级的群。更多的需求咱们会笼统出通用的敞开才能,将 IM 才能尽或许地敞开给企业和三方 ISV,使不同形态的业务都能在钉钉渠道上得到满意 。
市场调研标明,钉钉 IM 的敞开才能数量处于职业顶尖水平,咱们将继续结合业界智慧,打造好钉钉生态。
钉钉 IM 中心功用处理流
IM 本身是异步化交流体系,与开会或者电话交流比较,咱们在发送出一条音讯后,并不要求对方立刻给出回应。这种异步特性与音讯行列的才能很契合,音讯行列能够很好地协助 IM 完结异步化解耦、失利重试、削峰填谷等才能。
以 IM 体系最中心的发音讯和已读链路简化流程(如上图),来详细阐明音讯行列在体系里的重要作用。
发音讯链路流程
处于登录状况的钉钉用户发送一条音讯时,会与钉钉的接入层树立长衔接。首先会将恳求发送到 receiver 模块,为确保发音讯体验和成功率,receiver 运用只做这条音讯能否发送的校验,其他如音讯入库、接收者推送等都交由下流运用完结。校验完结之后将音讯投递给音讯行列,成功后即可回来给用户。
音讯发送成功,processor 会从音讯行列里订阅到这条音讯,并对音讯进行入库处理,再经过音讯行列将音讯交给同步服务 syncserver 做处理,将音讯同步给在线接收者。对于不在线的用户,能够经过音讯行列将音讯推给离线 push 体系。离线 push 体系能够对接接苹果、华为、小米等推送体系进行离线推送。
用户发音讯过程中的每一步,失利后都可经过音讯行列进行重试处理。如 processor 入库失利,可将音讯打回音讯行列,继续回旋处理,到达终究共同。一起,能够在订阅的过程中对消费限速,避免线上突发峰值给体系带来灾难性的结果。
已读链路对时效性要求低,但是整个体系的已读恳求量十分大
用户对一条音讯做读操作后,会发送恳求到已读服务。已读服务收到恳求后,直接将恳求放到音讯行列进行异步处理,一起能够到达削峰填谷的目的。已读服务处理完之后,将已读事件推给同步服务,让同步服务将已读事件推送给音讯发送者。
从以上两个中心链路能够看出,音讯行列是 IM 体系里十分重要的组成部分。
为什么选用 RocketMQ
阿里内部曾有 notify、RocketMQ 两套运用音讯中间件,也有其他根据 MQTT 协议完结的音讯行列,终究都被 RocketMQ 统一。
IM 体系对音讯行列有如下几个基本要求:首先,解耦和削峰填谷,这是音讯行列的根底才能;除此之外,IM体系对高功能、低时延要求也十分高,确保亿级规模用户的状况下不产生颤动;一起,可用性方面不只包含体系可用性,也包含数据可用性,要求写入音讯行列时音讯不丢掉(钉钉 IM 对音讯的确保等级是一条都不丢)。
RocketMQ 经过多次双 11 检测,其堆积功能、低时延、高可用已成为业届标杆,完全符合对音讯行列的要求。一起它的其他特性也十分丰富,如守时音讯能够以极低的成本完结分布式守时使命,事务音讯的音讯可重放和死信行列供给了后悔药的才能,比方线上体系呈现 bug ,许多音讯没有正确处理,能够经过重置位点、重新消费的方式,订正之前的错误处理。
另外,音讯行列的运用场景十分丰富,RocketMQ 的扩展才能能够在音讯发送和消费上做切面处理,完结通用性的扩展封装,大大下降开发工作量,比方阿里内部应广泛运用的 Trace 体系经过 RocketMQ 的扩展才能完结。Tag & SQL 订阅才能使得能够在 broker 层对音讯进行过滤,无需在消费端定于一切音讯,大幅下降下流体系的订阅压力。
钉钉运用 RocketMQ 至今从未产生毛病,集群峰值 TPS 可达 300w/s,从出产到消费时延能够确保在 10 ms 以内,基本只需网络方面的开支。支撑 30 亿条音讯堆积,中心指标数据表现抢眼,功能反常优异。
发音讯流程中,很重要的一步是 receiver 运用做完音讯能否发送的校验之后,经过 RocketMQ 将音讯投递给 processor 做音讯入库处理。投递过程中,咱们供给了三重稳妥,以确保音讯发送满有把握:
榜首重稳妥:receiver 将音讯写进 RocketMQ 时, RocketMQ SDK 默认会重试五次,每次测验不同的 broker ,保证了音讯写失利的概率十分小。
第二重稳妥:写入 RocketMQ 失利的状况下,会测验以 RPC 方式将音讯投递给 processor 。
第三重稳妥:假如 RPC 方式也失利,会测验将本地 redoLog 经过 Crontab 使命守时将音讯回放到 RocketMQ 里面。
此外,如安在体系反常的状况下做到音讯终究共同?
Processor 收到上游投递的音讯时,会测验对音讯做入库处理。即便入库失利,仍然会将音讯投给同步服务,将音讯下发,确保实时音讯收发正常。反常状况时会将音讯重新投递到反常 topic 进行重试并在音讯内带上失利节点,下次重试可直接在反常处重新开始。投递过程中经过设置 delay level 做退避处理,对反常 topic 做限速消费。
重试写不同的 topic 是为了与正常流量隔离,优先处理正常流量,防止因为反常流量消费而导致真正的线上音讯处理被延迟。另外, Rocket MQ 的一个 broker 默认只需一个 Retry 音讯行列,假如消费失利量特别大的状况下,会导致下流负载不均,某些机器打死。
此外,假如毛病的时刻长,虽然有退避战略,但假如不做限速处理,重试流量会继续叠加,导致雪崩。
RocketMQ 深化运用 – 分布式守时使命
用 RocketMQ 完结分布式守时使命的流程如上图所示。
在几千人的群里发一条音讯,假设有 1/4 的成员一起翻开谈天窗口,并且向服务端发送已读恳求。假如不对服务端已读服务和客户端需求更新的已读数做兼并处理,更新的 QPS 会高到达 1000/s。钉钉能够支撑十几万人的超大群,超大群的活泼对服务端和客户端都会带来很大冲击,而实际上用户只需求完结秒级更新。
针对以上场景,咱们进行了优化:能够运用 RocketMQ 的守时音讯才能完结分布式守时使命。以已读流程为例,如上图所示,用户发起恳求时,会将恳求放入集中式恳求行列,再经过 RocketMQ 守时音讯生成守时使命,比方 5 秒后批量处理。则 5 秒之后,RocketMQ 订阅到使命触发音讯,将行列里面一切恳求都取出进行批量处理。
咱们在 RocketMQ 守时音讯的根底之上笼统了一个分布式守时使命的组件,供给了许多其他实时性可达秒级的功用,如万人群的群状况更新、音讯扩展更新都接入了此组件,大幅下降体系压力。如上图右,在一些大群活泼的时刻点成功地让流量下降并保持平稳状况。
RocketMQ 的出产端战略如下:出产者获取到对应 topic 一切 broker 和 Queue 列表,然后轮询写入音讯。顾客端也会获取到 topic 一切 broker 和Queue列表。另外,还需求要从 broker 中获取一切顾客 IP 列表进行排序,依照装备负载均衡,如哈希、一次性哈希等战略计算出自己应该订阅哪些 Queue。
上图中,ConsumerGroupA的Consumer1被分配到MessageQueue0和MessageQueue1,则它订阅 MessageQueue0和MessageQueue1。
在 RocketMQ 的运用过程中,咱们面临了诸多问题。
问题 1:波浪式流量
咱们发现订阅音讯集群滚动时,CPU 呈现波浪式飙升。经过深化排查发现,断网演练后进行网络康复时,大量 producer 一起康复工作,一起从榜首个 broker 的榜首个 Queue 开始写入音讯,出产音讯波浪式写入 RocketMQ ,从而导致顾客端呈现波浪式流量。
终究,咱们联络 RocketMQ 开发人员,调整了出产战略,每次出产者发现 broker 数量或状况产生变化时,都会随机选取一个初始 Queue 写入音讯,以此处理问题。
另一个导致波浪式流量的问题是装备问题。排查线上问题时,从 broker 视角看,每个 broker 的音讯量都是平均的,但 consumer 之间流量相差特别大。终究经过在 producer 侧测验抓包得以定位到问题,是因为 producer 写入音讯时超时率偏高。梳理装备后发现,是因为 producer 写入音讯时装备超时太短,Rocket MQ 在写音讯时会测验多次,比方榜首个 broker 写入失利后,将直接跳到下一个 broker 的榜首个 Queue ,导致每个 broker 的榜首个 Queue 音讯量特别大,而靠后的 partition 几乎没有音讯。
问题 2:负载均衡只能到 Queue 维度,导致需求不时地重视 Queue 数量
比方线上流量增长过快,需求进行扩容,而扩容后发现机器数大于 Queue 数量,导致无论怎样扩容都无法分担线上流量,终究只能联络 RocketMQ 运维人员调高 Queue 数量来处理。
虽然调高 Queue 数量能处理机器无法订阅的问题,但因为负载均衡战略只到 Queue 维度,负载一直无法均衡。从上图能够看到, consumer 1 订阅了两个 Queue 而 consumer 2 只订阅了一个 Queue。
问题 3:单机夯死导致音讯堆积,这也是负载均衡只能到 Queue 维度带来的副作用
比方 Broker A 的 Queue 由 consumer 1 订阅,呈现宿主机磁盘 IO 夯死但与 broker 之间的心跳仍然正常,导致 Queue 音讯长时刻无法订阅从而影响用户接收音讯。终究只能经过手动介入将对应机器下线来处理。
问题 4:rebalance
Rocket MQ 的负载均衡由 client 自己计算,导致有机器反常或发布时,整个集群状况不稳定,经常会呈现某些 Queue 有多个 consumer 订阅,而某些 Queue 在几十秒内没有 consumer 订阅的状况。因此导致线上发布的时分,呈现音讯乱序或对方已回音讯但显示未读的状况。
问题 5:C++SDK 才能缺失
钉钉的中心处理模块 Receiver、processor 等运用都是经过 C++ 完结,而 RocketMQ 的 C++ SDK 比较于 Java 存在较大缺失。经常呈现内存走漏或 CPU 飙高的状况,严重影响线上服务的稳定。
共创晋级 RocketMQ POP 消费形式
面临以上困扰,在经过过多次讨论和共创后,终究孵化出 RocketMQ 5.0 POP 消费形式。这是 RocketMQ 在实时体系里程碑式的晋级,处理了大量实时体系运用 RocketMQ 过程中遇到的问题:
-
Pop消费形式下,每一个 consumer 都会与一切 broker 树立长衔接并具备消费才能,以 broker 保护整个音讯订阅的负载均衡和位点。重云轻端的形式下,负载均衡、订阅音讯、位点保护都在客户端完结,而新客户端只需做长链接管理、音讯接收,并且通用 gRPC 协议,使得多言语比方 C++、Go、 Python 等言语客户端都能轻松完结,无需继续投入力去晋级保护 SDK 。
-
broker才能晋级更简单。重云轻端很好地处理了客户端版别晋级问题,客户端改动的或许性和频率大大下降。以往晋级新特性或才能只能推进一切相关 SDK 运用进行晋级发布,晋级过程中还需考虑新老兼容等问题,工作量极大。而新形式只需晋级 broker 即可完结工作。
-
broker才能晋级更简单。重云轻端很好地处理了客户端版别晋级问题,客户端改动的或许性和频率大大下降。以往晋级新特性或才能只能推进一切相关 SDK 运用进行晋级发布,晋级过程中还需考虑新老兼容等问题,工作量极大。而新形式只需晋级 broker 即可完结工作。
-
无需重视partition数量。
-
彻底处理rebalance。
-
负载更均衡。经过新的订阅形式,不论上游流量如何偏移,只需不超越单个 broker 的容量上限,消费端都能完结真正意义上的负载均衡。
POP 形式消费形式现已在钉钉 IM 场景磨合得十分老练,在对可用性、功能、时延方面要求十分高的钉钉 IM 体系证明了自己,也证明了不断晋级的 RocketMQ 是即时通讯场景音讯行列的不二挑选。