JavaK8sMySQLRedisZooKeeper

跨机房单元化部署与数据分片架构

双活+单元化架构实战:从同城双活到异地多活,流量调度、数据同步与故障自愈的完整方案

一、项目概述

1.1 背景与行业痛点

随着业务规模的持续增长,单机房部署模式的脆弱性日益凸显。2022年某头部互联网公司因光缆被挖断导致全站故障持续 8 小时,直接损失估算超过 1.2 亿元,品牌口碑损失更是难以估量。这类事件为整个行业敲响了警钟:单机房故障不再是小概率事件,而是每个快速增长的企业都必须正视的现实威胁。

常见的单机房故障场景包括:电力系统失效(UPS电池耗尽、发电机故障)、网络基础设施故障(光缆断裂、DDoS攻击导致 BGP 瘫痪)、自然灾害(火灾、洪水、地震)、以及人为事故(误操作、配置错误)。任何一种场景都可能将业务置于 RTO(Recovery Time Objective)= 数小时 的险境。

与此同时,监管合规对数据本地化提出了更高要求。《数据安全法》和《个人信息保护法》实施后,部分业务数据必须存储在特定地域的数据中心,跨地域部署不再是可选项,而是必选项。这进一步推动了跨机房多活架构的落地进程。

1.2 核心矛盾与技术挑战

  • 跨机房数据同步延迟:同城机房间延迟约 <5ms,异地机房间延迟可达 30-50ms,同步方案设计必须充分考虑延迟对业务的影响
  • 网络分区(Network Partition):两个机房之间的网络中断,如何保证数据一致性和业务连续性?CAP 定理中 C 和 A 的取舍是核心决策
  • 流量调度的公平性:如何确保用户请求在多机房之间公平分配,且在故障时能够快速切换?DNS 缓存、Anycast 路由都是需要深度考虑的因素
  • 一致性保障:单元化架构中,相同用户的所有请求必须路由到同一单元,同时又不能牺牲跨单元的数据可见性
  • 运维复杂度激增:四机房部署意味着配置管理、监控告警、故障定位的复杂度呈指数级上升,需要强大的自动化运维能力

1.3 架构演进路径

冷备(Cold Standby)

备机房仅存储数据快照,故障时手动拉起,RTO 数小时,数据丢失以天计

2019前
热备(Hot Standby / 主从)

从库实时同步,主库故障时从库提升为主,RTO 分钟级,数据丢失以分钟计

2019-2020
同城双活(Active-Active)

双写数据同步,业务流量各 50%,RTO 秒级,网络分区时需人工决策

2021-2022
异地多活(Multi-Active)

主备机房异步复制,跨地域容灾,支持城市级故障切换,数据冲突需处理

2023-2024
单元化(Cell-Based Architecture)

按用户 ID 路由,单元间无跨单元调用,线性扩展,支持 100+ 单元

2025+

1.4 量化业务目标

RTO=0故障恢复时间目标
RPO=0数据恢复点目标
<30s故障自动切换时间
99.99%全年可用性目标
💡 架构决策:RTO=0 并不意味着完全没有故障,而是故障切换对用户完全透明。这要求系统在检测到机房故障后,能够在 30 秒内完成 DNS 切换、流量调度和数据库切换的全过程,且用户侧无感知。这意味着故障检测、健康检查、流量调度三个环节必须高度自动化,不能依赖人工介入。

二、技术架构设计

2.1 四层架构总体设计

系统采用四层架构:流量接入层 → 流量调度层 → 业务逻辑层 → 数据存储层。整体设计遵循"单元封闭"原则,即一个用户的所有请求在同一单元内闭环处理,单元之间无跨单元调用,确保故障隔离和线性扩展能力。下图展示了四层架构在跨机房多活场景中的整体协作关系:流量先经 GSLB 智能调度进入最近的机房网关,再由 Cell Router 按用户 ID 路由到对应单元,每个单元内的业务服务闭环处理后写入本机房的数据节点。

flowchart TB classDef access fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#ffffff classDef dispatch fill:#16213e,stroke:#7b61ff,stroke-width:2px,color:#ffffff classDef cell fill:#1a1a2e,stroke:#00d4ff,stroke-width:1.5px,color:#ffffff classDef data fill:#16213e,stroke:#7b61ff,stroke-width:1.5px,color:#ffffff classDef global fill:#0a0e27,stroke:#7b61ff,stroke-width:1.5px,color:#ffffff User(["🌐 用户请求"]):::access DNS["DNS 智能解析 / HTTPDNS
GEO 定位 + 健康过滤"]:::access LVS["LVS 四层负载均衡
主备集群"]:::access NGX["Nginx 七层网关
SSL 终结 + WAF + Lua"]:::access subgraph "流量调度层 (Global)" direction TB Apollo["Apollo 配置中心
灰度切流策略 / 权重下发"]:::global ZK["ZooKeeper 集群
健康心跳 + Leader 选举"]:::global end CellRouter["Cell Router
user_id % N → 单元 ID"]:::dispatch subgraph "业务逻辑层 (Cell 单元)" direction LR C1["Cell-1
用户 / 订单 / 支付 / 商品"]:::cell C2["Cell-2
用户 / 订单 / 支付 / 商品"]:::cell C3["Cell-3
用户 / 订单 / 支付 / 商品"]:::cell C4["Cell-N
用户 / 订单 / 支付 / 商品"]:::cell end subgraph "数据存储层" direction LR MGR["MySQL Group Replication
三节点 Paxos 强一致"]:::data Redis["Redis Cluster
单元私有缓存"]:::data Kafka["Kafka 跨单元消息
最终一致性通道"]:::data end User -->|HTTPS| DNS DNS --> LVS LVS --> NGX NGX -.读取权重.-> Apollo NGX -.心跳注册.-> ZK NGX --> CellRouter CellRouter --> C1 CellRouter --> C2 CellRouter --> C3 CellRouter --> C4 C1 --> MGR C2 --> MGR C3 --> MGR C1 <--> Redis C2 <--> Redis C3 <--> Redis C1 -.异步复制.-> Kafka C2 -.异步复制.-> Kafka C3 -.异步复制.-> Kafka
流量接入层

DNS 智能解析 → Anycast 路由 → LVS 四层负载均衡 → Nginx 七层反向代理 → 请求解析

流量调度层(Global)

流量调度中心(Apollo 配置)→ 健康检查(ZooKeeper 心跳)→ DNS 动态更新 → 灰度切流策略

业务逻辑层(Cell 单元)

单元内自包含:用户服务 + 订单服务 + 支付服务 + 商品服务。单元间通过 MQ 异步交互(最终一致性)

数据存储层

MySQL Group Replication(跨机房三节点)| Redis Cluster(单元私有)| ZooKeeper(全局协调)| Apollo(配置中心)

基础设施层

K8s 多集群(每机房一集群)| Ceph(分布式存储)| Kafka(跨单元消息)| Prometheus + Grafana

2.2 同城双活架构

同城双活是两个机房位于同一城市核心网络区域内,网络延迟 <5ms,具备构建强一致性双活的基础条件。下图展示了同城双活的完整流量与数据路径:用户请求经 GSLB 智能解析后 50/50 分配到机房 A 和机房 B,两个机房同时接收写请求,通过 MySQL Group Replication 实现强一致性数据同步;任意机房故障时,GSLB 在 30 秒内将流量切换到健康机房,由 ZooKeeper 选举确定 Leader 节点保证集群一致性。

flowchart TB classDef gw fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#ffffff classDef dc fill:#16213e,stroke:#7b61ff,stroke-width:2px,color:#ffffff classDef db fill:#1a1a2e,stroke:#00d4ff,stroke-width:1.5px,color:#ffffff classDef ctrl fill:#0a0e27,stroke:#7b61ff,stroke-width:1.5px,color:#ffffff classDef fault stroke:#ff4444,stroke-width:2px,stroke-dasharray: 5 5,color:#ff4444 U(["用户请求"]):::gw GSLB["GSLB 智能解析
GEO + 健康状态 + 权重"]:::gw subgraph "机房 A (主)" direction TB A_LB["LVS + Nginx 网关"]:::dc A_SVC["业务服务集群"]:::dc A_MGR["MySQL MGR 节点 A"]:::db end subgraph "机房 B (备)" direction TB B_LB["LVS + Nginx 网关"]:::dc B_SVC["业务服务集群"]:::dc B_MGR["MySQL MGR 节点 B"]:::db end subgraph "仲裁机房 (Arbiter)" direction TB C_MGR["MySQL MGR 节点 C"]:::db end subgraph "全局控制" direction TB ZK["ZooKeeper
Leader 选举 / 健康心跳"]:::ctrl Apollo["Apollo
DNS 权重动态下发"]:::ctrl end U -->|50% 流量| GSLB GSLB -->|机房A| A_LB GSLB -->|机房B| B_LB A_LB --> A_SVC B_LB --> B_SVC A_SVC <-->|Paxos 强同步
RTT < 5ms| A_MGR B_SVC <-->|Paxos 强同步
RTT < 5ms| B_MGR A_MGR <-->|多数派确认| C_MGR B_MGR <-->|多数派确认| C_MGR A_SVC -.同步写.-> B_MGR B_SVC -.同步写.-> A_MGR A_LB -.心跳.-> ZK B_LB -.心跳.-> ZK ZK -.健康状态.-> GSLB Apollo -.权重调整.-> GSLB A_LB -.机房A故障.-> GSLB:::fault
  • 双写数据同步:两个机房同时接收写请求,通过 MySQL Group Replication 实现强一致性复制
  • 流量 50/50 分发:DNS 智能解析根据用户地理位置将请求路由到最近机房,同时保证负载均衡
  • 故障自动切换:健康检查发现主机房不可达时,自动将流量切换到备用机房,切换时间 < 30 秒
  • 网络分区保护:当网络分区发生时,通过 ZooKeeper Leader 选举确定主机房,未连接主节点的机房自动降级为只读

同城双活的挑战在于 CAP 取舍:当网络分区发生时(两个机房之间网络中断),系统面临 CP(一致性+分区容忍)或 AP(可用性+分区容忍)的选择。我们的策略是默认选择 CP——在网络分区期间,从机房降级为只读,保证数据强一致;仅当业务明确允许最终一致性时,才允许从机房继续写入(通过冲突解决机制)。

2.3 异地多活架构

异地多活应对城市级灾难(地震、洪水、大规模停电)。主备机房相距 300-800 公里,网络延迟 30-50ms,不适合强一致性同步。异地多活采用最终一致性架构:

  • 异步数据复制:主机房写操作通过 Kafka 异步同步到备机房,复制延迟约 1-5 秒
  • 冲突数据处理:双写冲突使用"最后写者胜出(LWW, Last Write Wins)"策略,通过业务时间戳或全局序列号解决
  • 跨机房查询:读请求优先访问本机房数据,热点数据通过 Redis 跨机房复制加速
  • 城市级故障切换:主机房整体不可用时,备机房接管全部流量,RTO < 5 分钟(因涉及数据补齐)
💡 CAP 定理的工程实践:异地多活的核心矛盾在于"可用性"和"一致性"的权衡。我们的设计理念是:同机房内追求强一致,跨机房接受最终一致。用户在一个机房内的写操作立即可见,跨机房复制通过异步消息保证最终一致性。对于强一致性要求的场景(如支付),通过分布式事务(TCC 或 SAGA)协调跨机房操作。

2.4 单元化架构设计

单元化(Cell-Based Architecture)是跨机房多活架构的终极形态,目标是支撑业务线性扩展到 100+ 单元,每个单元完全自包含,单元之间无同步调用。下图展示了基于一致性哈希环的单元路由设计:用户 ID 哈希到固定单元,相同用户的所有请求路由到同一单元;新增单元时,仅需迁移一致性环上相邻的虚拟节点(影响范围 < 10%);全局报表/分析走只读副本(MySQL Binlog 同步),不侵入单元内事务;跨单元交互仅通过 MQ 异步消息(最终一致性)实现,彻底避免同步跨单元调用带来的延迟和可用性风险。

flowchart LR classDef user fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#ffffff classDef cell fill:#16213e,stroke:#7b61ff,stroke-width:2px,color:#ffffff classDef global fill:#0a0e27,stroke:#7b61ff,stroke-width:1.5px,color:#ffffff classDef msg stroke:#00d4ff,stroke-width:2px,color:#ffffff,stroke-dasharray: 4 3 classDef newnode fill:#16213e,stroke:#00ff88,stroke-width:3px,color:#ffffff U1(["用户 U-1001"]):::user U2(["用户 U-2048"]):::user U3(["用户 U-3096"]):::user U4(["用户 U-5099"]):::user Router["Cell Router
Consistent Hash(user_id)"]:::global subgraph "一致性哈希环" direction LR C1["Cell-1
Redis+MySQL+Services"]:::cell C2["Cell-2
Redis+MySQL+Services"]:::cell C3["Cell-3
Redis+MySQL+Services"]:::cell C4["Cell-5 (新增)
虚拟节点迁移 < 10%"]:::newnode end U1 -->|hash 命中| Router U2 -->|hash 命中| Router U3 -->|hash 命中| Router U4 -->|hash 命中| Router Router -->|user_id%N| C1 Router -->|user_id%N| C2 Router -->|user_id%N| C3 Router -->|迁移后命中| C4 C1 -.MQ 异步.-> C2 C2 -.MQ 异步.-> C3 C1 -.MQ 异步.-> C3 subgraph "全局只读副本" Binlog["MySQL Binlog 同步
报表/分析/对账"]:::global end C1 -.Binlog.-> Binlog C2 -.Binlog.-> Binlog C3 -.Binlog.-> Binlog
  • 数据路由隔离:按用户 ID 取模(cell_id = user_id % cell_count)将用户路由到固定单元,相同用户的所有请求在同一单元内处理
  • 单元封闭性:业务逻辑在单元内闭环,跨单元交互通过异步消息(MQ)实现,避免同步跨单元调用带来的延迟和可用性风险
  • 单元等比扩容:新增单元时,通过用户迁移实现数据重分布,迁移过程对用户无感知
  • 全局数据只读副本:报表、分析等需要聚合全量数据的场景,通过全局只读副本(MySQL Binlog 同步)支持,不影响单元内事务

单元化架构的关键挑战在于"新增单元时的用户迁移"。我们采用 一致性哈希环 替代简单取模:每个用户在一致性环上有多个虚拟节点,新增单元时只需迁移部分虚拟节点,影响范围控制在 < 10% 的用户。

2.5 流量调度架构

DNS 智能解析

用户请求到达本地 DNS → 递归 DNS 查询权威 DNS → 权威 DNS 根据 GEO 定位返回最近机房 IP,同时考虑机房健康状态和负载情况

Anycast 路由

跨运营商 Anycast 路由,用户流量路由到物理距离最近的机房出口,减少跨运营商延迟

LVS 四层负载均衡

LVS 集群(主备部署)将请求分发到 Nginx 网关层,支持会话保持(Cookie 插入)和加权轮询

Nginx 七层网关

SSL 终结、请求限流、WAF 防护、用户 ID 提取、Cookie 解析、路由到对应单元的微服务

单元路由(Cell Router)

根据用户 ID 在 Nginx 层通过 Lua 脚本计算目标单元 ID,相同用户的所有请求路由到同一单元

流量调度的关键挑战是 灰度切流:当需要将部分流量从主机房迁移到备用机房时,如何保证迁移过程平稳且可回滚?我们设计了五步灰度切流策略:

  • Step 1(1%):DNS 权重调整,将 1% 流量切到备用机房,观察 30 分钟
  • Step 2(5%):扩大至 5%,观察 1 小时,关注错误率、延迟、业务指标
  • Step 3(20%):扩大至 20%,如有问题立即回滚(DNS TTL=60s,5分钟内可完全回滚)
  • Step 4(50%):主机房降级为备用,新写入流量主要写入备用机房
  • Step 5(100%):主机房完全下线,备用机房接管全部流量

三、核心技术挑战与解决方案

挑战一:跨机房数据同步的一致性保障

同城双活的核心难题是:两个机房同时写入同一行数据时,如何保证最终数据一致?传统的异步复制(如 MySQL 半同步复制)在网络延迟增加时性能急剧下降;而异步复制(如 MySQL Binlog 异步复制)则无法保证数据不丢失。更复杂的是,CAP 定理要求在网络分区发生时必须做出 C/A 取舍。

✅ 解决方案:MySQL Group Replication + 冲突检测与解决

MySQL Group Replication(MGR)是 MySQL 官方提供的基于 Paxos 协议的分布式一致性解决方案。在三节点跨机房部署中,每个机房一个 MySQL 节点,通过 Paxos 协议保证写入的全局有序。任何一笔事务必须得到多数派节点(3 节点中至少 2 节点)的确认才返回成功。

冲突检测(Certification):MGR 在事务提交前执行冲突检测,通过主键和唯一索引判断是否存在写写冲突。如果两个机房同时修改同一行数据(概率极低但存在),后提交的事务会被 abort 并重试。用户层面看到的是:冲突的事务被回滚并重试,最终只有一笔写入成功。

CAP 取舍策略:我们采用"可配置一致性级别"的策略——默认使用多数派确认(强一致),对于延迟敏感的只读查询可配置为"单节点确认"(最终一致)。配置变更通过 Apollo 实时下发到所有应用节点。

📊 效果数据:跨机房写入 P99 延迟从 35ms 降至 12ms(通过-group_replication_compression、-group_replication_message_cache 参数调优),数据冲突率 < 0.01%,无数据丢失。

挑战二:DNS 缓存导致故障切换延迟

DNS 是互联网流量调度的核心基础设施,但 DNS 缓存机制(Local DNS 缓存、浏览器缓存、操作系统缓存)导致故障切换时用户可能持续被路由到故障机房。默认 DNS TTL 通常为 300-3600 秒,意味着故障发生后,用户可能在 5 分钟到 1 小时 内无法恢复。传统的"降低 TTL"方案会显著增加 DNS 查询压力,且无法根治递归 DNS 的缓存问题。

✅ 解决方案:HTTPDNS + DNS 主动探测 + 商业 DNS 管道

HTTPDNS(阿里云/腾讯云 HTTPDNS):绕过传统 DNS 解析链路,应用直接通过 HTTP 接口向权威 DNS 服务器请求域名解析。HTTPDNS 返回的 IP 是实时计算的,考虑了机房健康状态和地理位置,缓存时间可以设置为 0-60 秒。iOS/Android SDK 内置 HTTPDNS,SDK 每 30 秒主动刷新解析结果,故障时可在 30 秒内完成切换。

DNS 主动探测 + 动态更新:自研 DNS 探测 Agent 部署在每个机房和多个运营商的探测点,每 5 秒对所有机房进行 HTTP 健康探测。探测到主机房不可达时,立即通过 DNS 服务商的 API 更新 DNS 记录,将故障机房的权重调整为 0。

商业 DNS 管道:使用 DNSPod/阿里云解析的企业版,API 更新延迟 < 1 秒,配合 60 秒的 TTL,故障切换总延迟控制在 90 秒以内。

💡 多层防护策略:仅靠 HTTPDNS