在多云或同城双活架构中,“专线被挖断”几乎是每个架构师的噩梦。当连接两个云机房的跨云专线完全中断时,两边的机房会瞬间失去通信,形成“网络孤岛”。
这时候,原本统一的服务治理系统会陷入**“脑裂”(Split-Brain)状态。如果两边的业务服务依然各自接收写请求,并尝试往本地数据库里写数据,等到专线修复、数据双向同步重建时,就会产生灾难性的数据覆盖和冲突**。
作为微服务核心的 Nacos 注册中心,在这种极端场景下该如何配合整体架构,防止数据不一致的发生?本文将从控制面隔离、数据写保护、流量剪枝三个维度,拆解一套经过生产检验的应对方案。
一、 厘清本质:Nacos 脑裂并不直接等于数据脑裂
首先要纠正一个常见误区:Nacos 发生脑裂,并不直接导致业务数据不一致。
Nacos 是服务注册中心,存储的是服务实例的 IP 地址和元数据。
- 在 Nacos 的 AP 模式(Distro 协议)下,当专线中断,A、B 两个云机房的 Nacos 集群无法通信,它们会各自把对方机房的实例判定为“健康度未知”并最终剔除,只保留本地机房的实例。
- 此时,两个机房的服务都还活着,Nacos 也都能正常提供本地注册和发现服务。
真正的灾难在于服务路由和数据库。
如果你的业务是“双活”的,即 A、B 两边都能写数据库。专线断开后,由于 Nacos 依然在正常工作,A 机房的服务会继续往 A 数据库写,B 机房的服务往 B 数据库写。由于底层的数据库同步链路(如 Otter、Canal 或 DRS)也断了,两边的数据彻底分叉,这才是致命的**“数据脑裂”**。
因此,解决这个问题的核心逻辑是:利用 Nacos 的感知能力配合应用层、数据层,在网络中断的瞬间,强行关闭非主控制区的写入权限。
二、 第一道防线:利用 Nacos 元数据实现“同机房就近路由”
在专线正常时,为了提高容灾能力,我们可能会允许跨云调用。但一旦专线中断,跨云调用必须立刻被切断,否则会导致大量的连接超时,拖垮整个服务雪崩。
我们必须在 Nacos 配置中推行**同机房优先(Zone-Aware)**路由策略。
1. 实例打标
在服务的 application.yml 中,利用 Nacos 的 metadata 为实例注入所在机房(Zone)的标签:
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
metadata:
zone: cloud-a # B机房则配置为 cloud-b
2. 自定义负载均衡器
在 Spring Cloud Gateway 或 Ribbon/Spring Cloud LoadBalancer 中,重写负载均衡策略:
- 优先调用同 Zone 实例:从 Nacos 获取服务列表时,过滤出与当前服务实例
zone标签一致的实例。 - 跨机房降级:只有当同 Zone 实例完全挂掉,且专线健康时,才允许跨 Zone 调用。
- 专线断开瞬间:当过滤出的本地实例无法满足调用,或者跨 Zone 调用报错达到阈值时,直接快速失败(Fail-Fast),不再向跨云实例发起重试。这能避免请求在断开的专线上死等,瞬间释放线程资源。
三、 第二道防线:数据库“单活写”与“只读降级”
解决脑裂的核心,是保证任何一个数据分片,在同一时刻只能有一个机房有写权限。 常见的架构有两种应对策略:
策略 A:主备模式(Active-Standby)下的“强行只读”
如果你的业务采用的是“A机房主写,B机房备份”的模式,底层的数据库(如 MySQL)主库在 A 机房,B 机房通过只读从库提供读服务。
当专线中断:
- B 机房服务完全失去与 A 机房主库的连接。
- 此时,B 机房的应用服务如果尝试写数据库,会抛出数据库连接超时异常。
- 防脑裂配置:B 机房的 API 网关或应用层拦截器必须捕获此类异常,并触发全局只读开关。
- B 机房的 Nacos 配置中心(Config)立刻推送降级配置,将 B 机房所有写接口(POST/PUT/DELETE)直接拦截,返回
503 Service Unavailable或 “系统繁忙,请稍后再试”,仅保留读接口。
这样,虽然 B 机房失去了写功能,但保证了数据不会在两边同时产生变更,保住了数据一致性的底线。
策略 B:单元化架构(Unitization)下的“哈希分片”
对于大厂的核心系统,通常采用单元化架构(如阿里的 SET 架构)。
- 业务流量按照某个维度(如
user_id)进行哈希分片。 00-49的用户流量归属 A 机房(单元),所有读写操作都在 A 机房的数据库完成。50-99的用户流量归属 B 机房(单元),读写在 B 机房完成。
当专线中断时:
- A 机房和 B 机房各自成了独立的孤岛。
- 由于数据是物理分片的,A 机房写 A 的数据,B 机房写 B 的数据,它们之间天然不存在冲突。
- 挑战在于跨单元请求:如果一个 A 机房的用户(
05)因为某种原因(如 GSLB 调度错误)访问到了 B 机房,B 机房检测到该用户不属于本单元,且此时专线不通、无法路由回 A 机房,B 机房网关必须立刻拒绝该请求,绝对不允许越权写入。
四、 第三道防线:流量入口“快速剪枝”
当专线中断,最怕的是上游流量依然源源不断地送往“非主控机房”或“已降级的单元”,造成用户体验的严重撕裂。
1. 智能 DNS / GSLB 联动
在流量的最前端,DNS 或 GSLB(全局负载均衡器)需要对两个云机房的入口网关进行健康检查(拨测)。
- 拨测不仅要检查网关端口,还要检查跨云专线探测接口。
- 一旦探测到专线中断:
- 在主备模式下,GSLB 应该在 30 秒内,将原本解析到 B 机房的所有写流量,100% 切换到 A 机房(主写机房)。
- 在单元化模式下,GSLB 保持分流,但对于无法路由的跨单元请求,由各机房网关在入口处直接拦截。
2. 结合 Nacos 监听事件的主动隔离
在应用内部,可以写一个轻量级的健康检查组件,定时检测对端 Nacos 节点的连通性。
// 伪代码:监听 Nacos 连接状态或定时检测对端机房心跳
public class CloudLinkMonitor {
@Scheduled(fixedRate = 2000)
public void checkCrossCloudLink() {
boolean pingSuccess = pingRemoteGateway();
if (!pingSuccess) {
// 发现跨云链路断开,立刻触发本地状态机变更
LocalState.setLinkStatus(LinkStatus.DISCONNECTED);
// 联动通知本地网关,开启只读模式或强行熔断跨云路由
disableCrossCloudRoutes();
}
}
}
五、 灾后重建:专线恢复后的“数据对账”
专线修好后,Nacos 会在几秒钟内通过 Distro 协议自动重新同步实例,多云互通服务会自动恢复。但数据层的恢复必须非常谨慎。
不要在专线刚通的瞬间就盲目打开所有的写开关。应该遵循以下步骤:
- 保持写保护状态:继续维持非主控机房的只读/降级状态。
- 追赶数据延迟:让底层的数据同步工具(如 DRS/Canal)全力追赶积压的 Binlog。
- 数据一致性校验:通过自动化对账脚本,抽样校验两边关键业务表的数据一致性。
- 逐步放开流量:确认延迟降为 0 且数据无冲突后,先放开 10% 的写流量,观察无误后,再完全恢复双活。
总结
多云架构防脑裂,本质上是一个**“舍卒保车”**的哲学。
在极端断网情况下,CAP 定理起到了决定性作用。我们必须放弃一部分可用性(Availability),通过 Nacos 的同机房就近路由避免连接雪崩,通过数据库的“单活写/只读降级”或“严格单元化分片”确保一致性(Consistency)。宁可让部分用户看到“系统维护中”,也绝不能让两边数据库同时写入冲突的数据。