单元化的核心理念是将业务按照某种维度划分成一个个单元,理想情况下每个单元内部都是完成所有业务操作的自包含集合,能独立处理业务流程,各个单元均有其中一部分数据,所有单元的数据组合起来是完整的数据(各企业实际落地过程中会结合实际业务和基建情况做一些折中)。流量按照某种分区维度(例如流量所属用户)Sharding 到不同的单元,调度上按照流量携带的分区信息进行调度,保证同一时刻该分区的数据写入都在同一个单元,简化版示意图如下:
业界各企业演进到单元化一般主要都是出于下面几个原因:
资源限制:单机房受物理资源上限限制,同城多机房受地区的能评和供电等限制,无法做到机房的无限扩展,随着业务规模的扩大,长期一定会面临多地数据中心的布局;
合规要求:全球化产品通常会面临不同地区的合规要求(例如欧盟的 GDPR),会有当地用户数据只能存储在当地的要求,业务天然需要考虑围绕不同的合规区域构建单元;
容灾考虑:核心业务有城市级异地容灾需求,通过单元化方式可以构建异地单元,每个单元都有常态真实流量,流量可以灵活地在单元间进行调度。
除了上述最关键的问题外,建设单元化还能有其他方面的收益:
业务体验提升:通过结合就近调度,能够将用户流量调度到最近的单元,从而降低请求耗时,提升用户体验;
成本方面:相比于异地冷备,两地三中心等传统容灾架构,各个单元都能直接承载流量,减少资源冗余;
隔离方面:在最小的单元范围内去做各种技术演进,能够有效控制风险半径。
机房延迟问题:一般同城机房之间物理距离 <200KM,网络延迟和机房内差别不大,大部分业务场景无需过多考虑。但跨城异地机房之间的延迟会大很多,例如北京和上海之间 RTT P99 达 40 毫秒,此时跨机房请求耗时需要慎重考虑,特别是单个请求如果出现多次跨机房,增加的请求耗时可能是几百毫秒甚至更大的。
数据同步问题:
容灾单元之间的数据需要互通,以支持一个单元故障时能够切流到另一个单元进行容灾,因此需要在单元间进行数据同步;
数据同步需要考虑不同的存储引擎(数据库、缓存、消息队列等),不同引擎特性不一,实现成本和复杂度巨大;
跨城机房间的延迟大而且是弱网环境,网络的质量很难保证,进一步导致数据同步质量保证难度大。
流量路由的问题:所谓流量路由也就是如何将流量调度到正确的单元问题,需要考虑在请求链路哪个环节(客户端、流量入口、内网 RPC、存储层等)、根据请求什么信息(用户 ID、地理位置等)进行用户归属单元的识别,以及如何进行走错单元流量的纠偏。
数据正确性问题:
成本问题:
管理复杂度问题:随着单元的增加,不同的单元都需要管理对应的服务和基建,管理和运维的复杂性会有较大增加。
在本文中,我们仅对字节跳动在中国大陆的单元化架构做讨论,目前我们的单元化部署架构如下图所示:
我们围绕客户端选路、接入层纠偏、计算层纠偏、存储访问层管控四个维度构建了单元化流量调度和管控能力,通过技术手段确保单元化流量调度的正确性(将流量调度到正确的单元)和数据访问的正确性(数据不写脏),具体来说:
客户端:在客户端通过调度组件将用户流量从第一跳开始就调度到正确的单元,减少在内网的跨单元流量;
接入层:在网关通过网关的开放能力实现调度插件,按需对客户端调度出错的流量进行路由信息计算和纠偏;
计算层:计算层通过研发框架和 Mesh 的开放能力实现流量切面,确保对未经过网关的内部 RPC 流量的路由信息计算和纠偏;
存储层:通过存储访问中间件和 Mesh 的开放能力实现流量切面,确保对存储层路由出错 / 异常单元化流量进行审计和拦截。
目前包括抖音、抖音电商、抖音支付、抖音直播、抖音本地生活等业务均启动了异地单元化改造落地,生产环境已接入数千个核心微服务、超过 100 万实例。
单元维度的选择很大程度上决定了单元化架构的水平扩展能力、运维成本和容灾方式,需要结合业务推进单元化架构想要解决的核心问题(资源、合规、容灾)和业务特性来选型。业界实践大部分是围绕物理维度(例如 Region、机房)构建单元,也有少部分是逻辑维度的。
结合我们面临的业务特性:
同时有包括社交、电商、本地生活、直播、支付等多种类型的业务,不同业务在单元化架构上有不同考虑;
存在大量中台,各中台同时支持不同类型的业务,这些中台需要适配好不同上游业务的单元化流量和数据;
业务之间有比较复杂的依赖,例如电商和本地生活有从抖音入口进来的流量,也有自己独立入口的流量。
综合考虑,我们认为字节跳动的单元应该是物理维度的,单元的建设随着基建的规划和建设去演进,而不是业务自行构建,这种物理意义上的唯一调度依据能够保证不同的业务长期演进中能够在一个单元内闭环,避免调度混乱。
同时考虑我们目前最核心要解决的问题是资源和容灾问题,因此我们最终选择是 Region 维度构建单元,形成当前的 同城容灾 + 异地多活 容灾架构。
分区维度决定了数据和流量在单元间的划分标准,选择分区维度的时候需要重点考虑:
每个分区对应的数据互相不能重叠,以保证同一时刻同一个分区维度的数据写只在一个单元;
分区的粒度需要能支撑流量调度的灵活性要求,确保流量能按预期分布到各个单元;
路由信息的计算需要足够轻量;
确保单元内调用逻辑尽可能闭环,避免产生跨单元调用。
一般 ToC 类业务最常见的就是以用户作为分区维度,我们目前大部分业务选择用户(UserID)作为分区维度(抖音、电商等业务),少部分选择 Region 维度(搜索等业务)。
分区和单元的映射也就是对于一个请求我们找到它应该调度到哪个单元的依据,需要综合业务上对管理成本和灵活性的要求来考虑,我们通过以下两种方式结合来管理:
路由信息的计算逻辑一般不复杂,更关键的是决策需要在哪些环节计算以及怎么纠偏(把走错单元的流量调度到正确单元),一般来说,整个请求链路包括客户端、接入层、计算层和存储层四层,需要结合公司基建情况和业务要求来决策落地方式,以下是我们对于各层实现纠偏能力的必要性和实现方式上的思考和建议。
客户端:
接入层(负载均衡、网关):
计算层:
存储层:
一个比较完整的分层示例:
理想的单元化架构是所有服务都能在单元内闭环,不出现跨单元的流量,但是在实际业务场景下很难满足理想的单元化架构。以电商场景为例,如果用买家作为分区维度,那商家的数据必然会被多单元读写,因此一定存在部分服务的流量不能单元化调度的情况,单元化架构需要能做好适配。和业界基本思路类似,我们对服务类型做了区分:
服务类型决定了流量调度方式和服务访问的数据的同步方式。基于此,研发核心需要关注业务上服务类型的定义即可,其他包括数据同步管理、流量调度的细节都可以由框架 / 平台内部收敛解决,极大降低业务上的理解和管理复杂度:
实际业务落地过程中,甚至会出现同一个服务不同接口的访问行为不一致的情况,需要细化到接口维度区分类型,和服务类型类似设计即可,这里不赘述。
单元化架构下的数据同步有两种场景:
数据对账一般是全增结合,实时增量比对确保 Diff 发现的实时性,但写入 TPS 高时会有误差,周期全量比对兜底保证整体一致率,常见的一致性比对流程如下:
设计增量数据一致性比对能力的时候,需要重点考虑对热键的检测和处理,部分热键一直在 Update 的话需要能识别出来,否则会导致持续有不一致误报警,一个简单的基于重试比对的实现如下:
以 UserID 作为单元化分区维度的话,在实际业务场景可能出现两种异常情况:
这两种情况我们都认为是异常单元化流量,在存储访问层通过管控中间件支持了识别和拦截能力,兜底保障数据不被写脏:
在做单元间切流的时候,由于下面两个问题,可能导致数据出现脏写:
我们对切流过程引入 两阶段配置变更 + 存储访问禁写 来解决上述数据脏写问题,整体切流流程如下:
得益于我们整体单元化调度和存储访问中间件设计,我们的禁写是 UserID 维度的,能够控制到每次切流仅禁写切流中的用户,将切流的影响做的尽可能小。
结合前面关于切流期间通过 两阶段配置变更 + 存储访问禁写 来避免数据出现脏写的说明,可以看到配置下发生效的时间对于禁写时长是一个非常大的影响因素,而禁写生效时长直接影响切流那部分用户的使用体验,因此我们需要尽可能提升配置下发时效。
目前字节接入单元化的 Pod 数已超过 100 万,在这么大规模的实例数下快速下发调度配置的挑战是非常大的,我们通过 长连接主动推送 + 定时轮询拉取 的方式进行配置分发,提高配置收敛速度,减少配置不一致中间态的时间:
配置发布过程中,同一个 PSM 不同单元实例的生效时间不一样,可能导致流量纠偏到目标单元后由于目标单元重新计算纠偏回来,出现路由死循环的情况。我们通过单元化调度组件在流量上下文中记录并传递纠偏次数,拦截重复纠偏的流量,及时阻断回环流量:
业务核心指标观测检查:切流过程中对业务成功率、负载、延迟等指标的观测。
单元之间通常物理距离较远,网络专线建设难度大成本高,网络质量(延迟、丢包、可用带宽)也差于同城机房间。前面介绍的各种单元路由纠偏都会产生跨单元 RPC,相比单元内 RPC 其成功率 / 耗时指标有所劣化。为了提升跨单元 RPC 质量,除了网络专线本身的稳定性建设(属于网络基建范畴,此处不展开),也可以在架构设计和带宽使用策略上进行针对性的优化。
跨单元 RPC 通道收敛:在单元化流量路由场景,某个流量很大的 RPC 服务可能仅小比例命中跨单元纠偏,此时由于下游实例数量很多可能导致连接复用效果较差。此时如果让所有跨单元 RPC 先统一经过本地边界网关,然后传输到其他单元的边界网关,最后再转发给实际下游,则建连过程拆分成了 3 段,中间段(网关之间)被所有跨单元 RPC 共用,可以保持较高的链接复用率 ; 另外 2 段为本地建连,即使连接复用率低,带来的耗时增加也较少。此外收敛到网关通道也更容易集中对跨单元 RPC 进行其他优化,比如按接口等级进行 QoS 管控、在专线完全故障情况下对重要的控制信令做加密后 Fallback 到公网传输等:
跨单元网络分级 QoS 管控:长距离专线建设成本高,带宽有限,也更容易因为各类异常导致可用带宽变少,无法满足所有场景的跨单元网络传输的需求,因此需要对不同类型的跨单元流量进行分级 QoS 管控。跨机房 RPC 失败要么直接影响用户体验,要么导致故障无法操作止损,应在跨单元网络传输中给予更高的优先级 ; 离线数据传输容易短时间大量占用带宽,异常对用户感知相对不明显,应给以较低优先级,严格限制上限,必要时还应让出带宽:
字节跳动从原本的单 Region 内同城容灾架构演进到多 Region 异地单元化架构周期比较短(一年半左右),基础设施对多 Region 视角的支持还比较不足,对业务的整体研发和业务管理成本偏高,需要将多 Region 的研发和业务管理成本打平到单 Region。
从计算资源成本视角:在原来三机房同城容灾模式下,每个机房需要预留 50% 的 Buffer 用于机房故障容灾,演进到异地单元化架构后,基于两个容灾单元间的六个机房,部分业务机房故障可以将流量分摊到其他五个机房,此时各机房仅需 20% 的 Buffer。
从存储资源成本视角:我们现在是 同城容灾 + 异地多活 的容灾模式,各单元都支持同城容灾,因此部分业务可以直接进行数据的单元化拆分,单元内各自只有一部分数据(加起来是全量数据),理想情况下存储成本减少一半。
未来字节跳动在国内会有更多的区域,不同业务在各单元的排布模型会越来越复杂,结合我们复杂的业务依赖关系,这里的流量调度模型、数据单元化和同步模型都需要演进。
未来区域增多后,业务随着发展机房排布会调整,可能会需要在非容灾单元之间调整流量,此时存在数据单元化拆分和用户维度数据单元间搬迁能力,需要解决用户维度数据的识别和低成本搬迁问题。
字节跳动目前的存储对 AP 场景更友好(侧重抖音这种社交类场景),主要围绕单 Region 构建,在多单元场景下对于电商、支付类(对数据一致要求非常高)的业务支持较弱,在异地单元化架构下强依赖数据同步能力来支持多单元数据多活能力,业务上的限制偏大(例如写只能统一在一个单元),有跨 Region 强一致数据库的需求。
2024 年收官之作:12 月 13 日 -14 日,AICon 全球人工智能开发与应用大会将在北京举办。从 RAG、Agent、多模态模型、AI Native 开发、具身智能,到 AI 智驾、性能优化与资源统筹等大热的 AI 大模型话题,60+ 资深专家共聚一堂,深度剖析相关落地实践案例,共话前沿技术趋势。大会火热报名中,详情可联系票务经理 13269078023 咨询。