ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

异地多活与灾备的本质区别:单元化架构与数据同步实战指南

异地多活与灾备的本质区别:单元化架构与数据同步实战指南 干这一行这么多年每次听到我们要做异地多活这句话我心里都会先咯噔一下。不是觉得这事儿不该做而是大部分说这话的人其实根本分不清灾备和多活的区别。你问他现在机房什么状态他说我们有同城双活还有个异地灾备中心再问灾备中心平时跑流量吗他沉默了。这一沉默问题就来了。这篇文章我想把异地多活这个被讲了无数遍、但落地时仍然到处是坑的架构模式从定义、选型判断、单元化设计、数据同步、故障切换到分层建设掰开揉碎讲清楚。目标读者是正在评估多活方案的系统架构师、技术负责人以及那些被老板一句话我们也搞个多活吧推到风口浪尖的一线工程师。文章不会只给概念我会把真正动手时避不开的细节和判断逻辑都摊开来说。1. 别把灾备当多活两地三中心与异地多活的本质差异很多团队第一次聊多活拿出来的底牌是两地三中心。这方案本身没错但它和异地多活是两种完全不同的东西。搞混了后面所有的设计、投入、考核指标都会走偏。1.1 灾备中心的真实处境建了十年一次都没用过传统两地三中心的典型形态是同城两个机房做双活异地再建一个灾备机房。同城双活一般问题不大因为距离近专线延迟低数据库同步可以做到接近实时。真正的痛点在那个异地的灾备中心。我见过太多这样的案例灾备中心从建成那天起就没承担过一行生产流量日常也不跑业务只在假设发生灾难的应急预案里存在。等到真要启用的时候问题就全冒出来了——配置没同步、账号权限不一致、数据同步链路的延迟比预想高出一个量级、备库和主库的数据永远差着几分钟甚至更久。结果真出了事业务该断还是断灾备中心根本接不住。这里有个很反直觉的现实灾备系统平时越不常演练真到用的时候越靠不住。因为灾备本质上是一套保险不是业务的日常形态它的可用性没有被生产流量持续验证过。你不可能指望一辆放在车库里十年没动过的备胎在高速爆胎时能稳稳跑完剩下的两百公里。1.2 判定多活的硬标准不只是能切而是一直在跑那什么才算真正的异地多活我的判断标准很简单就两条至少两个机房同一套业务同时对外提供服务读和写都在本地机房完成而不是读在本地、写回主中心任意一个机房整体故障时流量能在短时间内自动或半自动地切到其他机房业务不中断且数据不丢或丢的量在可接受范围内。注意第一点里的写也在本地这是和灾备模式最根本的分水岭。很多团队号称做了多活实际上做的是写主读从所有写请求还是回源到同一个中心机房另一个机房只帮忙扛读流量。那叫读写分离异地扩展不叫异地多活。异地多活要求的是每一个接入机房都有完整的写入能力数据通过异步手段在机房之间同步。打个比方灾备模式像是你出门带了个备胎平时四个轮子里的一个压根不转异地多活则是四个轮子同时着地、同时驱动任何一个轮子爆了车还能靠另外三个轮子保持速度继续开。引擎数据同步、转向流量调度、刹车故障切换全都得是常态可用的状态。1.3 为什么读多活、写灾备在实践中长成了主流变种这里也得说句公道话纯正的异地多活工程难度和成本都非常高不是每个业务都扛得住。所以业界真正大规模跑起来的往往是读多活 写主从的变种或者是在单元化架构里把少数全局强一致性的写操作收敛到一个主单元。后面讲到单元化的时候我会再展开。但作为架构师你心里得清楚自己到底做的是哪种多活。你做的到底是全站全业务多活还是核心链路读多活决定了你要不要付出那么大的同步、冲突处理、切换编排成本。如果连这个都没想清楚就照搬别人的多活方案基本等于既花了钱又没买到真正的业务连续性。2. 做异地多活之前先回答四个致命问题多活是个看起来很美的目标但真正动手之前我建议每个团队先回答下面四个问题。回答不清楚后面每一步都是雷。2.1 业务对数据一致性的容忍度到底有多高这是最要命的一个问题也是决定多活方案走向的起点。请记住异地多活的天敌是强一致性。两个机房之间的物理距离决定了光速延迟无法被消灭北京到上海一个来回的RTT差不多要10到15毫秒跨地域再远一点就是几十到上百毫秒。如果一个读请求要先去A机房拿数据、再等B机房的同步确认这体验基本没法用。所以你必须把业务分个三六九等哪些能接受最终一致哪些必须强一致。拿电商举例子商品浏览、购物车、订单列表、用户评论这些走最终一致完全没问题但扣减库存、支付扣款、风控拦截这些一旦出现双写冲突就是资金损失或资损风险强一致的要求不能松口。对后者成熟的做法通常不是硬做跨机房强一致而是把这类操作收敛到一个主单元或者用全局的分布式事务方案宁可牺牲一点写入路径的灵活度也要换取确定性。2.2 数据模型能不能被切成单元多活的前提是数据能切分。你得把用户、订单、商品这类核心数据按照某个维度最常见的当然是用户ID切成多个分片每个机房负责一部分分片。如果业务数据天然是全局强关联的——比如一张全国性的品类配置表所有请求都要读同一份最新的数据——那这个业务就很难真正多活。这个问题我建议在立项阶段就让核心研发逐条业务过一遍别等到架构设计到一半才发现某张表没法分片那才是真正的进退两难。2.3 成本账算得过来吗异地多活不是一个省钱的架构恰恰相反它是用巨额成本换极致可用性。成本至少包括成本项说明专线费用多个机房之间要靠高质量专线打通跨地域专线按月租带宽越大费用越吓人同步带宽数据库binlog、消息队列在主备方向上的持续同步单向就会吃掉大量专线带宽硬件冗余每个单元都要有完整的应用、数据库、缓存、消息队列硬件资源翻倍甚至更多研发投入DRC数据同步平台、路由控制面、切换编排工具、监控水位体系这些都需要专职团队常年维护运维成本故障演练、值班机制、切换验证、容量规划都属于持续的人力投入有些团队做多活做到一半发现成本扛不住了开始砍专线带宽结果同步延迟飙到几十秒故障切换时数据追不上多活变成了多坏。这种例子我见得不少。所以做之前先让财务和运维一起坐下来把账算清楚。2.4 组织和流程能不能支撑起真切换架构上做完了最后卡住的往往是组织。多活的切换不是某个核心开发拍脑袋就能决定的它需要一整套决策链谁来发现故障、谁来评估影响面、谁有权限按下切换按钮、切换之后谁负责验证、回切又由谁同意。没有这些制度平时一切正常看不出问题真出了事就是一片混乱。我见过一个团队异地多活的架构做得相当漂亮数据同步延迟控制在秒级路由规则也有灰度能力。但因为整个切换流程没有明确的责任人和决策机制第一次真实故障演练时值班同学花了四十分钟在群里人等齐所有领导拍板业务已经断了一个小时。所以从第一天起就要把多活的运维流程当成一个独立项目来建设不能只盯着技术方案。3. 单元化架构目前唯一被大规模验证的成熟形态聊完了前提正式进入技术方案。如果你问我现在哪种模式能称得上异地多活成熟的架构模式我的答案很明确单元化架构。这不是什么花哨的新概念而是经过超大规模业务反复验证、被证明能真正落地的形态。3.1 单元化到底长什么样单元化的核心思想是把完整的业务栈——接入层、应用层、缓存、数据库、消息队列——按用户维度整体切割成若干个单元。每个单元都是完整的一份业务垂直切片只负责一部分用户的所有请求。举个例子如果有1000万用户切成10个单元每个单元的水平就是100万用户的完整生命周期数据。用户在一个单元内的读写都在本地闭合不需要跨机房访问数据。单元之间需要共享的只是路由配置、全局数据副本和少量跨单元的异步消息。这样的架构天然把故障爆炸半径限制住了一个单元挂了影响的只有那10%的用户其他单元照常服务流量也可以快速切到别的单元。3.2 分片键怎么选从简单hash到路由中心说到单元化最容易想到的是把用户ID拿来hash取模。这个方案简单但同样踩过坑一旦单元数量要调整比如从5个扩容到8个hash取模会让大量用户的映射关系发生漂移数据搬迁和路由变更的成本会非常高。成熟的做法是引入一个路由中心Gating/Route Service。它维护一张用户到单元的映射表路由规则可以动态调整。这样当用户量增长、某单元容量不足时你可以把部分用户迁移到其他单元而不需要全量重新hash。真实的映射表不能放在每个应用的本地内存里否则规则更新没法实时生效通常的做法是路由中心下发全量路由快照应用本地缓存配合定期刷新和实时推送。3.3 全局路由与就近接入从DNS到接入层都要认人单元化之后最大的问题是怎么让用户的请求精准地到达他所属的单元。这一条链路有三个关键节点DNS/GSLB层面按用户出口IP判断地理位置把流量调度到最近的机房。这一步只能保证就近不能保证正确单元。接入层网关真正干精细活的在这层。网关必须能从请求里解出uid或登录态然后去查路由中心把请求转发到正确的单元。一旦一个用户被路由到非所属单元比如出差场景、IP归属地和用户归属地不一致接入层要做单元间的透传转发但这属于兜底路径不能成为常态。这套机制还有一个隐蔽的大坑路由规则变更必须支持灰度。假设某个单元要下线你要先把它的用户路由一点点迁到其他单元而不是一秒钟切换所有用户的映射。否则一瞬间所有应用本地缓存的路由快照集体失效流量风暴直接把你精心设计的路由中心打垮。3.4 单元内的自治是爆炸半径控制的关键单元化最大的收益不是性能提升而是故障隔离。所以单元在设计上要尽量自治一个用户在一个单元内应该能完成绝大部分操作。如果一次请求进来A单元处理了一半发现要调B单元的数据那这个请求的延迟、失败率、排障难度都会急剧上升而且故障影响会跨单元扩散。自治的意思是缓存、数据库分片、消息队列的消费位点全部按用户维度放在同一个单元内。跨单元的调用应当是低频、异步、最终一致的。比如电商场景里用户在A单元下单但这个用户关注的店铺粉丝数在B单元的数据库里这时不应该同步去B单元查而是通过消息异步地通知B单元更新粉丝数。追求单元内闭环就是追求可用性。4. 数据同步和一致性多活真正的深水区网络、路由、应用层改造都有相对成熟的路数真正让无数团队翻车的是数据同步。毫不夸张地说异地多活做得好不好九成看数据层做得怎么样。4.1 从binlog到DRC双向同步的工程化底座异地多活的数据同步底层最常用的方式是解析数据库的binlog再异步传输到其他机房回放。开源界最有名的就是阿里开源的Canal及围绕它构建的DRCDatabase Replication Center体系。这套体系的核心链路是每个单元的主库产生的binlog被实时采集经过消息队列传输到目标单元再由回放程序写入目标单元的主库。这里有几个工程细节很多人一开始想不到采集程序必须高可用如果采集进程自己挂了binlog没有被及时消费同步延迟就会涨起来切换时数据追不平。传输链路要支持压缩和批量否则专线带宽会被binlog吃干抹净。回放程序要支持幂等因为链路抖动可能导致重复消息。同步延迟是这个体系里最重要的健康指标我在每个多活项目里都要求团队在监控大屏上把数据同步延迟放在最显眼的位置而不是只盯着CPU、内存这些常规指标。4.2 双向同步的冲突处理正常业务也会踩的三种雷单向同步简单麻烦的是双向同步。A机房的更新同步到B机房B机房的更新也同步到A机房如果恰好两边的流量同时改了同一行数据冲突就来了。有三种雷在正常业务流量下就可能爆第一唯一键冲突。虽然用户表按uid分片了但订单表、流水表里可能有全局唯一的业务订单号。如果各个单元各自生成订单号不同单元完全可能生成一样的号。解法是全局发号器订单号里带上单元标识或者由独立的全局序列服务统一发号让每个单元的号段天然不重叠。第二后写覆盖前写的乱序。假设同一条记录先被单元A更新又被单元B更新。两边的binlog传到对方机房回放时如果顺序乱了最终结果可能不是业务期望的最后一次更新的值。成熟的做法是在数据表里增加一个全局自增的版本号或GSN/SN回放时用版本号大的覆盖版本号小的来保证收敛。第三删除和更新互相覆盖。单元A对一条数据先更新后删除单元B在同步间隙里又对同一条数据更新了。最终两边同步完数据到底该存在还是该删取决于回放的顺序。这种问题要靠在业务逻辑层加逻辑删除删除标记的合规设计不能靠物理DELETE硬来。4.3 消息队列跨机房幂等是命门除了数据库消息队列是另一个难题。各单元的业务都可能发MQ消息消息也要通过同步通道广播到其他单元。这中间最怕的不是消息慢而是消息重复和消息错乱。消费方必须做幂等处理。一个订单被创建的消息在单元A消费了一次、在单元B又被消费了一次如果消费方没有做去重就可能生成两条重复的订单。我给团队的硬性要求是所有跨单元消费的MQ消息都必须在消息体里带一个全局唯一的消息ID消费端按这个ID去重重复消息直接丢弃。消息消费位点offset同样要纳入多活体系。如果一个单元的MQ消费位点存在本地流量切走后再切回来就可能出现大量消息被重复消费或者根本不消费的情况。成熟的方案是把关键消费位点同步到远端的存储或者把消费进度做成可重置的切换时接受一部分消息重复靠消费端幂等来兜底。4.4 脑裂与仲裁为什么不能两个机房同时做决定异地多活里最怕的一个词叫脑裂。网络抖动、专线闪断、心跳超时两个机房互相联系不上如果每个机房都认为对方已经挂了我是唯一幸存者那就可能出现两个机房同时对同一份数据做写入等网络恢复一同步冲突重到根本无法自动收敛。成熟的架构对此有一个铁律切换决策权必须收归到一个中心机房自身绝不能拥有自动升主的权限。比如DB层不做自动failover不做自动选主故障是否发生、是否切换、切到哪个单元由独立的控制面和值班决策人统一判断。网络隔离时宁可让部分流量短暂不可用也不能让两边同时接管写流量。再补充一个细节即使做到中心决策仲裁系统自身也要避免单点。控制面本身要部署在独立的仲裁节点上要有表决机制比如多数派确保它不会因为自身故障而误发切换指令。5. 故障切换从监控告警到回切验证的完整链路架构和数据层做完了最后真正检验成色的是故障切换。很多团队的多活方案PPT里写得头头是道一演练就露馅。这里我把切换的全链路拆开讲。5.1 切换前必须算清楚的两笔账RPO和RTO做切换设计RPO恢复点目标允许丢多少数据和RTO恢复时间目标多久能恢复这两个指标必须先定下来。异地多活不代表数据零丢失。受限于光速和物理距离跨地域的异步同步做不到RPO0这是物理规律不是努力就能解决的。绝大多数成熟的多活方案异地场景的RPO会定在秒级或分钟级RTO定在分钟级。我见过一些团队老板上来就要求异地多活RPO必须为0这基本等于要求你把同步复制架到异地结果两边的写入性能都废了。与其在商务上承诺一个物理上做不到的指标不如在架构上把RPO和RTO算清楚然后倒推需要什么样的同步链路、需要多少带宽、需要多快的切换流程。数据同步延迟是RPO最直接的体现所以切换前必须确认目标单元的数据延迟是否已经追到阈值之内。如果延迟还没追平就切流量丢的数据就是板上钉钉的事。5.2 四阶段切换流程发现、决策、执行、验证一套成熟的切换流程我习惯分成四个阶段发现监控系统探测到某个单元的可用性下降数据库连接失败、请求错误率飙升、同步延迟持续拉高。这阶段要的是快但是又不能误报所以要有多重信号互相印证。比如数据库主库不通同时单元内的核心接口错误率连续多个周期超过阈值才判定为疑似故障。决策值班负责人根据预案决定是否切换、切多少流量、切到哪个单元。决策阶段最重要的原则是先恢复后复盘不要在故障现场花时间争论根因。曾经有一次我们某个单元的网络交换机故障第一反应是花20分钟排查结果越查越乱最后切流量仅用了3分钟就恢复了教训极其深刻。执行按预定义的顺序操作一般先切消息消费位点再切写流量入口最后切读流量入口。顺序不能乱。切的时候也要小心尽量不要把全部流量一秒钟灌到目标单元先切5%验证目标单元的容量和数据健康度没问题再逐步放大。验证切完之后不能掉以轻心要持续观察核心业务指标和数据一致性。验证通过才能宣布切换成功。同时回切预案要和切换预案同等重要——等故障单元修复了把流量切回去的流程如果没演练过回切一样会出事故。5.3 演练才是真功夫故障注入的实践细节没有经过演练的多活不能叫多活只能叫多活PPT。我强烈建议把演练常态化、随机化、突袭化。最有效的做法是故障注入直接对生产环境或准生产环境做故障试验比如把某个单元的数据库流量切断、把核心交换机断个几分钟。别怕出乱子演练时出问题好过真实故障时出问题。演练的时间也要刻意打乱白天、凌晨、周末都安排几次因为真实故障从来不会挑你方便的时间。演练结束后的复盘会和故障复盘一样严肃认真把演练中暴露的问题全部录入改进清单约定整改期限。演练最核心的验收标准我一直坚持值班团队能不能在预案规定的时间内用最少的沟通成本完成一次安全可靠的切换和回切。如果能做到多活才是真的活了。6. 分层看多活建设网络、接入、应用、存储、消息各自怎么改多活不是一个单一的技术点而是从用户请求到达你系统那一刻起每一层都要跟着改。这一章我按分层把最关键的改造点列一遍你可以拿去做自查清单。6.1 网络层和接入层从DNS智能解析到流量染色网络层首先要解决就近接入问题。DNS/GSLB设备会根据用户源IP的地理位置把域名解析到不同机房的接入IP上。这里的坑在于用户地理位置并不是他归属单元的天然映射所以接入层必须有能力做二次路由。接入层的网关是整个路由体系里的交通警察。它需要从请求中解出身份标识通常是uid或登录态通过路由中心查到该用户所属的单元然后把请求精确转发。这里有个很多团队忽略的细节要支持流量染色。也就是给某类特定请求打上特殊标记让他们路由到指定单元这样可以做灰度切换和演练验证而不影响真实用户。所有的关键路径都要支持这个能力。6.2 应用层无状态化是铁律本地缓存也要三思应用层的改造核心就一句话把有状态的东西全部外部化。Session不能存在应用本地要放到分布式的Session存储里本地的内存缓存要特别谨慎因为一个单元的宕机可能导致所有本地缓存同时失效流量切到另一个单元时缓存穿透会把对端数据库打崩。应用层还要关注的是跨单元调用的治理。单元化理想状态下是单元内闭环但总有少量跨单元调用绕不开。这些跨单元调用的超时时间、重试策略、熔断阈值都需要单独设计绝不能和本地调用用同一套参数。我一般建议跨单元调用的超时设置远低于本地调用宁可快速失败也不能让一个跨单元调用的延迟把整个请求链路拖垮。6.3 存储层数据库、缓存、搜索索引的跨机房策略数据库这块前面已经提过DRC双向同步这里补充缓存和搜索。缓存比如Redis的跨机房复制相对简单大多数团队都是做缓存双写或者让缓存失效能容忍一定时间。但要注意的是缓存雪崩的风险在多活环境下会被放大切换时大量缓存未命中压力会瞬间打到数据库上所以切流量时要配合渐进式放量。搜索和索引类的数据比如ES通常可以接受分钟级的延迟。如果业务对搜索结果的实时性要求没那么高最简单的策略是每个机房部署一份由数据同步管道定期比如每30秒到1分钟同步增量更新。如果要实时性就得付出双倍的同步成本和稳定性代价。6.4 消息层发送落库、消费幂等、位点同步消息层前面重点讲了幂等这里再补两个实操要点一是发送方必须保证先落库再发消息。如果业务先发消息、数据库事务后面才提交消息一旦被消费方提前处理就会读到未提交的数据反过来如果数据库提交了但消息没发出去消息就丢了。成熟的方案是本地消息表或事务消息保证数据库操作和消息发送的原子性。二是消费位点要能跨机房接力。如果一个单元挂了它消息队列里的消费进度在另一个单元要怎么接管如果消费位点只存在本地换一个单元就没法从正确的位点继续消费。我见过一个团队干脆在切换时重置消费位点到几分钟前的位置靠消费端的幂等逻辑把这些重复消息吃掉。这招虽然粗暴但确实避免了位点不同步导致丢消息的更大问题。7. 我这几年被现实反复教育的几件事最后聊点经验教训。多活架构做得越多越发现真正难的不是那些高深的技术方案而是那些看起来不起眼的细节。第一数据同步延迟的监控系统一定要在项目的第一天就搭好不要等上线后再补。很多团队做多活第一个月痛点在路由第三个月痛点在数据冲突半年后最大的痛点变成了我不知道现在两个机房的数据到底差了多少。没有延迟水位监控切换就是盲人摸象。第二别小看容量规划。多活的每一个单元都得有独立扛下全部流量的能力否则切换过去就是二次故障。我做过的最极限的一次压测是把一个单元的流量整个切到另一个单元结果目标单元扛住了但目标单元的依赖服务扛不住一连串的雪崩差点把整个平台打挂。从那以后每个单元的容量上限我都要求留出至少30%的冗余。第三如果业务还没到那个规模我不建议硬上异地多活。同城双活加完备的备份恢复体系对大多数业务已经足够了。异地多活是给那些拆不掉的、断不起的业务准备的不是给看起来需要的业务准备的。启动多活项目之前先问自己一个问题你敢不敢当着全体值班同学的面在随机时间按下一个机房的切换按钮而且每隔几个月都来一次还能保证顺利回切。如果答案是有犹豫那就先把基础打好再上。
返回列表