数据复制 #
一、复制概述 #
1.1 复制原理 #
text
数据复制架构
┌─────────────────────────────────────────────────────────────┐
│ │
│ 每个 Range 有多个副本: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Range 1 │ │
│ │ │ │
│ │ ┌──────────┐ │ │
│ │ │ Leader │ Replica 1 (Node 1) │ │
│ │ │ │ 处理读写请求 │ │
│ │ └──────────┘ │ │
│ │ │ │ │
│ │ ├─────────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │Follower │ │Follower │ │ │
│ │ │ Replica 2│ │ Replica 3│ │ │
│ │ │ (Node 2) │ │ (Node 3) │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 默认副本数: 3 │
│ 通过 Raft 协议保持一致性 │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 复制特点 #
| 特点 | 说明 |
|---|---|
| 强一致性 | Raft协议保证 |
| 自动故障恢复 | 自动选举新Leader |
| 可配置副本数 | 根据需求调整 |
| 跨区域复制 | 支持多区域部署 |
二、Raft协议 #
2.1 Raft角色 #
text
Raft 角色
┌─────────────────────────────────────────────────────────────┐
│ │
│ Leader: │
│ ├── 处理所有客户端请求 │
│ ├── 复制日志到 Followers │
│ └── 一个 Raft Group 只有一个 Leader │
│ │
│ Follower: │
│ ├── 接收 Leader 的日志 │
│ ├── 应用日志到状态机 │
│ └── 响应 Leader 的心跳 │
│ │
│ Candidate: │
│ ├── 选举时的临时角色 │
│ ├── 发起投票请求 │
│ └── 获得多数票后成为 Leader │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 日志复制 #
text
日志复制流程
┌─────────────────────────────────────────────────────────────┐
│ │
│ 客户端写入 │
│ │ │
│ ▼ │
│ 1. Leader 接收请求 │
│ │ │
│ ▼ │
│ 2. Leader 追加日志到本地 │
│ │ │
│ ▼ │
│ 3. Leader 复制日志到 Followers │
│ │ │
│ │ ┌──────────┐ │
│ │ │ Leader │ │
│ │ └────┬─────┘ │
│ │ │ │
│ │ ┌────┴────┐ │
│ │ ▼ ▼ │
│ │ ┌──────┐ ┌──────┐ │
│ │ │Foll 1│ │Foll 2│ │
│ │ └──────┘ └──────┘ │
│ │ │
│ ▼ │
│ 4. 多数节点确认 (Quorum) │
│ │ │
│ ▼ │
│ 5. Leader 提交日志 │
│ │ │
│ ▼ │
│ 6. 返回客户端成功 │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 Leader选举 #
text
Leader 选举流程
┌─────────────────────────────────────────────────────────────┐
│ │
│ 触发条件: │
│ ├── Leader 故障 │
│ └── Follower 超时未收到心跳 │
│ │
│ 选举过程: │
│ │
│ 1. Follower 超时,转为 Candidate │
│ │ │
│ ▼ │
│ 2. Candidate 增加任期号 │
│ │ │
│ ▼ │
│ 3. 向其他节点发送投票请求 │
│ │ │
│ ▼ │
│ 4. 收集投票 │
│ │ │
│ ├── 多数投票 → 成为 Leader │
│ │ │
│ ├── 收到更高任期 → 降为 Follower │
│ │ │
│ └── 未获多数 → 重新选举 │
│ │
│ 选举安全: │
│ ├── 一个任期最多一个 Leader │
│ └── 只有最新日志的节点才能当选 │
│ │
└─────────────────────────────────────────────────────────────┘
三、副本配置 #
3.1 配置副本数 #
sql
-- 查看当前Zone配置
SHOW ZONE CONFIGURATION FOR RANGE default;
-- 配置默认副本数
ALTER RANGE default CONFIGURE ZONE USING
num_replicas = 5;
-- 配置数据库副本数
ALTER DATABASE mydb CONFIGURE ZONE USING
num_replicas = 5;
-- 配置表副本数
ALTER TABLE users CONFIGURE ZONE USING
num_replicas = 3;
-- 配置索引副本数
ALTER INDEX users@idx_email CONFIGURE ZONE USING
num_replicas = 3;
3.2 副本放置约束 #
sql
-- 配置区域约束
ALTER DATABASE mydb CONFIGURE ZONE USING
constraints = '[+region=us-east]';
-- 配置多区域约束
ALTER DATABASE mydb CONFIGURE ZONE USING
constraints = '[+region=us-east]',
replica_constraints = '[[+region=us-west], [+region=eu-west]]';
-- 查看Zone配置
SHOW ZONE CONFIGURATION FOR DATABASE mydb;
3.3 副本分布查看 #
sql
-- 查看Range分布
SELECT
range_id,
start_key,
end_key,
replicas,
lease_holder
FROM crdb_internal.ranges
LIMIT 10;
-- 查看特定表的Range
SHOW RANGES FROM TABLE users;
-- 查看节点上的Range数量
SELECT
node_id,
COUNT(*) AS range_count
FROM crdb_internal.ranges
CROSS JOIN UNNEST(replicas) AS node_id
GROUP BY node_id
ORDER BY node_id;
四、高可用设计 #
4.1 容错能力 #
text
容错能力
┌─────────────────────────────────────────────────────────────┐
│ │
│ 副本数与容错能力: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 副本数 │ 可容忍故障节点 │ 最小节点数 │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 3 │ 1 │ 3 │ │
│ │ 5 │ 2 │ 5 │ │
│ │ 7 │ 3 │ 7 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Quorum 计算: │
│ Quorum = (副本数 / 2) + 1 │
│ │
│ 示例: │
│ 3副本: Quorum = 2, 可容忍 1 个故障 │
│ 5副本: Quorum = 3, 可容忍 2 个故障 │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 故障恢复 #
text
故障恢复流程
┌─────────────────────────────────────────────────────────────┐
│ │
│ 节点故障检测: │
│ ├── 心跳超时 │
│ └── Gossip 信息更新 │
│ │
│ 恢复过程: │
│ │
│ 1. 检测到节点故障 │
│ │ │
│ ▼ │
│ 2. 受影响的 Range 触发 Leader 选举 │
│ │ │
│ ▼ │
│ 3. 新 Leader 选出 │
│ │ │
│ ▼ │
│ 4. 添加新副本到其他节点 │
│ │ │
│ ▼ │
│ 5. 数据同步 │
│ │ │
│ ▼ │
│ 6. 集群恢复正常 │
│ │
└─────────────────────────────────────────────────────────────┘
4.3 多区域部署 #
sql
-- 多区域部署配置
-- 假设有3个区域: us-east, us-west, eu-west
-- 配置数据库放置策略
ALTER DATABASE mydb CONFIGURE ZONE USING
num_replicas = 5,
constraints = '[+region=us-east]',
replica_constraints = '[[+region=us-west], [+region=eu-west]]';
-- 配置特定表的放置策略
ALTER TABLE users CONFIGURE ZONE USING
num_replicas = 3,
constraints = '[+region=us-east]',
replica_constraints = '[[+region=us-west], [+region=eu-west]]';
-- 查看区域分布
SELECT
r.range_id,
r.replicas,
n.locality
FROM crdb_internal.ranges r
CROSS JOIN UNNEST(r.replicas) AS replica
JOIN crdb_internal.kv_node_status n ON n.node_id = replica
WHERE r.start_key >= '/Table/53'
AND r.end_key < '/Table/54';
五、副本管理 #
5.1 副本迁移 #
sql
-- 手动触发副本迁移
-- 将Range迁移到特定节点
ALTER RANGE 123 RELOCATE TO [1, 2, 3];
-- 查看迁移状态
SELECT * FROM crdb_internal.ranges
WHERE range_id = 123;
5.2 副本状态查看 #
sql
-- 查看副本状态
SELECT
range_id,
replicas,
voting_replicas,
lease_holder,
lease_holder_locality
FROM crdb_internal.ranges
LIMIT 20;
-- 查看副本不足的Range
SELECT
range_id,
replicas,
array_length(replicas, 1) AS replica_count
FROM crdb_internal.ranges
WHERE array_length(replicas, 1) < 3;
5.3 租约持有者 #
sql
-- 查看租约持有者分布
SELECT
lease_holder,
COUNT(*) AS range_count
FROM crdb_internal.ranges
GROUP BY lease_holder
ORDER BY range_count DESC;
-- 手动转移租约
ALTER RANGE 123 TRANSFER LEASE TO 2;
六、读写一致性 #
6.1 一致性级别 #
text
读写一致性级别
┌─────────────────────────────────────────────────────────────┐
│ │
│ 强一致性读: │
│ ├── 从 Leader 读取 │
│ ├── 保证最新数据 │
│ └── 延迟较高 │
│ │
│ 过期读 (Stale Read): │
│ ├── 从 Follower 读取 │
│ ├── 可能不是最新数据 │
│ └── 延迟较低 │
│ │
│ 时间旅行查询: │
│ ├── AS OF SYSTEM TIME │
│ ├── 读取历史数据 │
│ └── 不阻塞写入 │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 过期读配置 #
sql
-- 使用 AS OF SYSTEM TIME
SELECT * FROM users
AS OF SYSTEM TIME '-10s';
-- 使用时间戳
SELECT * FROM users
AS OF SYSTEM TIME 1711555200000000000;
-- 在事务中使用
BEGIN AS OF SYSTEM TIME '-1m';
SELECT * FROM users;
COMMIT;
-- 配置最大过期时间
SET SESSION CHARACTERISTICS AS TRANSACTION
AS OF SYSTEM TIME '-30s';
七、监控与运维 #
7.1 监控指标 #
sql
-- 查看集群状态
SELECT * FROM crdb_internal.kv_node_status;
-- 查看副本健康状态
SELECT
range_id,
replicas,
lease_holder,
CASE
WHEN array_length(replicas, 1) >= 3 THEN 'healthy'
WHEN array_length(replicas, 1) >= 2 THEN 'degraded'
ELSE 'critical'
END AS health_status
FROM crdb_internal.ranges;
-- 查看Raft状态
SELECT
range_id,
raft_state
FROM crdb_internal.ranges
LIMIT 10;
7.2 运维操作 #
sql
-- 添加节点后重新均衡
-- CockroachDB 自动均衡,无需手动操作
-- 移除节点
-- 先安全下线
cockroach node drain --host=<node-host>:26257
-- 然后移除
cockroach node decommission <node-id> --host=<any-node>:26257
-- 查看下线状态
SELECT * FROM crdb_internal.kv_node_status
WHERE draining = true;
八、总结 #
数据复制要点:
| 特性 | 说明 |
|---|---|
| Raft协议 | 保证强一致性 |
| 副本管理 | 自动分布和均衡 |
| 高可用 | 自动故障恢复 |
| 多区域 | 支持跨区域部署 |
| 一致性 | 强一致和过期读 |
下一步,让我们学习数据分区!
最后更新:2026-03-27