异地多活的本质是让多个机房同时承担读写流量,而不是一个主库加多个只读副本。如果只是把异地机房做成冷备或只读节点,虽然能备份数据,却无法降低地域级故障带来的停机时间。真正的多活需要从数据拆分、请求路由、冲突处理三个层面同步设计,否则很容易出现数据不一致、跨机房写放大、故障切换失败等问题。多活架构不是简单复制,它要求每个机房都具备独立处理业务的能力,并且这些机房之间能够协调工作,共同对外提供服务。

一、从单元化拆分开始:确定多活边界
单元化是异地多活最常见的落地方式。核心思路是把用户或业务数据按照某种规则切成多个分片,每个分片归属一个机房单元,同一个单元的读写尽量闭环在本地机房。这样跨机房通信只发生在少量同步链路或管理操作上,不会因为一次用户请求同时打到两个机房而增加延迟。例如按照用户ID哈希取模,将用户流量固定到上海单元或深圳单元。切分维度需要稳定,不建议使用手机号或邮箱这类可能变更的字段,否则迁移成本极高。
单元划分后,每个机房必须部署完整的应用、缓存、数据库和消息队列栈,不能只部署应用层而共享一个中心数据库。这样即使某个单元整体故障,其他单元仍然可以独立处理自己分片内的请求。同时,单元之间需要明确路由规则,哪些请求允许跨单元、哪些必须本地处理,这些规则要在设计文档中固化下来。单元化拆分的粒度也很关键,过粗会导致单元内部数据量过大,过细又会增加管理和同步复杂度。
units:
- id: unit-shanghai
region: cn-east-2
hash_range: 0-499
- id: unit-shenzhen
region: cn-south-1
hash_range: 500-999
二、数据同步链路:如何让多个机房的数据保持一致
多活架构下最棘手的问题不是流量分发,而是数据一致性。一个单元写入的数据需要在其他单元可见,但又不能因为同步延迟阻塞本地事务。常见方案是基于数据库日志的异步复制,将binlog或WAL日志通过消息队列分发到其他单元,消费端重放SQL或事件。这种方式对业务侵入小,但存在秒级甚至分钟级延迟,适合最终一致性场景。对于核心业务,必须明确哪些数据允许延迟,哪些数据必须实时一致。
对于强一致要求的场景,可以采用同步写多数派或Paxos/Raft协议,但要接受跨地域的网络往返时间。更务实的做法是区分数据等级:核心交易数据允许短时不一致但需要冲突检测,账户余额等资金数据则强制在同一单元内闭环,避免跨单元转账引发双花。同步链路还需要配备数据比对任务,定期校验各单元数据是否一致,发现差异后自动修复或告警。数据同步的幂等性同样重要,重复消费、乱序到达都可能导致脏数据。
public void onOrderEvent(OrderEvent event) {
String unitId = event.getUnitId();
if (unitId.equals(currentUnitId)) {
return;
}
// 幂等写入本地数据库,避免重复消费造成脏数据
orderRepository.upsert(event.getOrderNo(), event.getPayload());
}
三、流量路由与故障切换:把请求送到正确的单元
路由层是异地多活的入口,所有请求在到达业务系统之前必须完成单元识别。常见做法是在网关或负载均衡器上根据用户标识计算哈希,并将计算结果映射到对应单元。如果用户请求到达了错误单元,网关可以做两种处理:直接返回重定向响应,或者通过内部专线转发到正确单元。后者虽然对用户透明,但会增加一次跨机房调用,放大故障影响面,因此更推荐使用客户端重定向。路由规则必须支持动态变更,否则每次调整单元都需要重新发布网关配置。
故障切换需要区分单元级故障和网络分区。单元级故障时,应将该单元流量切到备份单元,同时暂停该单元的数据写入,等待恢复后进行数据补齐。网络分区则更加复杂,不能简单地认为对方单元宕机,否则会出现脑裂。需要依赖租约、锁服务或外部仲裁节点来判断谁可以继续提供服务。切换动作必须支持一键执行,而不是依赖人工修改配置。切换过程中还要保证数据同步链路不会反向写入已经故障的单元,避免恢复后出现数据冲突。
upstream unit_shanghai {
server 10.10.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.10.2.11:8080 backup;
keepalive 64;
}
四、容量规划与容灾演练:别让多活变成摆设
很多团队在设计异地多活时只关注功能可用性,却忽略了容量冗余。如果两个单元平时各承担50%流量,当其中一个故障时,另一个必须能扛住100%流量,否则切换后立刻过载,多活就失去了意义。因此每个单元的容量至少按峰值流量的1.5倍预留,同时准备好自动扩容策略,例如基于CPU和连接数指标触发扩容。容量规划还要考虑数据同步链路的带宽消耗,避免在业务高峰期因同步流量过大影响正常请求。
容灾演练是检验多活架构能否真正工作的唯一手段。演练不能只在测试环境跑通,必须定期在生产环境做真实切流。可以先从低峰期开始,逐步扩大切换范围,记录每次演练的RTO和RPO,再根据数据不断优化切换脚本和同步链路。演练过程中发现的问题,比如配置项遗漏、数据延迟过大、监控缺失,都应当作为架构迭代的输入。没有经过真实演练的多活架构,往往在真正故障时暴露出各种意想不到的问题。
异地多活不是一次性项目,而是一个持续演进的工程。随着业务规模增长,单元数量可能需要从两个扩展到四个甚至更多,数据同步链路也要随之演进。架构上要预留扩展点,例如单元路由表支持动态下发,消息队列支持按单元分区扩容。只有把可运维性和可扩展性纳入设计目标,异地多活才能真正扛住区域故障,而不是停留在架构图上的一张漂亮图纸。