赢博体育

赢博体育

你的当前位置: 赢博体育 > 赢博动态

IM电竞APP阿里IM技术分享(十):深度揭密钉钉后端架构的单元化演进之路

发布日期:2023-02-17 01:01:58 点击次数:

  本文由钉钉技术专家啸台、万泓分享,为了获得更好的阅读效果,本文已对内容进行少修订和重新排版。

  钉钉后端架构的单元化工作从2018年开始到今年,已经是第五个年头了。五年的时间,钉钉单元化迭代了三个版本,从最初的毛头小子,到达今年已经小有成就。

  我们在进行单元化架构建设的过程中,除了网上能找到的屈指可数的文章外,可以直接使用的系统更是乏善可陈,使我们不得不从最基础的系统开始造轮子,极大的影响建设效率。幸运的是,近几年云原生技术的兴起,让我们能复用很多基础设施,进而快速提升我们的单元化建设能力,助力钉钉的发展。

  今天想借此文和大家分享我们在钉钉单元化架构实施过程中的心路历程和一些最佳实践。因涉及的技术和业务面太广,本文的分享无法做到面面俱到,主要是想在同路人中形成共鸣,进而能复用一些架构或子系统的设计和实现思路。

  - 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》- 开源IM框架源码:(备用地址点此)

  《阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处》

  《阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践》

  《阿里IM技术分享(七):闲鱼IM的在线、离线聊天数据同步机制优化实践》

  《阿里IM技术分享(八):深度解密钉钉即时消息服务DTIM的技术设计》

  《阿里IM技术分享(九):深度揭密RocketMQ在钉钉IM系统中的应用实践》

  本文内容中使用了一些专有的技术名词,为了方便大家理解,我把关键的几个术语概念的缩写及其含义专门列出来,供大家参考。

  :钉钉专有化部署单位,解决数据合规需求,Geo间数据按需互通,并且互通数据在Geo内部做镜像拷贝,解决两化问题;

  : Geo内部资源物理分区隔离的最小单位,解决Geo内的容灾和容量的问题;

  :客户端路由,决定了用户客户端接入钉钉服务器的所属单元,用户长连接所在的逻辑单元,起到连接加速作用。用户接入单元;

  :接入层路由,以用户为维度进行调度,即用户操作发生的单元。用户归属单元;

  :业务层路由,以业务资源为维度进行调度,大部分的业务资源所在单元应该和用户调度单元一致,但一些业务无法按照用户划分单元,如IM的会话,音视频的会议。 业务归属单元;

  :负责钉钉应用跨单元RPC调用的转发,可以认为是钉钉单元化RPC路由中间件;

  :负责钉钉应用跨单元MQ消息的转发,可以认为是钉钉单元化MQ路由中间件;

  2018年,部分大客户出于法律政策、商业机密数据存储的要求,要求钉钉的数据存储、访问接入、服务部署需要在其信任的区域内。既需要满足其数据存储私有化要求,同时需要满足跨地区网络的rt性能要求。

  于是我们结合阿里云机房部署位置、物理距离、用户数据安全等方面出发,钉钉在客户的阿里云机房内建设了一个单元,将通讯录、IM信息等企业数据单独存储在客户机房。

  我们通过一条专线,将两个机房逻辑串联到一起,内部通过DMB/DMR系统,实现了请求互通,这就是钉钉单元化架构的1.0版。

  1.0版比较简单,纯粹是业务驱动,和支付宝单元化建设的初衷——“容灾驱动”有较大区别。两个站点通过UID分段,将用户划分为中心用户和专有用户。

  上图只是一个简化的逻辑结构,内部实现远比上图复杂,但是1.0建设主要是从0到1,和大多数异地多活的系统较相似,这里就只简单的和大家分享一下。

  2020年是一个特殊的年份,由于疫情的原因,带给大家非常多的改变,其中也包括钉钉。

  由于在线办公与教育流量的突增,开年第一天上班就给钉钉一个下马威,平峰的流量已经和除夕跨年的持平,但是和除夕不同的是这个流量是持续的,即使节前准备了三倍容量,也抵挡不住流量对系统的冲击。只能借助阿里云的能力,不断的扩容。

  但是每天将近30%的流量增幅,单纯的扩容也能难保障服务的连续性,最终也遇到了扩无可扩的场景,张北机房没有机位了,有机器资源但是没有机位让我们有力无处使。我们不得不不断进行系统优化,同时借助限流、降级、双推等措施,勉强抗住了流量的最高峰。

  疫情之前,我们一直在做高可用,但是这个高可用主要集中在容灾机制上,比如搭建容灾单元。如同支付宝一样,是因为当时光纤被挖断;又比如银行的两地三中心架构,是担心某一个地域由于天灾或者战争导致数据丢失。疫情的流量给我们上了一课,仅仅关注容灾是不够的,特别是钉钉的DAU从千万走向亿级别之后,更需要在容量上做出提前规划。

  正因如此,我们认为“容量架构不是设计出来而是真真切切被逼出来的”,所以容量架构就成为我们单元化核心要素之一。

  容量架构是将流量划分到不同单元,每个单元承载各自的流量。容灾架构是单元异常时,能保障核心的能力可用,也可以将流量动态调度到别的单元,实现服务的快速恢复。

  要实现流量的划分,必然要基于一个维度进行划分,一部分到A单元,一部分到B单元。

  钉钉单元化架构也是参考了淘系和支付宝的单元化架构,前两者都是基于UID划分,钉钉单元化的第一个版本其实也是一样的,基于UID做拆分。

  以IM为例:一条消息其实属于聊天双方的,群聊亦是如此。用户能和任意一个人聊天,这样我们根本无法找到一个切入点来划分流量,强行按照UID拆分,必然导致一个用户的消息出现在N个单元,单元的自封闭就无法做了。

  也有同学会说:为什么消息不按照每个人存储,这不就能按照UID划分了吗?结论是不行。首先这个消息变成了写扩散,持久化的时候会变成多单元写,其次是成本翻倍,在DTIM这种过亿规模的场景这条路走不通。这里可以多说一点,因为这个观点来之不易,大家都知道,人是有惯性的,既然淘宝、支付宝甚至是微信都是UID划分,为什么钉钉要特立独行?当时我们团队受到了绝大部分钉钉技术团队的挑战,持续长达将近一个月的技术选型的“争吵”,最终还是达成了一致意见。

  DTIM主要有3个维度,分别是UID、会话(CID)、消息。其中会话和消息是绑定的,而系统中最大量的是消息,按照第一性原则来看,一定要将消息划分开来,才能做到将容量划分开来的效果。

  不同的业务拥有不同的维度,因此我们认为:单元化最重要的找到自身“最大”的业务维度,将这个维护拆分,才能实现单元的横向扩展,我们称之为“业务路由”。

  回头来看:我们之前其实是进入了思考误区,以为淘系和支付宝都是UID维度,我们也要这个维度,其实UID正是前者的业务维度,比如订单,也是围绕用户,并不会有交集的情况,会话就是IM的划分维度,因此做单元化之前要先找到属于自己的业务维度。

  UID路由有个最大的好处,就是可以按照UID分段,能实现高效的静态路由,也不用担心多单元之间的一致性问题。但是这种分段路由局限性也比较明显,需要预先分配,单元之间动态调度流量和数据成本极高,而且只能支持这种数值+顺序的场景。

  在钉钉的场景中,有会话维度、房间维度、企业维度等等,想简单采用这种预分段机制难以满足业务需求。因此我们需要构建一个业务路由系统(RoutingService),实现消息流量的精确路由。

  以IM为例:每次消息的发送,在单元化框架层面,会通过消息的会话(CID),查询路由信息,如果是本单元,流量下行并持久化;如果是非本单元,路由到对应的单元中。下图是三个会线,三个会话隶属不同单元,不管用户从哪个单元发送消息,都会路由到会话所在的单元。比如:用户在Unit B的cid:1001 中发送消息,当消息进入Receiver之后,会先查询此cid:1001所在的单元,发现是Unit A,路由框架将请求转到A单元,消息在A单元持久化并通过A单元的同步协议,将数据推送到客户端。

  从上图可知:每次消息发送,都要查询路由服务,DTIM百万的峰值,对路由必然会带来超大的压力,同时我们能发现,路由数据在多单元实现一致性是一个巨大的挑战。

  既然路由信息几乎不可变,是否将路由信息缓存呢?最常见的是使用一个集中式的Cache系统,缓存Hot的会话,我们也是这么做的,但是这么做还是不够,一旦Cache系统失效,DTIM还是会出现大面积故障,而且这个百万级的请求对Cache也是一个极大的压力。考虑到钉钉有强大的客户端,借用边缘计算的思路,我们将用户的会话数据缓存到客户端。对于客户端来说IM电竞APP,也只用缓存用户自身最热的N会话路由数据,消息发送时,通过Header将路由数据携带到服务端,服务端路由SDK只要做合法性和续约即可,这样就将路由流量降低了95%以上。当路由服务出现异常时,还可以继续使用客户端路由,将路由的可用性提升到一个新的高度。SDK本地会依据上行请求的返回中是否有新的路由信息,进而更新客户端路由。同时可以借助钉钉有主动下推的能力,通过同步协议将新的路由信息主动推送给客户端,使会话迁移做到更平顺。

  对于新会话:比如小明要创建一个群聊,是应该创建在那个单元呢?如果在A单元创建了,当会话消息来到B单元,系统怎么能第一时间知道会话已经在被绑定到A单元。这里一般的方式有两种:

  这两种方式由于都是异步的原因IM电竞APP,都会出现不一致的问题,如果会话同时被绑定在两个单元,逻辑上会导致用户的历史消息丢失,这个是不能接受的。多地域(Region)数据同步其实是通用的技术挑战,我们认为存储系统提供是最好的方式,正如Google的Spanner一样,这样对我们上层才是最友好的方式。因此我们找到了存储的OTS、Nuwa团队一起共建了GlobalTable。GlobalTable的核心原理还是借助Nuwa的一致性组,组分布在多个地域,采用多数派写入成功即返回的原理,做到20ms以内的一致性写。

  以DTIM为例,最大的特点是当服务单元异常时,服务侧仍能提供最核心的服务,保障最基本的能力。本质上是由于DTIM是最终一致性系统,可以短暂允许部分环节失败。可以看一下DTIM发送消息的容灾场景。当某个单元完全不可用的情况下,用户消息发送链路通过降级为local模式,在本地校验非本单元会话数据通过之后直接做消息发送,processor遇到非本单元的会话消息数据可以做单元间投递做数据回放,本地是否落库可选,同步协议推送不必区分是否为本单元会话消息数据直接通过本单元的topic推送给客户端,配合用户无状态快速迁移能力,单元间可以实现真正的分钟级别容灾切换能力。

  2022年对钉钉来说是成本之年,成本的压力不光落到了团队,还落到了每个人身上。正如存储的CAP理论是一样的,我们同时只能满足两个维度,对于流量(性能P)、成本(C)、体验(E)也是一样,在流量不可预知和干预的情况下,选择成本必然导致体验受损,反之选择体验,必然导致成本升高。进入下半年,疫情反复带来流量的反复,为了实现可控的教育成本,只能在高峰期降级部分能力,这又导致体验受损,这段时间的工单量可以窥见一斑。流量是用户侧触发的,我们无法干预,只能在成本和体验之间寻求平衡。和前面提及的一样,为了减小成本的消耗这就导致我们在扩容和缩容之间疲于奔命,反应不及时甚至有故障的危险,这种机制不可取也不可持续。到底是要流量与成本,还是要流量与体验,给我们技术团队带来了巨大的挑战和矛盾。

  专属钉钉通过APP专属化以及部分专属功能,比如为一个企业定制一个拥有独立Logo的APP,能满足一般的中大型客户的业务诉求。对于大型以及超大型客户,我们提供专有钉钉,提供专有化输出,完全隔离的方案,比如浙政钉。伴随着钉钉的商业化进入深水区,客户对钉钉提出了新的诉求,特别是数据安全与归属、互联互通、完整的能力栈等诉求,当前钉钉输出产品形态都无法同时地满足以上需求。前几年互联网上出现的几起数据安全事件,数据丢失与泄露,未经客户授权私自访问客户数据,让大多数客户不信任服务提供商,即使服务商的安全能力已经是业界一线能力。其实这个是可以理解的,数据即客户的生命线,数据无法在自身可控范围内,特别是对于很多特殊行业,这是无法接受的,自身性命岂能假手于人。专属钉钉在面临这种客户时,前线售卖同学是无能为力。那么很多同学肯定会提“如果专属钉钉满足不了需求,我们专有钉钉不是能解决这些问题吗?”,其实单单从诉求来看,专有钉钉场景是切合客户的业务诉求,提供完全独立运行环境、可控的数据安全。但是专有钉钉由于其独特的架构带来高昂的售价以及后期的运维代价,对于超大型的客户来说也难以承担如此高的成本。对于钉钉自身来说,从研发到后续运维,维护一套独立体系也难以在客户侧大面积推广。

  当整体架构成熟之后,我们也在思考,单元化能否从技术架构升级为业务架构,比如搭建独立的高可用单元,按照售卖的SLA提供给VIP客户,支持钉钉商业化的发展。同时我们在云原生逐步发力,将部分核心应用放到云上,经过这一年多的运行,遇到了新的挑战,但更获得云下无法获得的计算弹性能力,云上的弹性对云下是一个降维打击,从一个新的方向解决计算问题。如上文提到的两个核心挑战,钉钉单元化同样面临这个问题,在持续的发展中找到了一个合适的架构方向。基本思路是:

  :是云上云下,我们认为云上云下并不是取代的关系,而是相互补充的关系,是一个长期的状态,正如很多大客户随着规模的持续扩张,最终依赖的部分核心能力必然走向自研道理一样,这能做成本的进一步降低,所以架构是一个混合云架构;

  :业务架构上也是混合云架构,通过不同的Geo,将不同的业务逻辑上聚合到一起,构建起一张钉钉的大网,不同Geo按需互通,实现了业务架构的混合。

  但是最终一致系统不是,这类系统允许部分节点失败,不要阻碍其他流程,失败的流量通过一个异步回旋的队列,将数据逐步回放回来即可。这种回旋需要借助异步队列,而且要设计各种消费机制,比如限速、比如丢弃等等,这是一个通用的逻辑,但是每个业务方或多或少都在实现自己的回旋系统,重复的造轮子。又比如各种故障注入,单元化路由流量等等,要想拥有这个能力,团队不得不投入人力研发。

  对于方案一:打通的VPC涉及到IP规划,如果前期没有合理规划,后续很难打通;还有这种方案有水桶短板安全问题,一旦一个VPC被攻破,这张网也被攻破;但是对于内部的应用来说架构就比较简单,可以仅仅借助K8s DNS service就能做到服务发现。

  对于方案二:最大的缺点就是中间有一个集中式的负载均衡IM电竞APP,需要申请独立的LB才可访问;但是这种方案隔离性好。

  对于钉钉单元化来说,涉及N个业务方,N * M个应用,对应X个VPC,要想VPC之间打通,几乎没有可能性,而且VPC打通,还面临应用之间的安全性问题。要实现Geo之间互通,环境之间的隔离性是基本要求,与此同时,我们也要考虑到系统的可扩展性,所以我们必须要构建一套独立的流量网关,实现流量加密、寻址、转发等通用能力。

  在架构侧:首先是Sidecar持续强化,支持更多的中间件和环境,提供不同维度的安全能力,满足客户和应用的安全需求;

  在运维侧:我们需要构建多Geo管理能力,完善Geo和单元之间流量快速调度能力,提供自动化的自检系统等;

  在交付侧:如果实现快速交付,比如是否能做到新应用一周完成单元化改造,新Geo一天部署完成。这些挑战都是接下来我们要重点投入的方向。IM电竞APP