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