Skip to content

同城双活架构设计:流量分配与数据同步

想象一下这个场景:

你的系统在 A 机房运行,突然——停电了。

一个小时后恢复,但你的用户已经无法访问服务一个小时了。

如果你的系统做了同城双活,只需要几分钟切换,用户几乎无感知。

这就是同城双活的价值。

双活 vs 主备

很多人分不清双活和主备的区别:

  • 主备:主机房承载所有流量,备机房待机。故障时切流量。
  • 双活:两个机房都承载流量,故障时,另一个机房瞬间接管。
主备架构:
┌─────────────┐     ┌─────────────┐
│   主机房     │────▶│   备机房     │
│  (100%流量) │     │   (待机)    │
└─────────────┘     └─────────────┘

双活架构:
┌─────────────┐     ┌─────────────┐
│   机房 A     │◀───▶│   机房 B     │
│  (50%流量)  │     │  (50%流量)  │
└─────────────┘     └─────────────┘

双活的优势:

  1. 容灾效果更好:一个机房挂了,另一个机房瞬间接管
  2. 资源利用率高:两个机房都在工作,没有闲置资源
  3. 切换速度快:不需要等待备机房启动

同城双活的优势

为什么是同城?

  • 延迟低:同城机房之间网络延迟通常在 1-5ms
  • 网络稳定:同城网络质量好,丢包率低
  • 成本可控:比异地成本低很多

同城双活的 RTO(恢复时间目标)可以做到分钟级,RPO(恢复点目标)接近 0。

流量分配

DNS 智能解析

最常用的流量分配方式:

java
// DNS 配置示例
// 正常情况下:
// 北京用户 → 北京机房 A
// 上海用户 → 上海机房 B

// 故障情况下:
// 北京用户 → 上海机房 B(DNS 切换)

DNS 智能解析需要:

  1. EDNS 协议:获取用户真实 IP(而非递归 DNS IP)
  2. 健康检查:实时检测机房健康状态
  3. 负载均衡策略:轮询、加权、最优路径

VIP 漂移

VIP(Virtual IP)在两个机房之间漂移:

正常情况下
主机房 VIP: 10.1.1.100
备机房 VIP: 无

故障情况下
主机房 VIP: 无
备机房 VIP: 10.1.1.100

VIP 漂移通常配合网络设备(VRRP 协议)实现。

数据同步

同城双活的核心问题是:两个机房的数据如何保持一致?

同步复制

两个机房同时写入,数据强一致:

java
// 双写逻辑
@Transactional
public void transfer(Account from, Account to, BigDecimal amount) {
    // 同时写入两个机房
    dbA.update(from, -amount);
    dbB.update(from, -amount);
    dbA.update(to, amount);
    dbB.update(to, amount);
}

问题:

  1. 延迟增加:双写比单机写慢
  2. 一致性风险:如果一个机房写入成功、另一个失败,数据不一致
  3. 复杂度高:需要分布式事务支持

异步复制

主机房写入成功,异步复制到备机房:

java
// 异步复制
public void transfer(Account from, Account to, BigDecimal amount) {
    // 主机房写入
    dbA.update(from, -amount);
    dbA.update(to, amount);

    // 异步复制到备机房
    asyncReplication(from, -amount);
    asyncReplication(to, amount);
}

问题:

  1. 数据可能丢失:主机房写入后、备机房复制前,主机房挂了
  2. RPO > 0:恢复时会有数据丢失

延迟双写

主机房写入,备机房读取。当数据不一致时,用主机房数据覆盖:

java
// 延迟双写策略
public void transfer(Account from, Account to, BigDecimal amount) {
    // 主机房写入
    Transaction txA = dbA.beginTransaction();
    dbA.update(from, -amount);
    dbA.update(to, amount);
    txA.commit();

    // 备机房异步写入(延迟几百毫秒)
    delayQueue.add(new WriteTask(from, -amount));
    delayQueue.add(new WriteTask(to, amount));
}

挑战与权衡

网络延迟

两个机房之间有网络延迟,通常 1-5ms。

对于大部分业务,这个延迟可以忽略。

但对于强一致性要求的场景(如金融交易),需要考虑:

  1. 跨机房事务超时设置要更大
  2. 避免跨机房强一致性操作

数据一致性

双活架构下的一致性问题是复杂的:

  • 最终一致:异步复制,保证最终一致
  • 强一致:同步复制,性能差
  • 业务妥协:接受短暂不一致,通过补偿机制处理

流量切换

故障时如何切换流量?

  1. DNS 切换:修改 DNS 记录,用户本地 DNS 缓存过期后生效(5 分钟-24 小时)
  2. VIP 漂移:秒级切换,但需要网络设备支持
  3. 负载均衡器:流量在负载均衡层切换

面试追问方向

  • 同城双活和异地多活的区别是什么?(答:同城延迟低,适合 RTO/RPO 要求高的场景;异地成本高,但可以应对更大的灾难)
  • 如何保证双机房的数据一致性?(答:最终一致、同步复制、业务妥协等方案,根据业务选择)
  • 双活架构下如何做单元化?(答:按用户 ID 路由,同一用户的数据路由到同一机房)
  • 机房故障时如何快速切换?(答:VIP 漂移 + 健康检查 + 自动化脚本)

小结

同城双活是提高系统可用性的重要手段:

  1. 两个机房同时服务:提高资源利用率,加快故障切换
  2. 流量分配:DNS 智能解析 + VIP 漂移
  3. 数据同步:根据业务需求选择同步/异步
  4. 快速切换:自动化健康检查 + 流量切换

同城双活不是银弹,它增加了架构复杂度。需要根据业务需求(RTO/RPO)和成本权衡是否采用。

基于 VitePress 构建