同城双活:交易链路的稳定性与可靠性探索
背景2022年基于对稳定性的焦虑...和思考交易平台联动中间件平台启动过异地多活项目的探索虽然完成了核心应用及基础组件的改造但在疫情降本增效的影响下并未真正投产同时也缺乏充分的测试以及线上流量的大规模验证后续在不断的业务迭代中相关设计及代码被冲击的面目全非相关的多活自动化测试case也并没有沉淀下来。随着近期外部友商时有严重故障出现比如以上林林总总出现的故障都给我们敲响了警钟必须建设快速恢复的能力。出现问题几乎不可避免但如果能控制影响范围、缩短影响时间也就能把损失降到最低。我经历过的公司做交易的和做中间件的往往是最容易焦虑也最容易心态失衡的两拨技术人一方面所有问题都会暴露在C端用户面前影响范围大且不像toB/toM的场景 避开高峰期甚至有可能无人知晓另一方面流量高压力大容易面临突发流量及突发事件稳定性这根弦需要始终绷紧所以往往是面向稳定性(的焦虑)设计当然熬过去成长也最快。回到我们的现状得物目前的交易应用及中间件基础组件都是基于某云部署且前期为了降低跨机房调用产生的网络损耗较多应用都绑定了存储组件(db/redis/hbase)及核心依赖下游的所在可用区对此为了避免在极端情况下得物的交易主链路出现长时间不可用的情况团队决定提前预防启动同城双活项目。为了避免在极端情况下得物的交易主链路出现长时间不可用的情况团队决定启动同城双活项目目标是快速建设流量动态切换能力及快速恢复能力同时降低改造难度、减少改造工作量不增加大量额外成本。团队讨论决策绕过之前最复杂也最容易出问题的数据同步(db双向同步、redis双向同步等)同时也不需要在流量切换时做db禁写整体具有比较大的可操作可实施性。多说一句同城双活也有做数据双向同步的case当然更彻底--每个机房都有全量的数据及应用某个机房出问题 可以完全自闭环承接流量不过带来的复杂度上升、成本上升也会比较明显所以这次并没有选择这条路。换句话说个人更倾向于小成本低风险快速落地实现从0到1的功能建设而不是大而全的方案万一期间遇到问题只能徒呼奈何。当然在现阶段通过建设相对低风险低投入的同城双活积累更多基础能力的同时锻炼团队选择最合适当下的方案解决目前排在第一位的问题怎么想都觉得还是一件挺划算的事儿。画一幅简图来区分下我们这次同城双活的方案和业界异地双活方案的差异。异地双活主要特点存储相关有两份双机房内各自读写双向同步数据的循环赋值需要重点考虑如何处理数据间的同步延迟问题会比较明显不过各自机房内基本上可自闭环调用对于用户、商家资产的处理比较复杂比如用户券、卖家库存等一般需要考虑在某个机房维护(gzone)避免数据同步问题带来的超卖、超用切流时需要做目标机房的局部数据禁写避免脏数据产生同城双活特点只有一份数据源不需要考虑数据同步的延迟问题及切流时的禁写逻辑不过若数据所在机房出问题另一个机房无法正常承接流量(只能承接部分兜底流量如cdn、缓存等有兜底数据的场景)不需要考虑具备中心节点性质的数据问题如用户券、库存等跨机房访问较多尤其是数据层面的读写可能会造成RT的大幅上涨不管是同城还是异地、双活还是多活(双活只是多活里最简单的场景双活到三活难度飙升范围应该不亚于羊了个羊里第一关和第二关的难度)都是为了以下目标提高可靠性通过在不同的物理位置部署服务减少单点故障的风险。即使一个机房发生故障其他机房也可以接管服务确保业务连续性。负载均衡可以灵活分配用户请求流量避免单个机房过载尤其随着业务规模的扩大 单个云厂商的机房已经无力提供更多资源的情况下。灾难恢复通过流量的调度切换来快速恢复某个机房的故障问题减少业务中断时间。云成本在技术成熟度较高的前提下做同云、跨云甚至云自建IDC机房之间的多活一方面可以降低对某个云厂商的依赖从而获取一定的议价权另一方面多活本身在提高资源利用率方面可以有更多可能性。提高服务质量这点尤其表现在异地多活场景通过在多个中心之间分配流量可以减少网络延迟提供更快的响应时间和更高的服务质量。设计思路一句话描述在云机房的多个可用区(即多个物理机房)中构造应用层面的双集群部署配合目前已经在交易链路大规模上线的蓝绿发布完成流量的动态切换(含HTTP、RPC、DMQ[rocketmq/kafka])。而存储(redis/db)还是在单机房(但是可以跨机房部署)降低方案及实现的复杂度。双活整体架构可以看到整体在架构层面分为四层接入层DNS 域名解析 SLB主备 DLB自研流量网关)DAG(自研业务网关) 多机房部署保障接入层高可用。其中在DAG中实现了根据用户ID、流量比例等控制蓝绿流量的策略。应用层应用通过改造划分为逻辑蓝绿集群通过蓝绿同调的粘性屏蔽跨区调用。中间件层多个中间件组件有各自不同的跨AZ部署策略、数据同步、主动切换策略下面会详述。数据层数据层保持一份数据通过自动/手动主从切换跨区部署等技术手段保障机房级别故障下服务可用包含DB、Redis、Hbase等。具体改造方案本次双活涉及到三个主要部分分别是交易应用侧双活改造、交易依赖方应用双活改造、中间件基础组件改造。下面分别介绍交易应用侧双活改造项目范围\交易侧默认所有服务均参与同城双活改造一方面内部应用之间的调用关系复杂区分处理梳理工作量极高另一方面快速的业务迭代也会改变互相之间的依赖关系维护这套逻辑成本太高以及内部强弱依赖本身也在动态变化让团队的同学不断的识别哪些应该双活、哪些应该单点沟通和执行成本反而更高。业务改造思路及方案\实际业务场景中复杂的链路拓扑最终可以抽象为如下典型的、原子的链路拓扑(A-B-C)的叠加、组合。A、C服务参与双活需要跨可用区部署。B服务不参与双活不需要跨可用区部署。A、B、C服务都需要识别流量染色、服从流量调度。相关服务Owner各自将服务中集成的统一基础框架升级到指定版本接入无侵入、零配置、开箱即用的蓝绿发布能力组件全家桶。保证基于蓝绿发布的运行时流量调度能力被完整集成。上述简图中A、B、C服务需要进行该步骤。相关服务Owner各自在发布平台界面白屏化迁移发布模式。发布模式迁移到蓝绿发布时发布平台自动将服务Pod进行跨可用区部署并在Pod中注入支撑流量调度的进程级元信息。蓝绿发布能力组件在上游调用方LoadBalance时介入进行流量染色、流量调度。上述简图中A、C服务需要进行该步骤。完成上述改造后双活链路上的流量呈现就近调用、可用区封闭的特点即流量染色后后续链路上的每一跳调用都会优先向下游服务集群中与流量同色(同可用区)的实例发起调用。交易依赖方应用双活改造仅仅依靠交易侧应用无法完成所有的P0链路如下单时依赖供应链侧时效。强依赖的外域服务同样纳入了同城双活改造范围。其改造点基本一致不再赘述。中间件基础组件识别机器资源可用区项目初期。我们发现容器POD和ECS缺少可用区标识导致无法区分对应的资源归属。于是我们配合运维组和监控组的同事制定了一份规范。在环境变量里给机器都打上对应的标记同时这也是监控和日志能透出机房标记的基石。中间件RTO同城双活要求中间件在单个可用区出问题的时候仍能对外提供服务。其设计目标的RTO为以下主要组件双活改造方案DLB - 自研流量网关DLB是无状态组件在两个可用区对等部署。当其中一个可用区故障时在SLB的endpoints上故障节点会被剔除流量会打到正常的节点实现故障快速恢复的目标。预计秒级完成。彩虹桥 - 自研分布式关系数据库代理彩虹桥目前不具备自动流量切换能力一方面自动切换过于复杂另一方面也容易带来更多的风险以及也依赖DB层面的主备切换所以走手动切换预计分钟级完成。目前流量99%走A区集群、1%的流量走B区集群当A区发生可用区故障时可手动把流量全部调度至B区集群同时需要DB层完成主备切换(a-b)。DMQ通过Broker分片级别打散到不同的可用区形成一套完整的集群。当可用区故障时集群可用分片会减少一半集群整体可用。DMQ的改造经过了多次试错最开始通过在消费端创建多个consumer group的方式实现但需要业务侧配合多次升级处理且会导致消费端存在双倍的consumer group后面才决定将主要改造工作放在rocketmq broker内部。简要介绍如下蓝绿属性BROKER中的队列设定成偶数并且2. 我们把前一半队列视为逻辑上的蓝色队列后一半队列视为绿色队列(这里也可以看到双活里的很多处理逻辑都是非此即彼但是如果到多活复杂度就会更高)。生产者在进行队列选择时根据集群环境蓝绿颜色进行分组选择a) 蓝集群的消息会被投递的broker的前一半队列中b) 绿集群的消息会被投递到broker的后一半队列中在每种选择逻辑内部是按照轮循的方式进行选择不破坏生产者本身支持的容错逻辑。消费者消费者也是类似。蓝色消费者消费蓝色队列的消息。绿色消费者消费绿色队列的消息。Kafka由于ZK的ZAB协议要求保证 Math.floor(n/2)1 奇数个节点存活才能选出主节点所以 ZK 需要进行3个可用区部署上面的nameserver类似。分散在3个可用区中A:B:C 节点数 2N:2N:1确保始终是奇数个集群节点。Broker 在两个可用区对等部署分区的主从跨区部署。当单个可用区故障时分区leader切换。ESES多可用区部署需要区分数据节点和master节点。数据节点需要保持各个可用区之间节点对等以保证数据的平衡使用分区感应把主副分片隔开保持在不同可用区内。master节点部署在至少三个可用区以保证任何一个可用区挂了都不影响master的选举。注册中心自研分布式注册中心基于raft协议实现系统可用性、数据一致性。承担得物全站RPC服务发布/订阅职责。代理节点多分区部署保障多可用区双活Sylas集群Raft节点3个分区部署保障多可用区双活流量分配策略RPC流量双活的RPC的入口流量在DAG上进行调整DAG会尽量根据用户ID进行流量分配。1. 每个应用会在请求上下文中附上当前的蓝绿标识2. 如果某个应用没有纳入双活范畴这里的蓝绿标识会丢失此时有两种策略a. 随机分配不过会破坏链路的纯洁性b. 根据userID再算一次不过需要增加一次对ark配置的处理。MQ流量比例因为蓝绿集群的生产者和消费者对队列进行了绑定。所以只要调整蓝绿生产者的消息比例就可以调整整个MQ的消费流量比例。而蓝绿生产者的消息比例一般由RPC流量决定。所以调整RPC的流量比例MQ的流量比例也会得到相应的调整。不过会有一定的滞后(5-10s)。上线环节前期准备阶段整体思路确定基于当前的蓝绿发布做双活每次的蓝绿发布过程就是一次双活切流演练避免长久不使用需要用的时候手忙脚乱或者年久失修。服务层做双活部署数据层不做大的改造DB和Redis通过自身的主从切换实现高可用从节点分布在不同的可用区交易域内所有服务核心链路相关外域服务做双活改造梳理所有业务场景、MQ情况、容器部署现状、数据库缓存主从节点可用区现状交易域所有服务以及核心业务场景强依赖的外部服务、强依赖的具体业务场景、可否降级有无兜底MQ使用情况DMQ还是Kafka还是其他、是否需要保证消息的顺序性所有服务当前机器所在可用区、是否绑定固定可用区交易域所有数据库、Redis对应的主节点和从节点分别所在可用区情况依赖zookeeper的job情况评估改动范围上下游非交易域沟通确认必须纳入改造范围的服务、可以不用双活改造的服务必须要有兜底双活涉及到的服务jar升级、未接入蓝绿发布的接入蓝绿发布跨区调用情况下RT上涨明显的接口针对性优化部分业务场景是否需要接入自建Redis的就近读改造运维侧提供自建Redis的就近读方案但是对于数据一致性方面有所牺牲各方根据实际业务场景和接口RT情况综合评估是否需要接入开发验证阶段服务jar升级支持双活蓝绿切流、支持MQ蓝绿发送消费双活蓝绿染色测试环境搭建、测试流程改善环境本身的搭建服务蓝绿集群拆分、绑定可用区、容器蓝绿集群机器比例配置双活蓝绿染色环境代码版本校验、代码准入规则、分支自动合并规则、测试流程流转等将双活蓝绿染色环境定为测试二轮round2环境在日常迭代中常态化回归验证双活流程双活蓝绿染色测试环境回归正常业务流程回归测试环境蓝绿切流回归测试环境MQ生产消费切流回归核心业务接口RT情况记录对比、优化意见双活染色环境全局通道打开情况下蓝绿发布通道切流回归验证通道优先级发布通道优先级 全局通道预发环境集群拆蓝绿此刻预发环境等于已经实际上完成了双活改造预发环境验证RT问题重点关注线上所有双活改造服务单独拆一台机器到B区观察验证RT上涨问题交易平台绝大部分服务之前都是绑定可用区A区每个服务单独部署一台机器到B区观察接口RT情况DMQ升级蓝绿2.0支持按照蓝绿标消费线上准备上线阶段日志平台、监控平台、trace链路、容器升级支持蓝绿标生产环境DMQ切换为蓝绿2.0支持按照双活蓝绿标消费数据库Redis主节点切换保证主从节点只在A区或者B区大部分在在a、b这两个区也有例外。核心是主节点一定要在这两个区线上服务拆分蓝绿集群手动项目正式上线回归验证RT问题关注绿集群A区扩容至100%机器蓝集群B区维持50%机器灰度观察5天线上RT上涨接口技术专项优化发布平台双活保障迭代升级支持新增服务一键加入双活蓝绿集群双活蓝绿集群支持按区批量扩容能力单机房故障情况下快速拉起存活区的服务容器平台支持容器管控多可用区部署项目成果2023年12月14日筹备近100天的交易链路同城双活完成上线经过5天(12.14-12.18)的观察及圣诞前高流量(DLB流量达到双十一的77.8%)的验证确认无明显异常之后线上集群完成缩容。部分场景的RT有一定比例的上涨(数据层面只做了跨可用区容灾但是并没有实现就近访问所以蓝集群的所有数据层面调用都需要跨可用区)已启动技术小项目推动优化中。从实际效果上看经过12.22的大版本发布过程中的跨机房切流交易链路已经具备跨机房流量调度的能力如下流量表现A区 - 绿集群B区 - 蓝集群)两个可用区的集群流量达到了50:50。不过rocketmq 由于存在少量上下游应用并未进行多活改造还有较小流量未严格分布核心指标 qps/rt/错误率核心基础组件访问情况由于所有数据存储(db、redis、hbase)均在 A 区故 B 区的 rt 有一定上涨整体看上浮大概 7-8ms( 存在一次请求 查询多次数据的场景)还在持续推动优化成本情况因A区原有云资源均为包年包月模式停止使用依然会有费用产生同时在B区部署服务稳定性支撑50%流量之前存在5天的并行期A区100%资源、B区50%资源共150%期间共产少量成本。灰度并行期结束后A区资源释放掉50%整体成本回归原有平均线无额外成本产生。带来的新问题及后续蓝绿发布中如果下游接入了双活但没有进入发布通道消费流量会倾斜比如在上游切换流量过程中RPC或MQ会优先本可用区调用也就是另一个可用区流量比例会受影响需要关注每个可用区中冗余的容量评估是否可以支撑全量流量。RT变化对于下游未加入双活、或者某些存储/缓存中间件如DB/Hbase/Redis未开启就近读取B机房的RT会普遍高5-8ms。已在逐步投入优化。容器管控作为基础设施在出现机房级故障的时候需要保证正常运行能够顺利完成扩缩容操作即容器管控面的多可用区部署这块目前还在建设中。机房级故障情况下单机房批量扩容快速拉起是否有足够的可用资源尤其是大促期间云厂商本身资源就吃紧。多个大域之间的双活联动问题比如交易和搜推两个大域双活切流是否需要联动联动影响范围被放大且搜推侧扩容不易不联动各域双活流量非常割裂两个大域之间的是否识别相同的蓝绿标各大域内部自闭环保证同区访问or大域之间也需要保证如何在线上无损情况下进行一次贴近实际的演练。以上问题都是在双活之后带来的新挑战也都在不断的思考及投入解决。不管做什么不管怎么做人生总会有新的问题出现不是么Keep a long-term view lol...*文/Alan 英杰 Matt 羊羽