特殊时期,钉钉如何通过单元化扛住流量高峰?
引言
单元化1.0
单元化2.0
| 服务端视角 | 客户端视角 |
|
|
|
边缘计算:端到端路由
在DTIM的场景中,会话的路由信息几乎不会变更,只有当我们决定将某些超大的会话或者企业腾挪到新单元时,才会发起路由的变更,因此会话的路由信息几乎可以认为是恒定不变的,那么每次查询路由服务端,效费比太低,是极大的浪费。既然路由信息几乎不可变,是否将路由信息缓存呢?最常见的是使用一个集中式的Cache系统,缓存Hot的会话,我们也是这么做的,但是这么做还是不够,一旦Cache系统失效,DTIM还是会出现大面积故障,而且这个百万级的请求对Cache也是一个极大的压力。 考虑到钉钉有强大的客户端,借用边缘计算的思路,我们将用户的会话数据缓存到客户端,对于客户端来说,也只用缓存用户自身最热的N会话路由数据,消息发送时,通过Header将路由数据携带到服务端,服务端路由SDK只要做合法性和续约即可,将路由流量降低了95%以上。当路由服务出现异常时,还可以继续使用客户端路由,将路由的可用性提升到一个新的高度。 SDK本地会依据上行请求的返回中是否有新的路由信息,进而更新客户端路由。同时可以借助钉钉有主动下推的能力,通过同步协议将新的路由信息主动推送给客户端,使会话迁移做到更平顺。计算下沉:多单元一致性
对于新会话,比如小明要创建一个群聊,是应该创建在那个单元呢?如果在A单元创建了,当会话消息来到B单元,系统怎么能第一时间知道会话已经在被绑定到A单元。这里一般的方式有两种,单元之间的存储系统采用类似DTS的机制进行异步同步,这种机制有秒级延迟;或者第二种方式是在应用层主动同步,比如接入消息队列。这两种方式由于都是异步的原因,都会出现不一致的问题,如果会话同时被绑定在两个单元,逻辑上会导致用户的历史消息丢失,这个是不能接受的。 多地域(Region)数据同步其实是通用的技术挑战,我们认为存储系统提供是最好的方式,正如Google的Spanner一样,这样对我们上层才是最友好的方式。因此我们找到了存储的OTS、Nuwa团队一起共建了GlobalTable。GlobalTable的核心原理还是借助Nuwa的一致性组,组分布在多个地域,采用多数派写入成功即返回的原理,做到20ms以内的一致性写。| 分类 | 能力 | 描述 |
| 基础能力 | 统一的单元化顶层设计 | 和支付宝一样,在钉钉做单元化有了顶层设计和最佳实践,应用能快速进行业务改造。 |
| 业务路由能力 | 基于业务路由(L2),每个业务由于自身的特点,在容量和容灾场景有会有独立的选择。 | |
| 路由框架 | 提供路由框架,集成各种注解,简化业务改造代价。 | |
| 端到端路由能力 | ||
| 单元化管控大盘 | 支持会话迁移、路由查询、容量分配等 | |
| 容量 | 多单元 | 支持多个单元 |
| 会话迁移 | 1万会话分钟级别迁移 | |
| 推荐路由 | 可以按照业务属性进行动态单元推荐 | |
| 容灾 | 一键降级 | 分钟级容灾切换。DTIM能做到容灾场景下核心功能依旧可用。 |
| 路由服务异常降级本地 | 路由服务作为整个单元化的核心,提供99.995%的可用性,但是对于DTIM这种超大场景,仅有的0.005%异常也会带来极大的影响。所以DTIM和单元化做了降级兜底。 | |
| 常态化容灾演练 | 演练不影响正常的服务 |
单元化3.0
鱼和熊掌不可兼得
2022年对钉钉来说是成本之年,成本的压力不光落到了团队,还落到了每个人身上。正如存储的CAP理论是一样的,我们同时只能满足两个维度,对于流量(性能P)、成本(C)、体验(E)也是一样,在流量不可预知和干预的情况下,选择成本必然导致体验受损,反之选择体验,必然导致成本升高。进入下半年,疫情反复带来流量的反复,为了实现可控的教育成本,只能在高峰期降级部分能力,这又导致体验受损,这段时间的工单量可以窥见一斑。 流量是用户侧触发的,我们无法干预,只能在成本和体验之间寻求平衡。和前面提及的一样,为了减小成本的消耗这就导致我们在扩容和缩容之间疲于奔命,反应不及时甚至有故障的危险,这种机制不可取也不可持续。到底是要流量与成本,还是要流量与体验,给我们技术团队带来了巨大的挑战和矛盾。商业化路在何方
当前钉钉为支持大客户提供了多种解决方案,专业钉钉、专属存储与打包、专有钉钉。专属钉钉通过APP专属化以及部分专属功能,比如为一个企业定制一个拥有独立Logo的APP,能满足一般的中大型客户的业务诉求。对于大型以及超大型客户,我们提供专有钉钉,提供专有化输出,完全隔离的方案,比如浙政钉。伴随着钉钉的商业化进入深水区,客户对钉钉提出了新的诉求,特别是数据安全与归属、互联互通、完整的能力栈等诉求,当前钉钉输出产品形态都无法同时地满足以上需求。 前几年互联网上出现的几起数据安全事件,数据丢失与泄露,未经客户授权私自访问客户数据,让大多数客户不信任服务提供商,即使服务商的安全能力已经是业界一线能力。其实这个是可以理解的,数据即客户的生命线,数据无法在自身可控范围内,特别是对于很多特殊行业,这是无法接受的,自身性命岂能假手于人。专属钉钉在面临这种客户时,前线售卖同学是无能为力。 那么很多同学肯定会提“如果专属钉钉满足不了需求,我们专有钉钉不是能解决这些问题吗?”,其实单单从诉求来看,专有钉钉场景是切合客户的业务诉求,提供完全独立运行环境、可控的数据安全。但是专有钉钉由于其独特的架构带来高昂的售价以及后期的运维代价,对于超大型的客户来说也难以承担如此高的成本。对于钉钉自身来说,从研发到后续运维,维护一套独立体系也难以在客户侧大面积推广。云原生技术 :抵抗系统架构熵增的有效手段
近几年,互联网圈最火的技术莫过于以Docker为代表的云原生技术最为火热,各大云厂商也都在不遗余力的推广云原生技术以及对应的产品;同时钉钉服务过亿DAU的客户,面对各种可靠性、服务连续性、并发、容灾等技术挑战,也都走到了现有技术的边界。所以我们也在不断吸收新的技术和架构,希望从体系与架构上降低我们的技术复杂度,以抵抗熵增。我们在2021年底启动了我们的云原生升级战略,我们升级云原生技术并不是为了技术而升级,而是切实面临巨大的技术挑战。 首先我们面临多语言的挑战。我们以IM为例,IM的核心逻辑都是使用C++构建,但是我们常用的中间件三大件:存储、缓存、异步队列,其中缓存和异步队列在C++客户端上长期建设不足,导致IM长期在使用低版本,低版本由于长时间缺乏维护,经常会出现异常,比如队列假死、消费不均等,导致我们自己不得不亲自上阵修改SDK的代码;最后难以使用到产品的新能力,阻碍IM服务能力的提升。 其次是多产品多云的挑战。我们以阿里云为例,数据库类目下的产品,从类别上就有关系数据库、NoSQL数据库、数仓等等,还有存储也是一样。对于我们上层业务,其实绝大部分服务都只依赖了底层的CURD,这么多产品,每次对接一个产品都要开发一轮。配置系统也是一样,弹内有Diamond,云上有Nacos、Mse,K8s有自己的Configmap等,而且这些配置系统不像数据库有标准,而是百花齐放,但是这样却苦了我们使用者。这些内容不是我们的核心路径,浪费大把时间在各种产品接口的适配上,明显拖累了钉钉的发展。 最后就是通用的流量治理挑战。钉钉很多系统都是最终一致的系统,IM就是典型的最终一致系统,这类系统和强同步系统在架构设计有一个明显的区别,强一致系统如果遇到失败,必须要持续重试直到成功,所以一般编程上都是重试+退避。但是最终一致系统不是,这类系统允许部分节点失败,不要阻碍其他流程,失败的流量通过一个异步回旋的队列,将数据逐步回放回来即可。这种回旋需要借助异步队列,而且要设计各种消费机制,比如限速、比如丢弃等等,这是一个通用的逻辑,但是每个业务方或多或少都在实现自己的回旋系统,重复的造轮子。又比如各种故障注入,单元化路由流量等等,要想拥有这个能力,团队不得不投入人力研发。 在对付架构复杂度上,我们主要从两个维度来屏蔽复杂度,首先代码层面我们选择了DDD模式,我们使用DDD分层核心是把对外系统的依赖全部收拢到Infrastructure这一层,全部采用纯虚函数(Interface)对外提供接口。屏蔽底层中间件差异和细节。在架构上采用Sidecar的模式,类似于Dapr的思想,通过标准的GRPC和PB实现应用与中间件解耦。Sidecar中集成了各种中间件、配置系统、灰度系统等,等价实现了应用和中间件的解耦。上文中提到的不管是多语言挑战、多云多产品的挑战、重复造轮子等问题,都能很好的解决。互通网关 :混合架构的基石
云上云下互通,或者说多个云账户VPC之间的互通,我们常见的有两种方案,其一就是VPC直接打通,让多个VPC之间形成一个大的局域网,RealServer实现点对点互通;其二是中间搭建一个负载均衡器,通过暴露EIP实现互通。两个方案都有自己的优缺点。比如方案一,打通的VPC涉及到IP规划,如果前期没有合理规划,后续很难打通;还有这种方案有水桶短板安全问题,一旦一个VPC被攻破,这张网也被攻破;但是对于内部的应用来说架构就比较简单,可以仅仅借助K8s DNS service就能做到服务发现。对于方案二,最大的缺点就是中间有一个集中式的负载均衡,需要申请独立的LB才可访问;但是这种方案隔离性好。 对于钉钉单元化来说,涉及N个业务方,N * M个应用,对应X个VPC,要想VPC之间打通,几乎没有可能性,而且VPC打通,还面临应用之间的安全性问题。要实现Geo之间互通,环境之间的隔离性是基本要求,与此同时,我们也要考虑到系统的可扩展性,所以我们必须要构建一套独立的流量网关,实现流量加密、寻址、转发等通用能力。 钉钉互通网关是构建在Envoy之上的系统,双向Ingress和Egress,支持GRPC和钉钉自研协议。具备流量管理、传输加密、单元寻址等能力。钉钉单元化借助互通网关的能力,再配合全局流控系统,我们可以在多单元之间实现精确的流量控制和调度。展望
伴随着专属集群的持续输出,客户对专属的场景需求会越来越多,需要我们投入更多的人力持续的建设。比如架构侧的加强,首先是Sidecar持续强化,支持更多的中间件和环境,提供不同维度的安全能力,满足客户和应用的安全需求。在运维侧,我们需要构建多Geo管理能力,完善Geo和单元之间流量快速调度能力,提供自动化的自检系统等。在交付侧,如果实现快速交付,比如是否能做到新应用一周完成单元化改造,新Geo一天部署完成。这些挑战都是接下来我们要重点投入的方向。 对于标准钉来说,这个是我们的基本盘,一个稳定可靠且低成本的钉钉是我们持之以恒的目标,接下来我们会加大云上流量的占比,充分的借助云上弹性能力,实现可控的成本。 今天我们只是站在钉钉的角度上抛了一个“砖”,希望在异地多活这个领域激起一层浪花,欢迎大家一起讨论。核心概念
- Geo:钉钉专有化部署单位,解决数据合规需求,Geo间数据按需互通,并且互通数据在Geo内部做镜像拷贝,解决两化问题
- Unit: Geo内部资源物理分区隔离的最小单位,解决Geo内的容灾和容量的问题
- L0:客户端路由,决定了用户客户端接入钉钉服务器的所属单元,用户长连接所在的逻辑单元,起到连接加速作用。 用户接入单元
- L1:接入层路由,以用户为维度进行调度,即用户操作发生的单元。 用户归属单元
- L2:业务层路由,以业务资源为维度进行调度,大部分的业务资源所在单元应该和用户调度单元一致,但一些业务无法按照用户划分单元,如IM的会话,音视频的会议。 业务归属单元
- DMB:负责钉钉应用跨单元RPC调用的转发,可以认为是钉钉单元化RPC路由中间件。
- DMR:负责钉钉应用跨单元MQ消息的转发,可以认为是钉钉单元化MQ路由中间件。
🚀🚀🚀参与PolarDB-X开源产品评测,赢取猫王蓝牙音箱、评测局定制卫衣、数据库技术图书、10元猫超卡等好礼🎁
点击阅读原文查看详情。