首页 / 资讯中心 / 文章详情

跨云多活架构设计:从数据同步到流量调度的落地实践

跨云多活架构设计:从数据同步到流量调度的落地实践 ★ FEATURED ARTICLE
1. 为什么跨云多活成了SRE绕不开的话题先说一个最近被反复问到的场景一个原本跑在单朵云上的业务发展得不错日活涨得很快结果某天云厂商一个区域的网络抖动整个服务接近不可用。复盘会上业务方问我们数据不是有备份吗怎么服务还是断了 这时候我一般会解释备份解决的是数据不丢跨云多活解决的才是服务不停。这两件事的复杂度完全不在一个量级。跨云多活这四个字这两年几乎成了SRE团队绕不开的KPI。我所在的团队负责一个日活过千万的内容社区平台前几年陆续做完同城双活、异地容灾最后才真正走到跨云多活。这条路踩了不少坑也沉淀出一些方法论。这篇文章把整个链条拆开讲一遍从架构选型、数据同步、流量调度到切换演练、成本控制给正在做或者准备做的团队一个可以照着参考的路线图。1.1 从“数据不丢”到“服务不停”的演进很多团队对容灾的理解还停留在“数据多备份一份”的阶段。RPO恢复点目标可以做到分钟级甚至秒级RTO恢复时间目标却往往非常长。因为数据备份只是把数据复制到另一个地方真正出故障时你还得重新拉起服务、改路由、做数据校验、观察一段时间再放流量这个过程少则十几分钟多则几个小时。对于内容社区、电商交易这类业务来说小时级的不可用已经足以造成明显的用户流失和口碑影响。跨云多活的核心诉求是把RTO从小时级压到分钟级同时让RPO尽可能接近零。这意味着故障发生时流量可以快速切到另一个云上的可用副本服务不中断用户无感知。注意这里的“活”不是简单的“备”而是两个云上的系统都在承担业务流量任何一个云挂了另一个云能立刻把全部流量接住。这给架构、数据、运维都带来了全新的要求。1.2 跨云多活真正要解决的核心问题跨云多活不是一个独立的技术点而是一整套架构治理体系。它真正要回答的是四个问题第一业务怎么做单元化拆分才能在一朵云故障时把流量整体切走第二数据怎么同步才能让两个云上的系统保持基本一致第三流量怎么调度才能做到可灰度、可控制、可回滚第四故障发生时人怎么决策、系统怎么切换才能不慌乱、不出错云原生在这个场景里的价值在于提供了容器化、服务编排、服务网格、可观测性等基础能力。没有这些能力跨云多活几乎做不起来。但我也见过不少团队以为上了Kubernetes就等于天然多活这是很大的误解。容器化只解决了应用打包和调度的问题数据同步、流量切换、一致性保障这些硬骨头一个都绕不过去。这篇文章适合这些团队参考已经完成了基础容灾正在从“双活”往“多活”进阶的业务对可用性要求高准备引入多云战略但不知道从哪下手的以及被老板一句话“我们也要跨云多活”逼着做技术方案的SRE和架构师。我会尽量讲得具体所有方案都来自实际落地过程中的取舍不是纸上谈兵。2. 跨云多活架构设计的关键选择很多团队上来就问“用什么工具做跨云多活”我的回答通常是工具是最后一步。第一步是先把架构选型想清楚。架构选型一旦错了后面所有工具都是在错误的地基上打补丁。2.1 先分清双活、多活、容灾的区别有一个很常见的混淆把“容灾”和“多活”当成一回事。容灾的本质是“我有备用方案”多活的本质是“多个数据中心/云平台都在承担流量随时可以互为切换”。两者对系统架构的要求完全不同。方案核心思路典型RTO典型RPO成本适用场景备份恢复定期备份 恢复演练小时级分钟到小时级低对可用性要求不高的内部系统同城双活同城两个机房同时提供服务分钟级秒级中延迟敏感、数据强一致要求高的业务异地容灾异地机房保存数据可切换十几分钟到小时级秒级中高防地域级灾难可接受短暂中断跨云多活多云同时承载流量互相切换分钟级秒级甚至准实时高核心业务要求极致可用性需要说清楚的是跨云多活并不适合所有业务。如果一个业务本身允许停服几分钟那做备份恢复就够了强行上多云多活只会带来巨大的成本和技术复杂度。我见过一个内部报表系统也要求“多活”结果数据同步链路比业务本身还复杂最后报表跑出来的数据经常不一致反而得不偿失。合理的选择是核心链路做多活非核心链路做容灾或者降级。2.2 单元化架构与流量分片跨云多活的基础前提是业务可以拆成若干个互相独立的单元。最常见的做法是按用户维度分片把用户ID哈希成若干个分片每个分片对应一朵云上的一个逻辑单元。逻辑单元包含该分片用户所需的应用服务、缓存和数据存储。平时两个云各跑一部分分片一个云故障时把属于它的分片流量整体切到另一个云。这里有一个关键决策点分片粒度到底怎么划分做过一次线上事故之后我的体会是分片粒度不能太粗也不能太细。太粗比如整个业务线作为一个单元切流时影响面太大太细比如按每个用户ID单独管理数据同步和路由规则会爆炸。比较合理的做法是先用几个大分片落地比如“全量用户按ID哈希拆成4到8个分片”每个分片再包含若干个子业务域。这样切换时粒度可控规则也容易维护。单元化架构最容易被忽略的是路由一致性。切换动作发生时入口网关、应用层、缓存层、数据层必须使用同一套路由规则否则会出现流量已经切到B云但某个应用还在读A云缓存的情况。我建议把路由规则做成一个独立的配置中心统一管理由SRE统一发布而不是让每个业务团队自己维护一套。2.3 云原生基础设施层的多集群规划云原生底座方面我们选择的是多套Kubernetes集群每朵云一套集群之间完全独立。这样做的好处是避免了跨云集群的网络延迟也让故障域更清晰。两个云上的集群版本尽量保持一致镜像统一构建这样才能保证切换过去之后行为一致。跨云多活对容器平台提出的额外要求有三个。第一是集群级别的配置一致性所有环境变量、配置项都要通过配置中心下发不能在某个集群里手工改配置。第二是跨云镜像分发镜像仓库需要高可用不能因为一朵云挂了导致另一朵云拉不到镜像。第三是服务发现机制跨云场景下服务名应该全局唯一每个服务实例通过多集群Service Mesh或者自定义注册中心暴露出来。还有一点值得强调多集群不等于多活。很多人以为在另一个云上再部署一套Kubernetes就是多活了但这只是把应用跑起来了流量怎么过去、数据怎么同步、状态怎么保持一致仍然缺一套完整方案。基础设施层只能保证“能跑”真正的多活能力在业务和数据层。3. 数据同步与一致性最难的硬骨头如果说架构选型是跨云多活的骨架那数据同步就是血液系统。这一块做不好其他一切都白搭。我见过不少团队在应用层做得很好一上数据同步就卡住半年最后整个项目搁浅。3.1 数据同步链路的通用设计跨云多活场景下数据层面的通用思路是每个分片的写流量入口在同一个时刻只能落在一朵云上避免双写冲突另一朵云通过异步复制获得数据副本。具体链路一般是这样的应用写入主云数据库数据库变更日志被采集后写入消息队列消费端在另一朵云上把变更回放到从库或者下游存储。这套链路里有三个核心组件变更日志采集、消息队列、回放消费。变更日志采集要尽量和业务解耦比较常见的做法是使用数据库自身的增量日志能力比如开源数据库的binlog/oplog机制采集组件伪装成从库去拿日志。消息队列要跨云高可用本身也需要做容灾不能出现生产云挂了、消息队列也拿不到数据的情况。回放消费必须保证幂等因为跨云网络抖动可能导致重复消息消费端要做去重。我们曾经踩过一个坑B云的从库回放延迟到了几十秒平时看不出来真到切换时才发现数据慢了太多。后来我们加了回放延迟监控并且把同步链路纳入核心SLO一旦延迟超过阈值就告警。这个改动成本很低但价值非常大。3.2 缓存、消息与存储的一致性权衡数据同步不只是数据库的事还有缓存、消息、搜索索引等。业务读取链路通常是先查缓存再查数据库。切流之后如果B云上没有对应的缓存大量请求会直接打到数据库可能把数据库压垮。所以缓存预热是切换流程里的标配动作但预热又会带来另一个问题怎么保证预热的数据是新的这里需要接受一个现实跨云多活很难做到强一致绝大多数场景都是最终一致。对于缓存我们采取的策略是“同步为主、失效兜底”。同步组件把数据库变更回放之后顺带更新B云缓存同时保留消息通知缓存失效的链路防止数据不一致。对于消费消息这类场景消费者在B云重新消费时要注意消息队列的offset一致性尽量做成独立消费组避免互相干扰。存储层的强一致问题是最敏感的。比如交易、余额这类场景如果A云和B云同时处理了同一笔请求就会出现账目不一致。我们的原则是同一份业务数据在同一个时刻只有一朵云可以写另一朵云只提供读能力写入权通过运维发布的“写开关”来全局控制。这样就从源头上避免了双主冲突。3.3 切换时的数据延迟与补偿方案无论同步链路做得多好切换时仍然会有一个数据延迟窗口。假设A云故障A云上最后一批数据还没来得及同步到B云此时把流量切过去B云上就会缺少这几十毫秒甚至几秒的数据。我们当时的处理办法分三层。第一层是切换前置延迟检查切换前持续观察同步延迟如果延迟超过阈值先暂停切换等追平。第二层是数据校验对关键业务表做行数和摘要校验两边数据差异在可控范围后才开始切写流量。第三层是补偿机制A云恢复之后对A云这段时间产生的增量数据做补偿回放再做反向校验。这里不得不提一个教训延迟差不是越小越好关键是能不能在切换前拿到这个数据。我们曾经为了追求毫秒级延迟把同步链路做得极其复杂结果引入的故障反而比解决的问题还多。后来退回到“确认式最终一致”接受几十毫秒到几百毫秒的延迟同时把校验和补偿机制做扎实整体稳定性反而更高了。4. 流量调度与故障切换的落地实操前面讲的都是静态架构接下来这部分是动态能力真出了故障流量怎么切、切多少、怎么回滚。流量调度是跨云多活里最需要预先设计的环节因为在紧急时刻人没有时间临场想方案。4.1 全局流量调度与DNS改造流量调度的第一层是域名解析。传统的DNS有一个很麻烦的问题TTL缓存时间太长全球各地递归DNS缓存了旧IP切换之后用户还会在很长时间内访问旧地址。为了解决这个问题我们做了两件事一是把核心域名的TTL调低同时接受一定的解析性能损失二是引入HTTPDNS方案在客户端或者接入层直接返回IP列表绕过本地递归DNS的缓存问题。第二层是接入网关的调度能力。我们的做法是在网关层维护一张“区域到云单元”的路由表正常情况下不同区域的用户按就近原则进入不同云故障切换时把受影响区域的路由改为全部指向健康云。这种方式比只在DNS层切流更可控因为它能在网关层精确控制流量比例也方便做灰度。还需要考虑长连接。很多业务使用WebSocket或者TCP长连接切换DNS和路由后已经建立的长连接不会自动断开。必须在接入层设计一个“连接主动迁移”机制比如通过服务端下发重连指令或者在网关层做连接排空否则流量看起来切走了存量连接还堆在故障云上。4.2 切换编排从手动到半自动跨云多活切换必须提前做成运维编排脚本。我们第一版是纯手动的靠几个SRE开语音会议对着Runbook敲命令。后来发现紧急情况下人很容易漏步骤而且每个人的操作习惯不一样一锅粥。于是我们把切换流程做成了半自动的编排流水线。切换流水线大致分为五个阶段准备检查同步延迟、集群健康、依赖资源、切读流量先把非写入流量切过去、数据校验确认B云数据基本追平、切写流量打开B云写开关关闭A云写开关、观察确认持续观察错误率、延迟、资源水位。每个阶段都设置前置条件和自动校验条件不满足就暂停而不是盲目往下执行。这里想给一个非常具体的建议切换开关一定要有“熔断返回值”。也就是说如果编排脚本执行到一半发现异常要有一个自动回滚到上一稳定状态的机制而不是人肉去猜当前状态。我们在一次演练中就遇到过编排脚本走到第三步时某个服务版本不一致导致校验失败如果当时没有自动回滚后面就是线上事故。4.3 混沌演练把切换变成肌肉记忆很多团队做完了架构、写好了Runbook就以为万事大吉。但真正的考验是故障真的来了人能不能按流程走完。跨云多活最忌讳的是从没演练过一旦发生真实故障指挥者犹豫、执行者手抖、各种意外状况叠加在一起。混沌演练是我们花了大半年时间坚持下来的事情。刚开始是季度一次后来改为每月一次每次随机选择一个故障场景进行注入。故障注入不只是停一台机器而是模拟更真实的场景断掉一朵云到另一朵云的专线、让数据库主实例宕机、制造数据同步延迟尖峰、让消息队列堆积告警等等。演练结束后必须产出报告列出哪些环节没跟上、哪些脚本有问题、哪些依赖没考虑周全。演练的价值在于提前暴露问题。我们曾经在演练中发现切换完成后某个核心服务依赖的第三方推送通道还留在A云导致通知类业务静默失败。这种问题写在文档里是看不出来的只有真正切一次才能暴露。所以我的态度是宁可每月演练折腾一次也不要等真实故障来教育团队。5. 可观测性与容量成本管理跨云多活一旦运行起来监控和容量就变成了日常管理的重点。很多人容易忽略的是监控系统本身如果只部署在一朵云上那故障发生时监控也看不到了。这是SRE容易犯的一个隐蔽错误。5.1 跨云链路追踪与依赖拓扑跨云场景下一次用户请求可能会经过A云网关、A云应用、B云缓存、B云数据库。如果链路追踪系统没有跨云能力排障时看到的就是一段一段的断点根本拼不出完整调用链。我们做了两类事情一是全链路TraceId透传从网关入口生成TraceId无论请求怎么跨云透传都把这个ID带下去二是监控数据汇聚两朵云的指标、日志、链路数据都汇聚到一个独立的监控中心而不是各自看各自的。这个监控中心本身也要多活。我们的做法是监控数据双写每朵云上都保留一份完整副本。如果监控中心所在云故障可以直接用另一朵云上的数据源继续观测。这个细节很容易被忽略但一旦发生排障效率会下降一个数量级。想象一下你连当前服务的错误率都看不到怎么敢做切换决策。5.2 容量管理与成本控制跨云多活天然会带来容量冗余这也是很多团队不愿意做的原因。我遇到最多的问题是“做多活是不是所有资源都要双倍”答案是“不是”。合理的做法是核心服务在每朵云上都保留能承担全量流量的容量但非核心服务可以按分片或者降级的方式组织不需要每个云都备全量。具体来说我们把服务分成了三类核心服务内容读取、登录、评论发布等每朵云都保持全量容量支撑服务推荐、搜索等在非故障云上可以暂时降级比如牺牲部分个性化能力保证可用性离线任务不做多活以队列延迟换取成本。通过这种方式整体资源冗余控制在1.5倍左右而不是双倍。成本控制上还有两个小技巧。第一非核心的流量在正常情况下可以按权重分散到两朵云保证资源不被闲置同时又留出切换空间。第二容量高峰期可以借助云的弹性能力做短暂扩容而不是常驻等量资源。但要特别注意跨云扩容比同云内扩容慢得多关键业务还是要提前锁定额外容量不要把希望完全寄托在临时扩容上。6. 踩坑实录与排查思路最后分享几个我们踩过的真实问题都是文档里不常写、但实际特别要命的细节。按问题现象、原因、解法来整理方便大家对照排查。6.1 切换后数据未对齐用户看到旧数据现象流量切到B云后部分用户刷到的内容明显是旧的过一阵子又恢复。排查发现是因为缓存预热只做了热点key冷门数据重新查库时回放延迟还没追平导致读到了旧版本。解法一是切换前置检查里把同步延迟阈值从“分钟级”收紧到“秒级”不达标不切换二是给读接口增加“数据版本号”校验查到旧版本时触发主动刷新而不是直接返回给用户。这个方案会增加一点延迟但对内容展示类业务完全可接受。6.2 流量切过去了但连接没过去现象DNS和网关路由都切了B云的新请求确实在涨但A云上的服务连接数一直没降数据库连接和消息连接大量残留A云迟迟不能安全下线。解法排查发现是长连接没有做服务端主动踢出。我们在接入层加了“连接排空”机制切换指令下发后网关会通知所有存量连接进行优雅关闭客户端收到关闭指令后自动重连到新地址。类似地消息消费者也要做rebalance让旧的消费实例自动退出。6.3 业务悄悄绕过多活形成隐形单点现象某次切换时一个看起来不重要的业务在B云上完全没有流量排查发现是某个新的应用上线时配置里直接把消息队列地址写死了指向A云绕过了配置中心。解法这类问题的根源是“配置漂移”。我们把配置中心做成了强制规范所有应用不允许在代码里写任何环境相关的地址必须通过配置中心获取。同时增加了巡检任务定期扫描应用配置中是否出现硬编码的地址或IP。这个坑非常隐蔽建议所有做多云多活的团队都做一次自查。6.4 几个容易被挑战的设计问题最后整理几个在方案评审时经常被问的问题提前想好答案很有用。第一跨云网络延迟高读多写少的场景怎么办答案是分片内就近读跨云只做主从同步写流量尽量收敛到同一朵云。第二数据同步消息队列本身挂了怎么办答案是把消息队列也纳入多活体系或者使用云厂商提供的跨区域复制能力但最好提前验证极限情况。第三切换之后A云恢复了要不要马上切回来答案通常是不用急着切回先让A云作为从属继续追数据观察一段时间再做反向切换避免来回切造成二次抖动。根据我的个人经验跨云多活不是一个“上线即结束”的项目而是一个持续运营的体系。最难的不是最初的建设而是长期维持切换能力和数据一致性。如果你正在做类似的事情我最大的建议是不要先追求完美方案先让切换跑得通再逐步打磨细节。一次成功的演练胜过十次纸上谈兵。
阅读完成 · 觉得有帮助?
咨询建站