高可用-多活与容灾架构设计
在互联网时代,系统的可用性直接关系到企业的生存。
当机房断电、光缆被挖断、地震洪水等灾难发生时,如何保证服务不中断?多活架构和容灾体系就是应对这些极端场景的核心方案。
本文将深入探讨从同城双活到异地多活的完整架构设计。
# 一、容灾基础理论
# 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、设计原则
- 分层容灾: 接入层、应用层、数据层分别设计容灾方案
- 故障隔离: 避免故障扩散
- 自动化: 自动检测、自动切换
- 定期演练: 验证方案有效性
- 成本平衡: 根据业务重要性选择合适方案
# 3、未来发展方向
- 云原生容灾: 基于 Kubernetes 的多集群管理
- 智能调度: AI 预测故障,提前切换
- 边缘计算: 就近计算,降低延迟
- Serverless: 按需弹性,降低成本
# 4、学习建议
- 理论学习: 深入理解 CAP、一致性理论
- 动手实践: 搭建本地多活环境,实际演练
- 研究开源: 学习 TiDB、Consul 等分布式系统
- 关注大厂: 研究阿里、腾讯的容灾实践
容灾不是一蹴而就的,需要持续建设和优化。从同城双活起步,逐步演进到异地多活,最终实现真正的"永不宕机"。
祝你变得更强!