知易行难,双活过程中遇到了非常多的问题,但是回过头看很难完美的表述出来,之所以这么久才行文也是这个原因,总是希望可以尽可能的复现当时的思考、问题细节及解决方案,但是写出来才发现能给出的都是多次打磨、摸索之后的我们认为偏合理的方案;不过换个角度看,给大家展示出来一个正确答案,是否有更积极的参考价值呢?
以及,涉及到容器、发布平台、底层网络运维、监控等组件的内容,限于视野及技术能力并未包含在内,仅聚焦在业务团队及中间件组件的设计及改造上。
一 背景
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等。
四 具体改造方案
本次双活涉及到三个主要部分,分别是:交易应用侧双活改造、交易依赖方应用双活改造、中间件&基础组件改造。下面分别介绍:
交易应用侧双活改造
1. 项目范围
交易侧默认所有服务均参与同城双活改造,一方面内部应用之间的调用关系复杂,区分处理梳理工作量极高;另一方面快速的业务迭代也会改变互相之间的依赖关系,维护这套逻辑成本太高;以及,内部强弱依赖本身也在动态变化,让团队的同学不断的识别哪些应该双活、哪些应该单点,沟通和执行成本反而更高。
2.业务改造思路及方案
实际业务场景中复杂的链路拓扑最终可以抽象为如下典型的、原子的链路拓扑(A-B-C)的叠加、组合。
A、C服务参与双活,需要跨可用区部署。B服务不参与双活,不需要跨可用区部署。
A、B、C服务都需要识别流量染色、服从流量调度。
相关服务Owner各自将服务中集成的统一基础框架升级到指定版本,接入无侵入、零配置、开箱即用的蓝绿发布能力组件全家桶。保证基于蓝绿发布的运行时流量调度能力被完整集成。上述简图中A、B、C服务需要进行该步骤。
相关服务Owner各自在发布平台界面白屏化迁移发布模式。发布模式迁移到蓝绿发布时,发布平台自动将服务Pod进行跨可用区部署,并在Pod中注入支撑流量调度的进程级元信息。蓝绿发布能力组件在上游调用方LoadBalance时介入进行流量染色、流量调度。上述简图中A、C服务需要进行该步骤。
完成上述改造后,双活链路上的流量呈现就近调用、可用区封闭的特点,即:流量染色后,后续链路上的每一跳调用都会优先向下游服务集群中与流量同色(同可用区)的实例发起调用。
交易依赖方应用双活改造
仅仅依靠交易侧应用,无法完成所有的P0链路,如下单时依赖供应链侧时效。强依赖的外域服务同样纳入了同城双活改造范围。其改造点基本一致,不再赘述。
中间件&基础组件
识别机器资源可用区
项目初期,我们发现容器POD和ECS缺少可用区标识,导致无法区分对应的资源归属。于是我们配合运维组和监控组的同事制定了一份规范。在环境变量里给机器都打上对应的标记,同时这也是监控和日志能透出机房标记的基石。
中间件RTO
同城双活要求中间件在单个可用区出问题的时候,仍能对外提供服务。其设计目标的RTO为以下:
主要组件双活改造方案
01
DLB - 自研流量网关
DLB是无状态组件,在两个可用区对等部署。
当其中一个可用区故障时,在SLB的endpoints上故障节点会被剔除,流量会打到正常的节点,实现故障快速恢复的目标。预计秒级完成。
02
彩虹桥 - 自研分布式关系数据库代理
彩虹桥目前不具备自动流量切换能力,一方面自动切换过于复杂,另一方面也容易带来更多的风险,以及也依赖DB层面的主备切换,所以走手动切换,预计分钟级完成。
目前流量99%走A区集群、1%的流量走B区集群,当A区发生可用区故障时,可手动把流量全部调度至B区集群,同时需要DB层完成主备切换(a->b)。
03
DMQ
通过Broker分片级别打散到不同的可用区形成一套完整的集群。
当可用区故障时,集群可用分片会减少一半,集群整体可用。
DMQ的改造经过了多次试错,最开始通过在消费端创建多个consumer group的方式实现,但需要业务侧配合多次升级处理,且会导致消费端存在双倍的consumer group,后面才决定将主要改造工作放在rocketmq broker内部。简要介绍如下:
蓝绿属性
BROKER中的队列设定成偶数,并且>=2。我们把前一半队列视为逻辑上的蓝色队列,后一半队列视为绿色队列(这里也可以看到,双活里的很多处理逻辑都是非此即彼,但是如果到多活,复杂度就会更高)。
生产者
在进行队列选择时,根据集群环境蓝绿颜色进行分组选择:
蓝集群的消息会被投递的broker的前一半队列中
绿集群的消息会被投递到broker的后一半队列中
在每种选择逻辑内部是按照轮循的方式进行选择,不破坏生产者本身支持的容错逻辑。
消费者
消费者也是类似。蓝色消费者消费蓝色队列的消息。绿色消费者消费绿色队列的消息。
04
Kafka
由于ZK的ZAB协议要求保证 Math.floor(n/2)+1 奇数个节点存活才能选出主节点,所以 ZK 需要进行3个可用区部署,上面的nameserver类似。分散在3个可用区中,A:B:C 节点数 = 2N:2N:1,确保始终是奇数个集群节点。
Broker 在两个可用区对等部署,分区的主从跨区部署。当单个可用区故障时,分区leader切换。
05
ES
ES多可用区部署,需要区分数据节点和master节点。
数据节点:需要保持各个可用区之间节点对等,以保证数据的平衡;使用分区感应把主副分片隔开,保持在不同可用区内。
master节点:部署在至少三个可用区,以保证任何一个可用区挂了,都不影响master的选举。
06
注册中心
PS:自研分布式注册中心,基于raft协议实现系统可用性、数据一致性。承担得物全站RPC服务发布/订阅职责。
代理节点多分区部署,保障多可用区双活
Sylas集群Raft节点3个分区部署,保障多可用区双活
流量分配策略
01
RPC流量
双活的RPC的入口流量在DAG上进行调整,DAG会尽量根据用户ID进行流量分配。
每个应用会在请求上下文中附上当前的蓝绿标识;
如果某个应用没有纳入双活范畴,这里的蓝绿标识会丢失,此时有两种策略:
a. 随机分配,不过会破坏链路的纯洁性;
b. 根据userID再算一次,不过需要增加一次对ark配置的处理。
02
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%,整体成本回归原有平均线,无额外成本产生。
七 带来的新问题及后续
1. 蓝绿发布中,如果下游接入了双活但没有进入发布通道,消费流量会倾斜,比如在上游切换流量过程中,RPC或MQ会优先本可用区调用,也就是另一个可用区流量比例会受影响;需要关注每个可用区中冗余的容量评估是否可以支撑全量流量。
2. RT变化,对于下游未加入双活、或者某些存储/缓存中间件,如DB/Hbase/Redis未开启就近读取,B机房的RT会普遍高5-8ms。已在逐步投入优化。
3. 容器管控作为基础设施,在出现机房级故障的时候需要保证正常运行,能够顺利完成扩缩容操作,即容器管控面的多可用区部署,这块目前还在建设中。
4. 机房级故障情况下,单机房批量扩容快速拉起,是否有足够的可用资源(尤其是大促期间,云厂商本身资源就吃紧)。
5. 多个大域之间的双活联动问题,比如交易和搜推
两个大域双活切流是否需要联动(联动:影响范围被放大,且搜推侧扩容不易;不联动:各域双活流量非常割裂)
两个大域之间的是否识别相同的蓝绿标(各大域内部自闭环保证同区访问or大域之间也需要保证)
6. 如何在线上无损情况下进行一次贴近实际的演练。
以上问题都是在双活之后带来的新挑战,也都在不断的思考及投入解决。
不管做什么,不管怎么做,人生总会有新的问题出现,不是么?Keep a long-term view lol...