一、为什么需要多机房容灾
单机房部署存在诸多风险:
- 硬件故障:服务器、交换机、存储设备故障
- 电力中断:UPS电池耗尽、发电机故障
- 网络故障:光纤被挖断、运营商故障
- 自然灾害:火灾、水灾、地震等不可抗力
- 人为失误:运维操作失误导致服务中断
多机房容灾的目标:
- RTO(恢复时间目标):接近0
- RPO(恢复点目标):数据丢失接近0
二、多机房架构模式
1. 主备模式(Active-Standby)
主机房(Active) ← 同步复制 → 备机房(Standby)
↓ ↓
读写请求 只读/热备
特点:
- 架构简单,易于理解和维护
- 资源利用率低(备机房闲置)
- 故障切换需要手动或自动触发
- 适合对可用性要求较高的核心系统
适用场景:
- 核心数据库
- 关键业务系统
- 资金交易系统
2. 双活模式(Active-Active)
机房A(Active) ←→ 机房B(Active)
↓ ↓
东部用户 西部用户
特点:
- 两个机房同时对外提供服务
- 资源利用率高
- 数据同步是关键挑战
- 任一机房故障,另一机房承载全部流量
技术挑战:
- 跨机房网络延迟(10-50ms)
- 数据一致性保证
- 请求路由的准确性
3. 多活模式(Multi-Active)
机房A ←→ 机房B ←→ 机房C ←→ 机房D
↓ ↓ ↓ ↓
流量1 流量2 流量3 流量4
特点:
- 多个机房同时提供服务
- 按地域、用户特征分配流量
- 最高级别的可用性保障
- 复杂度也最高
三、数据同步方案
1. MySQL主主同步
主主复制(Master-Master)实现双向同步:
ini
# 机房A - my.cnf配置
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
gtid-mode = ON
enforce-gtid-consistency = ON
auto-increment-offset = 1
auto-increment-increment = 2
relay-log = relay-bin
ini
# 机房B - my.cnf配置
[mysqld]
server-id = 2
log-bin = mysql-bin
binlog-format = ROW
gtid-mode = ON
enforce-gtid-consistency = ON
auto-increment-offset = 2
auto-increment-increment = 2
relay-log = relay-bin
建立复制关系:
sql
-- 机房A配置
CHANGE MASTER TO
MASTER_HOST = '192.168.1.101',
MASTER_USER = 'repl',
MASTER_PASSWORD = 'password',
MASTER_PORT = 3306,
MASTER_AUTO_POSITION = 1;
START SLAVE;
-- 机房B配置(对称配置)
CHANGE MASTER TO
MASTER_HOST = '192.168.1.100',
MASTER_USER = 'repl',
MASTER_PASSWORD = 'password',
MASTER_PORT = 3306,
MASTER_AUTO_POSITION = 1;
START SLAVE;
2. Redis主从同步
Redis原生支持主从复制,跨机房场景:
conf
# 机房A Redis配置
bind 0.0.0.0
port 6379
masterauth "repl_password"
requirepass "redis_password"
# 开启AOF持久化
appendonly yes
appendfsync everysec
bash
# 机房A添加从节点(机房B)
redis-cli -h 192.168.1.100 REPLICAOF 192.168.1.101 6379
重要配置:
conf
# 适当调大超时时间,应对跨机房网络抖动
repl-timeout 120
# 启用无磁盘复制(减少网络开销)
repl-diskless-sync yes
repl-diskless-sync-delay 5
3. 消息队列同步
使用Kafka MirrorMaker实现跨机房消息同步:
yaml
# Kafka MirrorMaker配置
version: '3'
services:
mm:
image: confluentinc/cp-kafka-mirror-maker:latest
environment:
BOOTSTRAP_SERVERS: "kafka-a:9092"
TOPICS: ".*"
WHITELIST: ".*"
MIRROR_MAKER_CONSUMER_BOOTSTRAP_SERVERS: "kafka-b:9092"
MIRROR_MAKER_CONSUMER_GROUP_ID: "mm-group-1"
MIRROR_MAKER_PRODUCER_BOOTSTRAP_SERVERS: "kafka-b:9092"
command: /usr/bin/start-mirror-maker
4. 数据同步策略选择
| 数据类型 | 同步方案 | 同步频率 | 适用场景 |
|---|---|---|---|
| 交易数据 | 实时同步(同步复制) | 毫秒级 | 金融、订单 |
| 用户数据 | 准实时同步 | 秒级 | 用户中心 |
| 配置数据 | 实时同步 | 秒级 | 配置中心 |
| 日志数据 | 异步同步 | 分钟级 | 日志分析 |
四、流量调度方案
1. DNS智能解析
DNS是最外层的流量调度:
用户(北京) → DNS → 机房A(北京节点)
用户(上海) → DNS → 机房B(上海节点)
DNS服务商配置示例(阿里云DNS):
json
{
"DomainName": "example.com",
"RegionList": [
{
"RegionName": "cn-east-1",
"RegionId": "cn-east-1",
"Open": true,
"IspList": ["default"],
"RecordCount": 2
}
]
}
2. 应用层路由
java
public class RegionRouter {
public String route(HttpServletRequest request) {
// 根据用户IP判断地域
String ip = getClientIp(request);
String region = ipToRegion(ip);
// 根据地域选择机房
if ("east".equals(region)) {
return "http://dc-east.example.com";
} else if ("west".equals(region)) {
return "http://dc-west.example.com";
}
return "http://dc-primary.example.com";
}
}
3. 七层负载均衡调度
nginx
# Nginx按地域路由
geo $region {
default "primary";
10.0.0.0/8 "east";
172.16.0.0/12 "east";
192.168.0.0/16 "west";
}
upstream backend_east {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream backend_west {
server 192.168.2.10:8080;
server 192.168.2.11:8080;
}
upstream backend_primary {
server 192.168.3.10:8080;
server 192.168.3.11:8080;
}
server {
listen 80;
location / {
set $upstream "";
if ($region = "east") {
set $upstream backend_east;
}
if ($region = "west") {
set $upstream backend_west;
}
if ($upstream = "") {
set $upstream backend_primary;
}
proxy_pass http://$upstream;
}
}
五、故障切换方案
1. 健康检查
java
public class HealthChecker {
@Scheduled(fixedRate = 5000)
public void checkHealth() {
boolean primaryHealthy = checkDatacenter("primary");
boolean backupHealthy = checkDatacenter("backup");
if (!primaryHealthy && backupHealthy) {
// 触发故障切换
failoverService.switchTo("backup");
alertService.alert("主数据中心故障,已切换到备份中心");
}
}
private boolean checkDatacenter(String dc) {
try {
HttpResponse response = httpClient.execute(
new HttpGet("http://" + dc + "/health/check")
);
return response.getStatusLine().getStatusCode() == 200;
} catch (Exception e) {
return false;
}
}
}
2. DNS切换
java
public class DnsSwitcher {
public void switchDns(String targetDc) {
// 更新DNS记录
AlidnsClient client = new AlidnsClient();
// 暂停原主站点的A记录
updateDnsRecord("primary.example.com", null, 0);
// 激活备用站点的A记录
updateDnsRecord("backup.example.com", getBackupIp(), 600);
// 记录切换日志
log.info("DNS切换完成: {}", targetDc);
}
}
3. 切换流程
1. 检测到主站点不可达
2. 自动/手动触发切换
3. 停止向主站点写入数据
4. 等待主站点数据同步完成
5. 修改DNS解析
6. 恢复服务
7. 通知相关人员
8. 切换完成后进入监控状态
六、常见问题与解决方案
Q1:跨机房数据冲突怎么办?
解决方案:
- 按地域划分数据写入,避免冲突
- 使用分布式ID(如雪花算法)避免ID冲突
- 对于必须双向同步的数据,使用时间戳+版本号解决冲突
- 关键冲突场景使用分布式锁
Q2:网络抖动导致切换失败?
解决方案:
- 设置合理的健康检查阈值和重试次数
- 使用TCP长连接检测,避免误判
- 配置多级健康检查(进程+端口+应用)
Q3:如何保证数据一致性?
解决方案:
- 强一致性场景使用同步复制
- 最终一致性场景使用异步复制+补偿机制
- 重要数据采用"写入确认"机制
七、总结
多机房容灾是保障业务高可用的终极方案:
- 架构选型:根据业务需求选择主备、双活或多活
- 数据同步:根据数据特性选择同步方案
- 流量调度:DNS + 应用层双重保障
- 故障切换:自动化健康检查 + 快速切换
实施建议:
- 优先实现数据层的多机房部署
- 核心系统采用双活架构
- 建立完善的监控和告警体系
- 定期进行故障切换演练
思考题:你们系统目前的容灾方案是什么?有哪些改进空间?
个人观点,仅供参考