轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 架构设计与模式

    • 高可用-分布式基础之CAP理论
    • 高可用-服务容错与降级策略
    • 高可用-故障检测与自动恢复
    • 高可用-混沌工程实践
    • 高可用-分布式事务实战
    • 高可用-多活与容灾架构设计
      • 一、容灾基础理论
        • 1、什么是容灾?
        • 2、RTO 和 RPO
        • RTO (Recovery Time Objective) - 恢复时间目标
        • RPO (Recovery Point Objective) - 恢复点目标
        • 3、容灾等级划分
        • 4、容灾架构的演进
      • 二、同城双活架构
        • 1、同城双活基本原理
        • 2、同城双活的核心挑战
        • 2.1、数据一致性问题
        • 2.2、分布式锁问题
        • 2.3、Session 共享问题
        • 3、同城双活流量调度
        • 3.1、DNS 调度
        • 3.2、负载均衡器调度
        • 3.3、智能调度策略
        • 4、同城双活故障切换
        • 4.1、自动故障检测
        • 4.2、自动切换策略
        • 5、同城双活实战案例
        • 案例: MySQL 主主同步配置
      • 三、异地多活架构
        • 1、异地多活基本原理
        • 2、异地多活的核心挑战
        • 2.1、数据一致性 vs 性能
        • 2.2、跨地域网络延迟
        • 3、单元化架构
        • 4、全局流量调度
        • 4.1、GSLB (Global Server Load Balance)
        • 4.2、客户端智能路由
        • 5、数据同步方案
        • 5.1、MySQL 异地同步
        • 5.2、NoSQL 异地同步
        • 5.3、消息队列异地同步
      • 四、故障演练与容灾建设
        • 1、故障演练的重要性
        • 2、故障演练实施
        • 2.1、演练场景设计
        • 2.2、演练工具
        • 3、容灾建设最佳实践
        • 3.1、建立容灾等级体系
        • 3.2、制定应急预案
        • 3.3、定期演练和复盘
      • 五、实战案例分析
        • 1、阿里巴巴: 异地多活架构
        • 2、微信: 同城双活到异地多活
        • 3、美团: 混合云容灾
      • 六、总结与展望
        • 1、核心要点回顾
        • 2、设计原则
        • 3、未来发展方向
        • 4、学习建议
    • 高性能-缓存架构设计
    • 高性能-性能优化方法论
    • 高性能-异步处理与消息队列
    • 高性能-数据库性能优化
  • 代码质量管理

  • 基础

  • 操作系统

  • 计算机网络

  • AI

  • 编程范式

  • 安全

  • 中间件

  • 心得

  • 架构
  • 架构设计与模式
轩辕李
2025-01-01
目录

高可用-多活与容灾架构设计

在互联网时代,系统的可用性直接关系到企业的生存。

当机房断电、光缆被挖断、地震洪水等灾难发生时,如何保证服务不中断?多活架构和容灾体系就是应对这些极端场景的核心方案。

本文将深入探讨从同城双活到异地多活的完整架构设计。

# 一、容灾基础理论

# 1、什么是容灾?

容灾(Disaster Recovery) 是指系统在遭遇灾难性故障时,能够快速恢复服务的能力。

容灾 vs 高可用:

对比维度 高可用(HA) 容灾(DR)
应对场景 单点故障、服务异常 机房级、地域级灾难
故障范围 单机、单服务 整个机房、整个地域
恢复目标 秒级、分钟级 分钟级、小时级
成本 中等 高
典型方案 主备切换、集群 异地多活、两地三中心

# 2、RTO 和 RPO

这是衡量容灾能力的两个核心指标:

# RTO (Recovery Time Objective) - 恢复时间目标

定义: 系统从故障到恢复服务的最大可容忍时间。

故障发生 → [RTO] → 服务恢复

分级:

  • Tier 0: RTO = 0 (实时零中断,双活架构)
  • Tier 1: RTO < 1分钟 (自动切换)
  • Tier 2: RTO < 1小时 (人工介入)
  • Tier 3: RTO < 24小时 (冷备恢复)

# RPO (Recovery Point Objective) - 恢复点目标

定义: 系统能容忍的最大数据丢失量(时间窗口)。

最后一次数据同步 → [RPO] → 故障发生

分级:

  • RPO = 0: 零数据丢失(同步复制)
  • RPO < 1分钟: 准实时复制
  • RPO < 1小时: 定时备份
  • RPO > 1天: 冷备份

RTO 和 RPO 的关系:

成本 ↑
  │
  │     ┌─────────────┐
  │     │ 异地多活(双0)  │
  │     └─────────────┘
  │   ┌──────────────┐
  │   │  同城双活      │
  │   └──────────────┘
  │ ┌────────────────┐
  │ │  主备切换        │
  │ └────────────────┘
  │┌──────────────────┐
  ││  冷备恢复          │
  │└──────────────────┘
  └───────────────────────→ RTO/RPO
    高                    低

# 3、容灾等级划分

国际标准 SHARE 78 定义了7个容灾等级:

等级 名称 RTO RPO 典型方案 成本
Tier 0 无备份 数天 数天 无 最低
Tier 1 本地备份 数小时 1-24小时 磁带备份 低
Tier 2 异地备份 数小时 1-24小时 异地磁带 中低
Tier 3 电子传输 数小时 数小时 网络备份 中
Tier 4 定时数据复制 2-4小时 1-4小时 异地备份中心 中高
Tier 5 实时数据复制 数分钟 数分钟 主备机房 高
Tier 6 数据零丢失 数分钟 0 同步复制 很高
Tier 7 业务零中断 0 0 异地多活 最高

金融行业要求: 核心系统至少达到 Tier 5 级别。

# 4、容灾架构的演进

第一代: 冷备份
  - 定期备份数据到磁带
  - 灾难时手动恢复
  - RTO: 数天, RPO: 数天
  
第二代: 热备份
  - 异地备份中心
  - 定时数据同步
  - RTO: 数小时, RPO: 数小时
  
第三代: 同城双活
  - 两个机房同时提供服务
  - 实时数据同步
  - RTO: 分钟级, RPO: 秒级
  
第四代: 异地多活
  - 多个地域同时提供服务
  - 用户就近访问
  - RTO: 0, RPO: 0

# 二、同城双活架构

# 1、同城双活基本原理

同城双活是指在同一城市的两个机房同时提供服务,互为备份。

核心特点:

  • 距离: 通常在 30-100 公里
  • 网络延迟: < 2ms (光纤直连)
  • 数据同步: 准实时或实时
  • 流量分配: 双活或主备

架构图:

用户请求
   ↓
DNS/CDN (智能解析)
   ↓
 ┌─────────────────┐
 │   负载均衡器      │
 └─────────────────┘
   ↓           ↓
机房A(50%)    机房B(50%)
   ↓           ↓
 ┌────┐      ┌────┐
 │应用层│      │应用层│
 └────┘      └────┘
   ↓           ↓
 ┌────┐      ┌────┐
 │缓存层│      │缓存层│
 └────┘      └────┘
   ↓           ↓
 ┌────┐  ←→  ┌────┐
 │数据库│      │数据库│ (实时同步)
 └────┘      └────┘

# 2、同城双活的核心挑战

# 2.1、数据一致性问题

挑战: 两个机房的数据库如何保持一致?

方案1: 主从复制

机房A (主)                    机房B (从)
   ↓                           ↓
写请求 → 主库 ─────同步────→ 从库
读请求 → 主库                  从库

优点: 简单可靠
缺点: 机房A故障时需要手动切换

方案2: 主主复制

机房A                        机房B
   ↓                           ↓
写请求 → 主库 ←────双向同步────→ 主库
读请求 → 主库                   主库

优点: 真正双活,无需切换
缺点: 可能出现数据冲突

冲突解决策略:

public class ConflictResolver {
    
    /**
     * 策略1: 时间戳优先
     */
    public Record resolveByTimestamp(Record r1, Record r2) {
        return r1.getUpdateTime().isAfter(r2.getUpdateTime()) ? r1 : r2;
    }
    
    /**
     * 策略2: 版本号优先
     */
    public Record resolveByVersion(Record r1, Record r2) {
        return r1.getVersion() > r2.getVersion() ? r1 : r2;
    }
    
    /**
     * 策略3: 机房优先级
     */
    public Record resolveByPriority(Record r1, Record r2) {
        // 机房A优先
        return r1.getDataCenter().equals("DC-A") ? r1 : r2;
    }
}

# 2.2、分布式锁问题

挑战: 两个机房如何协调锁?

方案1: 基于数据库的分布式锁

@Service
public class DatabaseLockService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public boolean tryLock(String lockKey, int timeoutSeconds) {
        try {
            String sql = "INSERT INTO distributed_lock (lock_key, holder, expire_time) " +
                        "VALUES (?, ?, ?)";
            
            jdbcTemplate.update(sql, 
                lockKey,
                getHostId(),
                LocalDateTime.now().plusSeconds(timeoutSeconds)
            );
            return true;
        } catch (DuplicateKeyException e) {
            // 锁已被占用
            return false;
        }
    }
    
    public void unlock(String lockKey) {
        String sql = "DELETE FROM distributed_lock WHERE lock_key = ? AND holder = ?";
        jdbcTemplate.update(sql, lockKey, getHostId());
    }
}

方案2: 基于 Redis 的分布式锁

@Service
public class RedisLockService {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public boolean tryLock(String lockKey, String requestId, int expireSeconds) {
        // 使用 SET NX EX 原子操作
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS);
        
        return Boolean.TRUE.equals(result);
    }
    
    public void unlock(String lockKey, String requestId) {
        // Lua 脚本保证原子性
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";
        
        redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            requestId
        );
    }
}

注意: Redis 需要配置跨机房同步,推荐使用 Redis Sentinel 或 Redis Cluster。

# 2.3、Session 共享问题

挑战: 用户请求在两个机房之间跳转,Session如何共享?

方案1: Session 复制

// Tomcat Session 复制配置
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
    <Manager className="org.apache.catalina.ha.session.DeltaManager"
             expireSessionsOnShutdown="false"
             notifyListenersOnReplication="true"/>
    <Channel className="org.apache.catalina.tribes.group.GroupChannel">
        <!-- 配置跨机房组播 -->
    </Channel>
</Cluster>

缺点: 性能差,不适合大规模集群。

方案2: 集中式 Session 存储

@Configuration
@EnableRedisHttpSession
public class SessionConfig {
    
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        // Redis 集群配置
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
        clusterConfig.addClusterNode(new RedisNode("dc-a-redis", 6379));
        clusterConfig.addClusterNode(new RedisNode("dc-b-redis", 6379));
        
        return new LettuceConnectionFactory(clusterConfig);
    }
}

使用:

@RestController
public class UserController {
    
    @GetMapping("/user/info")
    public UserInfo getUserInfo(HttpSession session) {
        // Session 自动存储到 Redis
        UserInfo user = (UserInfo) session.getAttribute("user");
        return user;
    }
}

# 3、同城双活流量调度

# 3.1、DNS 调度

基于 DNS 的流量分配:

example.com
   ↓
DNS 解析
   ↓
 ┌─────────────────┐
 │ 机房A: 1.1.1.1  │ (50%)
 │ 机房B: 2.2.2.2  │ (50%)
 └─────────────────┘

优点: 简单,成本低
缺点:

  • DNS 缓存导致切换慢(TTL通常几分钟)
  • 无法感知机房健康状态

# 3.2、负载均衡器调度

基于 LVS/Nginx 的流量分配:

upstream backend {
    # 机房A
    server 1.1.1.1:8080 weight=5;
    server 1.1.1.2:8080 weight=5;
    
    # 机房B
    server 2.2.2.1:8080 weight=5;
    server 2.2.2.2:8080 weight=5;
    
    # 健康检查
    check interval=3000 rise=2 fall=3 timeout=1000;
}

server {
    listen 80;
    
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout;
    }
}

优点:

  • 实时健康检查
  • 灵活的调度策略

# 3.3、智能调度策略

基于地理位置的就近接入:

@Component
public class GeoRoutingFilter implements Filter {
    
    @Autowired
    private GeoIPService geoIPService;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest) request;
        String clientIp = getClientIp(req);
        
        // 获取用户地理位置
        Location location = geoIPService.getLocation(clientIp);
        
        // 选择最近的机房
        String targetDC = selectNearestDataCenter(location);
        
        // 设置路由标识
        req.setAttribute("target_dc", targetDC);
        
        chain.doFilter(request, response);
    }
    
    private String selectNearestDataCenter(Location location) {
        // 简化示例: 根据经纬度选择最近机房
        double distanceToA = calculateDistance(location, DC_A_LOCATION);
        double distanceToB = calculateDistance(location, DC_B_LOCATION);
        
        return distanceToA < distanceToB ? "DC-A" : "DC-B";
    }
}

# 4、同城双活故障切换

# 4.1、自动故障检测

@Service
public class HealthCheckService {
    
    @Scheduled(fixedDelay = 5000) // 每5秒检测一次
    public void checkDataCenterHealth() {
        // 检查机房A
        boolean dcAHealthy = checkDataCenter("DC-A");
        
        // 检查机房B
        boolean dcBHealthy = checkDataCenter("DC-B");
        
        // 更新机房状态
        updateDataCenterStatus("DC-A", dcAHealthy);
        updateDataCenterStatus("DC-B", dcBHealthy);
        
        // 如果某个机房故障,触发切换
        if (!dcAHealthy) {
            triggerFailover("DC-A", "DC-B");
        } else if (!dcBHealthy) {
            triggerFailover("DC-B", "DC-A");
        }
    }
    
    private boolean checkDataCenter(String dc) {
        try {
            // 1. 检查应用服务
            boolean appHealthy = checkApplicationHealth(dc);
            
            // 2. 检查数据库
            boolean dbHealthy = checkDatabaseHealth(dc);
            
            // 3. 检查缓存
            boolean cacheHealthy = checkCacheHealth(dc);
            
            return appHealthy && dbHealthy && cacheHealthy;
        } catch (Exception e) {
            log.error("Health check failed for DC: {}", dc, e);
            return false;
        }
    }
}

# 4.2、自动切换策略

@Service
public class FailoverService {
    
    @Autowired
    private DNSService dnsService;
    @Autowired
    private LoadBalancerService lbService;
    
    public void triggerFailover(String failedDC, String targetDC) {
        log.warn("Triggering failover from {} to {}", failedDC, targetDC);
        
        // 1. 更新 DNS 解析
        dnsService.removeDataCenter(failedDC);
        
        // 2. 更新负载均衡器
        lbService.disableDataCenter(failedDC);
        lbService.setWeight(targetDC, 100);
        
        // 3. 更新数据库主从关系
        if (failedDC.equals("DC-A")) {
            // 将机房B的从库提升为主库
            promoteSlaveToMaster("DC-B");
        }
        
        // 4. 发送告警通知
        sendAlert("Failover completed: " + failedDC + " -> " + targetDC);
        
        log.info("Failover completed successfully");
    }
    
    private void promoteSlaveToMaster(String dc) {
        // 执行数据库主从切换
        // 实际实现依赖于数据库类型 (MySQL/PostgreSQL/Oracle)
    }
}

# 5、同城双活实战案例

# 案例: MySQL 主主同步配置

机房A配置 (my.cnf):

[mysqld]
# 服务器ID (全局唯一)
server-id=1

# 开启 binlog
log-bin=mysql-bin
binlog_format=ROW

# 主主复制配置
auto_increment_offset=1      # 起始值
auto_increment_increment=2   # 步长 (避免主键冲突)

# 从库配置
relay-log=relay-bin
log_slave_updates=1

机房B配置 (my.cnf):

[mysqld]
server-id=2

log-bin=mysql-bin
binlog_format=ROW

auto_increment_offset=2      # 起始值
auto_increment_increment=2   # 步长

relay-log=relay-bin
log_slave_updates=1

建立主主同步:

-- 在机房A执行: 配置机房B为主库
CHANGE MASTER TO
    MASTER_HOST='dc-b-mysql',
    MASTER_PORT=3306,
    MASTER_USER='repl',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=154;

START SLAVE;

-- 在机房B执行: 配置机房A为主库
CHANGE MASTER TO
    MASTER_HOST='dc-a-mysql',
    MASTER_PORT=3306,
    MASTER_USER='repl',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=154;

START SLAVE;

验证同步状态:

SHOW SLAVE STATUS\G

-- 关键指标:
-- Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes
-- Seconds_Behind_Master: 0 (延迟秒数)

# 三、异地多活架构

# 1、异地多活基本原理

异地多活是指在不同地域(城市)的多个数据中心同时提供服务。

核心特点:

  • 距离: 数百到数千公里
  • 网络延迟: 10-100ms
  • 数据同步: 异步为主
  • 流量分配: 就近接入

架构图:

             用户请求
                ↓
         全局负载均衡(GSLB)
                ↓
    ┌───────────┼───────────┐
    ↓           ↓           ↓
  北京机房      上海机房      广州机房
    ↓           ↓           ↓
  应用集群      应用集群      应用集群
    ↓           ↓           ↓
  本地缓存      本地缓存      本地缓存
    ↓           ↓           ↓
  本地数据库    本地数据库    本地数据库
    └───────────┼───────────┘
          异步数据同步

# 2、异地多活的核心挑战

# 2.1、数据一致性 vs 性能

CAP 理论的权衡:

异地多活 → 必须保证 P (分区容错)
         → 选择 A (可用性) 还是 C (一致性)?

方案1: 最终一致性 (AP)

@Service
public class EventualConsistencyService {
    
    @Autowired
    private LocalDatabase localDb;
    @Autowired
    private MessageQueue messageQueue;
    
    @Transactional
    public void updateUser(User user) {
        // 1. 更新本地数据库
        localDb.update(user);
        
        // 2. 发送变更事件到消息队列
        UserUpdatedEvent event = new UserUpdatedEvent(user);
        messageQueue.send("user.updated", event);
        
        // 其他机房异步接收并更新
    }
}

@Component
public class UserEventConsumer {
    
    @RabbitListener(queues = "user.updated")
    public void handleUserUpdated(UserUpdatedEvent event) {
        // 接收其他机房的变更,更新本地数据库
        localDb.update(event.getUser());
    }
}

优点: 高性能,高可用
缺点: 存在数据不一致窗口

方案2: 强一致性 (CP)

@Service
public class StrongConsistencyService {
    
    @Autowired
    private DistributedTransactionManager txManager;
    
    public void updateUserSync(User user) {
        // 使用分布式事务保证多机房同步更新
        txManager.begin();
        
        try {
            // 更新所有机房
            beijingDb.update(user);
            shanghaiDb.update(user);
            guangzhouDb.update(user);
            
            txManager.commit();
        } catch (Exception e) {
            txManager.rollback();
            throw e;
        }
    }
}

优点: 数据强一致
缺点: 性能差,跨地域网络延迟大

实战选择:

  • 读多写少: AP (最终一致性)
  • 强一致性要求: 单元化架构(后文详述)

# 2.2、跨地域网络延迟

问题: 北京到广州的网络延迟约 40ms,一次数据库同步往返 80ms。

优化方案:

方案1: 数据分片 + 就近写入

@Service
public class ShardingService {
    
    public void createOrder(Order order) {
        // 根据用户ID分片,确定归属机房
        String userDC = getUserDataCenter(order.getUserId());
        
        // 路由到对应机房
        DataSource ds = getDataSource(userDC);
        orderDao.insert(ds, order);
    }
    
    private String getUserDataCenter(String userId) {
        // 根据用户ID哈希到机房
        int hash = Math.abs(userId.hashCode());
        int dcIndex = hash % 3;
        
        String[] datacenters = {"beijing", "shanghai", "guangzhou"};
        return datacenters[dcIndex];
    }
}

方案2: 多级缓存

@Service
public class MultiLevelCacheService {
    
    @Autowired
    private LocalCache localCache;        // L1: 本地缓存
    @Autowired
    private RedisCache localRedis;        // L2: 本机房 Redis
    @Autowired
    private RedisCache centralRedis;      // L3: 中心 Redis
    @Autowired
    private Database database;            // L4: 数据库
    
    public User getUser(String userId) {
        // L1: 本地缓存 (耗时 < 1ms)
        User user = localCache.get(userId);
        if (user != null) return user;
        
        // L2: 本机房 Redis (耗时 1-5ms)
        user = localRedis.get(userId);
        if (user != null) {
            localCache.put(userId, user);
            return user;
        }
        
        // L3: 中心 Redis (耗时 10-50ms)
        user = centralRedis.get(userId);
        if (user != null) {
            localRedis.put(userId, user);
            localCache.put(userId, user);
            return user;
        }
        
        // L4: 数据库 (耗时 50-200ms)
        user = database.query(userId);
        if (user != null) {
            centralRedis.put(userId, user);
            localRedis.put(userId, user);
            localCache.put(userId, user);
        }
        
        return user;
    }
}

# 3、单元化架构

单元化是解决异地多活数据一致性问题的核心方案。

核心思想:

  • 将用户按某个维度(如用户ID)分配到不同单元
  • 每个单元是一个完整的业务闭环
  • 单元内强一致性,单元间最终一致性

架构图:

           用户请求
              ↓
       路由层(根据UID路由)
              ↓
    ┌─────────┼─────────┐
    ↓         ↓         ↓
  单元1      单元2      单元3
  (北京)     (上海)     (广州)
    ↓         ↓         ↓
  用户1-100  用户101-200 用户201-300
    ↓         ↓         ↓
完整业务栈   完整业务栈   完整业务栈

单元化路由实现:

@Component
public class UnitRouter {
    
    private static final int UNIT_COUNT = 3;
    
    /**
     * 路由表: userId -> unitId
     */
    private LoadingCache<String, String> routingTable = CacheBuilder.newBuilder()
        .maximumSize(10000)
        .build(new CacheLoader<String, String>() {
            @Override
            public String load(String userId) {
                return calculateUnit(userId);
            }
        });
    
    /**
     * 计算用户所属单元
     */
    private String calculateUnit(String userId) {
        int hash = Math.abs(userId.hashCode());
        int unitId = hash % UNIT_COUNT;
        
        String[] units = {"beijing", "shanghai", "guangzhou"};
        return units[unitId];
    }
    
    /**
     * 获取用户所属单元
     */
    public String getUnit(String userId) {
        return routingTable.getUnchecked(userId);
    }
    
    /**
     * 路由请求到目标单元
     */
    public <T> T routeToUnit(String userId, Function<String, T> action) {
        String targetUnit = getUnit(userId);
        String currentUnit = getCurrentUnit();
        
        if (targetUnit.equals(currentUnit)) {
            // 本单元处理
            return action.apply(targetUnit);
        } else {
            // 转发到目标单元
            return forwardToUnit(targetUnit, action);
        }
    }
}

使用示例:

@Service
public class OrderService {
    
    @Autowired
    private UnitRouter unitRouter;
    
    public Order createOrder(String userId, OrderRequest request) {
        // 路由到用户所属单元
        return unitRouter.routeToUnit(userId, unit -> {
            // 在目标单元执行订单创建
            Order order = new Order();
            order.setUserId(userId);
            order.setUnit(unit);
            
            // 本单元内的操作都是强一致性的
            orderDao.insert(order);
            inventoryService.deduct(request.getProductId(), request.getQuantity());
            
            return order;
        });
    }
}

单元化的优势:

  • ✅ 单元内强一致性
  • ✅ 无跨单元分布式事务
  • ✅ 故障隔离(单元故障不影响其他单元)
  • ✅ 可线性扩展

单元化的挑战:

  • ❌ 跨单元查询困难
  • ❌ 用户迁移复杂
  • ❌ 单元容量规划

# 4、全局流量调度

# 4.1、GSLB (Global Server Load Balance)

DNS 级别的全局负载均衡:

用户请求 www.example.com
   ↓
GSLB 根据以下因素决定返回哪个机房IP:
   - 用户地理位置
   - 机房健康状态
   - 机房负载
   - 网络质量
   ↓
返回最优机房IP

基于 GeoDNS 的实现:

@Service
public class GeoDNSService {
    
    public String resolveDataCenter(String clientIp) {
        // 1. 获取用户地理位置
        Location location = geoIPService.getLocation(clientIp);
        
        // 2. 获取所有健康的机房
        List<DataCenter> healthyDCs = getHealthyDataCenters();
        
        // 3. 计算最近的机房
        DataCenter nearest = healthyDCs.stream()
            .min(Comparator.comparingDouble(dc -> 
                calculateDistance(location, dc.getLocation())))
            .orElseThrow();
        
        return nearest.getIp();
    }
    
    private List<DataCenter> getHealthyDataCenters() {
        return dataCenterRegistry.getAll().stream()
            .filter(DataCenter::isHealthy)
            .filter(dc -> dc.getLoad() < 0.8) // 负载 < 80%
            .collect(Collectors.toList());
    }
}

# 4.2、客户端智能路由

移动APP场景: 客户端根据网络质量选择最优机房。

public class SmartRoutingClient {
    
    private List<String> dataCenters = Arrays.asList(
        "https://bj.api.example.com",
        "https://sh.api.example.com",
        "https://gz.api.example.com"
    );
    
    /**
     * 选择最优机房
     */
    public String selectBestDataCenter() {
        Map<String, Long> latencies = new HashMap<>();
        
        // 并发测试所有机房延迟
        dataCenters.parallelStream().forEach(dc -> {
            long latency = measureLatency(dc);
            latencies.put(dc, latency);
        });
        
        // 选择延迟最低的机房
        return latencies.entrySet().stream()
            .min(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElse(dataCenters.get(0));
    }
    
    private long measureLatency(String url) {
        long start = System.currentTimeMillis();
        try {
            HttpURLConnection conn = (HttpURLConnection) new URL(url + "/ping").openConnection();
            conn.setConnectTimeout(3000);
            conn.connect();
            conn.getResponseCode();
        } catch (Exception e) {
            return Long.MAX_VALUE;
        }
        return System.currentTimeMillis() - start;
    }
}

# 5、数据同步方案

# 5.1、MySQL 异地同步

方案1: 主从异步复制

北京(主库) ──异步复制──> 上海(从库)
                    ──> 广州(从库)

配置:

-- 在从库执行
CHANGE MASTER TO
    MASTER_HOST='beijing-mysql',
    MASTER_PORT=3306,
    MASTER_USER='repl',
    MASTER_PASSWORD='password',
    MASTER_AUTO_POSITION=1;  -- 使用 GTID

START SLAVE;

优点: 性能好
缺点: 有数据丢失风险

方案2: 半同步复制

# 主库配置
[mysqld]
plugin-load="rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000  # 1秒超时

# 从库配置
[mysqld]
plugin-load="rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled=1

工作原理:

  • 主库等待至少一个从库确认
  • 超时后降级为异步复制

方案3: 双向同步 (多主)

使用 MySQL Group Replication 或 Galera Cluster:

-- MySQL Group Replication 配置
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;

# 5.2、NoSQL 异地同步

Redis 跨地域同步:

使用 Redis Cluster + 跨机房复制:

# 创建北京集群
redis-cli --cluster create \
    bj-node1:6379 bj-node2:6379 bj-node3:6379

# 创建上海集群
redis-cli --cluster create \
    sh-node1:6379 sh-node2:6379 sh-node3:6379

# 配置跨集群复制 (使用 Redis Enterprise 或自研方案)

MongoDB 异地同步:

// 配置跨地域副本集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "beijing-mongo:27017", priority: 2 },
    { _id: 1, host: "shanghai-mongo:27017", priority: 1 },
    { _id: 2, host: "guangzhou-mongo:27017", priority: 1 }
  ]
})

# 5.3、消息队列异地同步

RocketMQ 跨地域同步:

// 配置跨地域复制
public class CrossRegionReplication {
    
    public void setupReplication() {
        // 北京集群
        DefaultMQProducer beijingProducer = new DefaultMQProducer("beijing_group");
        beijingProducer.setNamesrvAddr("beijing-mq:9876");
        beijingProducer.start();
        
        // 上海集群
        DefaultMQProducer shanghaiProducer = new DefaultMQProducer("shanghai_group");
        shanghaiProducer.setNamesrvAddr("shanghai-mq:9876");
        shanghaiProducer.start();
        
        // 消费北京消息,同步到上海
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("replicator");
        consumer.setNamesrvAddr("beijing-mq:9876");
        consumer.subscribe("*", "*");
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                // 转发到上海集群
                Message newMsg = new Message(msg.getTopic(), msg.getBody());
                shanghaiProducer.send(newMsg);
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });
        consumer.start();
    }
}

# 四、故障演练与容灾建设

# 1、故障演练的重要性

Netflix 的 Chaos Monkey: 随机关闭生产环境的实例,验证系统的容错能力。

演练目标:

  • ✅ 验证容灾方案的有效性
  • ✅ 发现系统薄弱环节
  • ✅ 提升团队应急能力
  • ✅ 优化 RTO 和 RPO

# 2、故障演练实施

# 2.1、演练场景设计

场景 演练内容 预期结果
单机房故障 关闭一个机房的所有服务 流量自动切换到其他机房
数据库主库故障 关闭主库 从库自动提升为主库
网络分区 模拟跨机房网络中断 各机房独立运行
缓存故障 关闭 Redis 集群 降级到数据库,性能下降
消息队列故障 关闭 MQ 集群 消息堆积,服务降级
部分服务故障 关闭某个微服务的所有实例 熔断生效,返回降级结果

# 2.2、演练工具

ChaosBlade: 阿里开源的混沌工程工具

# 模拟 CPU 负载
blade create cpu load --cpu-percent 80

# 模拟网络延迟
blade create network delay --time 3000 --interface eth0

# 模拟进程故障
blade create process kill --process mysql

# 模拟磁盘IO故障
blade create disk fill --path /data --size 10000

Hystrix Dashboard: 监控熔断降级效果

@HystrixCommand(fallbackMethod = "fallbackMethod",
    commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
    })
public String callRemoteService() {
    // 调用远程服务
    return restTemplate.getForObject("http://remote-service/api", String.class);
}

public String fallbackMethod() {
    return "服务降级: 返回默认值";
}

# 3、容灾建设最佳实践

# 3.1、建立容灾等级体系

系统类型 容灾等级 RTO RPO 方案
核心交易 Tier 6 0 0 异地多活
用户数据 Tier 5 < 5分钟 < 1分钟 同城双活
营销活动 Tier 4 < 1小时 < 1小时 异地备份
日志分析 Tier 3 < 4小时 < 1天 定时备份

# 3.2、制定应急预案

示例: 机房故障应急预案

1. 故障发现 (T+0)
   - 监控告警触发
   - 值班人员确认

2. 故障评估 (T+5分钟)
   - 判断故障级别
   - 决定是否切换

3. 执行切换 (T+10分钟)
   - DNS 切换
   - 负载均衡器调整
   - 数据库主从切换

4. 验证恢复 (T+15分钟)
   - 检查服务可用性
   - 检查数据一致性
   - 监控关键指标

5. 通知用户 (T+20分钟)
   - 发布公告
   - 客服应对

6. 故障复盘 (故障后24小时)
   - 分析根因
   - 改进措施

# 3.3、定期演练和复盘

演练频率:

  • 核心系统: 每季度一次
  • 重要系统: 每半年一次
  • 一般系统: 每年一次

复盘模板:

## 故障复盘报告

### 1. 故障概述
- 时间: 2025-12-14 14:30 - 15:00
- 影响范围: 北京机房所有服务
- 故障等级: P1

### 2. 故障经过
14:30 - 机房空调故障,温度升高
14:35 - 服务器开始告警
14:40 - 决定切换到上海机房
14:45 - 切换完成,服务恢复

### 3. 根因分析
- 直接原因: 空调控制系统故障
- 根本原因: 缺少温度监控和自动切换

### 4. 改进措施
- [ ] 增加温度监控
- [ ] 配置自动切换规则
- [ ] 增加备用空调

# 五、实战案例分析

# 1、阿里巴巴: 异地多活架构

背景: 支撑双11大促,需要超高可用性。

架构特点:

  • 单元化: 按用户ID分配到不同单元
  • 三地五中心: 杭州、上海、深圳共5个机房
  • 分层架构: 接入层、应用层、数据层分别多活

关键技术:

  • GTS (Global Transaction Service): 分布式事务
  • TDDL (Taobao Distributed Data Layer): 分库分表中间件
  • Diamond: 配置中心

效果:

  • RTO: 0
  • RPO: 0
  • 可用性: 99.99%

# 2、微信: 同城双活到异地多活

演进路径:

2012年: 单机房
   ↓
2013年: 同城双活 (深圳两个机房)
   ↓
2015年: 异地多活 (深圳 + 上海)
   ↓
2018年: 全球多活 (中国 + 海外)

关键设计:

  • SET化: 将用户分配到不同SET,SET内闭环
  • 消息漫游: 消息跨SET同步
  • 就近接入: 用户连接最近的接入点

# 3、美团: 混合云容灾

场景: 突发流量高峰,公有云弹性扩容。

架构:

平时: 自建机房承载 100% 流量
   ↓
高峰: 自建机房 60% + 公有云 40%
   ↓
故障: 公有云接管 100% 流量

实现:

  • Kubernetes: 统一编排
  • Istio: 流量管理
  • 自动扩缩容: 根据负载自动调整

# 六、总结与展望

# 1、核心要点回顾

架构模式 RTO RPO 成本 适用场景
同城双活 分钟级 秒级 中 一般核心业务
异地多活 0 0 高 超核心业务
两地三中心 小时级 分钟级 中高 金融行业
单元化 0 0 高 超大规模系统

# 2、设计原则

  1. 分层容灾: 接入层、应用层、数据层分别设计容灾方案
  2. 故障隔离: 避免故障扩散
  3. 自动化: 自动检测、自动切换
  4. 定期演练: 验证方案有效性
  5. 成本平衡: 根据业务重要性选择合适方案

# 3、未来发展方向

  • 云原生容灾: 基于 Kubernetes 的多集群管理
  • 智能调度: AI 预测故障,提前切换
  • 边缘计算: 就近计算,降低延迟
  • Serverless: 按需弹性,降低成本

# 4、学习建议

  1. 理论学习: 深入理解 CAP、一致性理论
  2. 动手实践: 搭建本地多活环境,实际演练
  3. 研究开源: 学习 TiDB、Consul 等分布式系统
  4. 关注大厂: 研究阿里、腾讯的容灾实践

容灾不是一蹴而就的,需要持续建设和优化。从同城双活起步,逐步演进到异地多活,最终实现真正的"永不宕机"。

祝你变得更强!

编辑 (opens new window)
#多活架构#容灾#高可用
上次更新: 2025/12/17
高可用-分布式事务实战
高性能-缓存架构设计

← 高可用-分布式事务实战 高性能-缓存架构设计→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式