第 6 章:集群管理
第 6 章:集群管理
掌握 rqlite 集群的节点管理、Leader 选举、数据同步和成员变更操作。
6.1 集群基础
6.1.1 集群概念
rqlite 集群由多个节点(Node)组成,通过 Raft 协议实现数据复制。每个集群有且仅有一个 Leader 节点负责处理写请求。
┌─────────────────────────────────────────────────┐
│ rqlite 集群 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ Leader │ │Follower │ │Follower │ │
│ │ │ │ │ │ │ │
│ │ HTTP:4001│ │ HTTP:4011│ │ HTTP:4021│ │
│ │ Raft:4002│ │ Raft:4012│ │ Raft:4022│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ Raft 通信(心跳 + 日志复制) │
└─────────────────────────────────────────────────┘
6.1.2 节点角色
| 角色 | 写请求 | 读请求(strong) | 读请求(weak) | 读请求(none) |
|---|---|---|---|---|
| Leader | ✅ | ✅ | ✅ | ✅ |
| Follower | ❌ 转发 | ❌ 重定向 | ❌ 重定向 | ✅ |
| Candidate | ❌ | ❌ | ❌ | ❌ |
6.2 节点加入与移除
6.2.1 添加节点到集群
方式一:启动时通过 -join 参数加入
# 新节点启动时直接加入集群
rqlited -node-id=node4 \
-http-addr=0.0.0.0:4031 \
-raft-addr=0.0.0.0:4032 \
-join=http://127.0.0.1:4001 \
/tmp/rqlite/node4
方式二:运行时通过 API 加入
# 确保新节点已独立启动
rqlited -node-id=node4 \
-http-addr=0.0.0.0:4031 \
-raft-addr=0.0.0.0:4032 \
-disco-mode=off \
/tmp/rqlite/node4 &
# 通过 Leader 的 HTTP API 将新节点加入集群
curl -XPOST 'localhost:4001/join' \
-H 'Content-Type: application/json' \
-d '{"id": "node4", "address": "127.0.0.1:4032"}'
注意:
/join请求中的address是 Raft 地址,不是 HTTP 地址。
6.2.2 从集群移除节点
# 移除指定节点
curl -XPOST 'localhost:4001/remove' \
-H 'Content-Type: application/json' \
-d '{"id": "node4"}'
移除节点后的操作:
- 该节点的 Raft 成员资格被撤销
- 该节点收到通知后自动关闭
- 如需重新加入,需要清除数据目录后重新启动
6.2.3 查看集群节点
# 查看所有节点信息
curl -s 'localhost:4001/nodes?pretty' | python3 -m json.tool
输出示例:
{
"nodes": [
{
"id": "node1",
"api_addr": "http://127.0.0.1:4001",
"addr": "127.0.0.1:4002",
"voter": true,
"reachable": true,
"leader": true
},
{
"id": "node2",
"api_addr": "http://127.0.0.1:4011",
"addr": "127.0.0.1:4012",
"voter": true,
"reachable": true,
"leader": false
},
{
"id": "node3",
"api_addr": "http://127.0.0.1:4021",
"addr": "127.0.0.1:4022",
"voter": true,
"reachable": true,
"leader": false
}
]
}
6.3 Leader 选举
6.3.1 选举过程
当集群启动或当前 Leader 宕机时,Raft 协议会触发 Leader 选举:
时间线 ─────────────────────────────────────────────►
1. 集群启动
Node1 ─────────── 成为 Leader(任期 1)
Node2 ─────────── 成为 Follower
Node3 ─────────── 成为 Follower
2. Node1 宕机
Node1 ──────── ✕ (故障)
Node2 ─────────── 选举超时 → 成为 Candidate → 请求投票
Node3 ─────────── 收到投票请求 → 投票给 Node2
Node2 ─────────── 获得多数票 → 成为 Leader(任期 2)
3. Node1 恢复
Node1 ─────────── 重新启动 → 发现更高任期 → 成为 Follower
6.3.2 选举配置
# 查看当前 Raft 配置
curl -s 'localhost:4001/status?pretty' | python3 -c "
import json, sys
data = json.load(sys.stdin)
raft = data.get('store', {}).get('raft', {})
for key, val in raft.items():
print(f'{key}: {val}')
"
6.3.3 手动触发 Leader 转移
# 在当前 Leader 上触发转移
curl -XPOST 'localhost:4001/raft/transfer-leadership'
场景: 需要对 Leader 节点进行维护时,先转移 Leader 再停机。
6.3.4 防止脑裂
rqlite 通过以下机制防止脑裂(Split-Brain):
| 机制 | 说明 |
|---|---|
| Quorum 多数派 | 写入必须获得多数节点确认 |
| Leader 租约 | Leader 在租约期内独占写入权 |
| 任期递增 | 新 Leader 的任期号必须更高 |
| 奇数节点 | 推荐使用奇数节点,避免对等分裂 |
6.4 数据同步
6.4.1 同步机制
写入请求 → Leader → Raft 日志 → 复制到 Follower → 多数确认 → 提交 → 应用到 SQLite
│
▼
Follower 落后时
│
┌─────▼──────┐
│ 日志追赶 │
│ (Catch-Up) │
└─────┬──────┘
│
▼
如果日志已被截断
│
┌─────▼──────┐
│ 快照传输 │
│ (Snapshot) │
└────────────┘
6.4.2 新节点数据同步
当新节点加入集群时:
- Leader 将最新快照(Snapshot)发送给新节点
- 新节点应用快照数据到本地 SQLite
- Leader 将快照之后的日志条目发送给新节点
- 新节点追上后进入正常同步状态
# 查看同步状态
curl -s 'localhost:4001/status?pretty' | python3 -c "
import json, sys
data = json.load(sys.stdin)
store = data.get('store', {})
print(f'Raft State: {store.get(\"raft_state\")}')
print(f'Last Log Index: {store.get(\"last_log_index\")}')
print(f'Last Log Term: {store.get(\"last_log_term\")}')
print(f'Applied Index: {store.get(\"applied_index\")}')
print(f'Commit Index: {store.get(\"commit_index\")}')
"
6.4.3 处理网络分区
当网络分区发生时:
┌──────────────────────┐ ┌──────────────────────┐
│ 分区 A (多数派) │ │ 分区 B (少数派) │
│ │ │ │
│ ┌────────┐ │ │ ┌────────┐ │
│ │ Node 2 │ (Follower)│ │ │ Node 1 │ (Leader) │
│ └────────┘ │ │ └────────┘ │
│ ┌────────┐ │ │ │
│ │ Node 3 │ (Follower)│ │ │
│ └────────┘ │ │ │
│ │ │ │
│ 选举超时后: │ │ 无法获得多数确认 │
│ Node 2 成为新 Leader │ │ 写入操作失败 │
└──────────────────────┘ └──────────────────────┘
关键规则: 拥有多数节点的分区会继续正常工作。少数派分区中的 Leader 会退位,写入操作失败但不会产生数据不一致。
6.5 集群运维操作
6.5.1 滚动升级
升级集群时,逐个替换节点:
# 步骤 1: 升级 Follower 节点
# 停止 node3
systemctl stop rqlited
# 替换二进制
sudo mv /tmp/rqlite-new /usr/local/bin/rqlited
# 重启 node3
systemctl start rqlited
# 确认 node3 恢复正常
curl -s 'localhost:4021/status?pretty' | grep raft_state
# 步骤 2: 升级 node2(同上)
# 步骤 3: 转移 Leader 到已升级节点,再升级原 Leader
curl -XPOST 'localhost:4001/raft/transfer-leadership'
# 等待转移完成
systemctl stop rqlited
sudo mv /tmp/rqlite-new /usr/local/bin/rqlited
systemctl start rqlited
6.5.2 故障节点恢复
# 如果 node3 数据损坏
# 方法 1: 清除数据目录,重新加入
rm -rf /tmp/rqlite/node3/*
rqlited -node-id=node3 \
-http-addr=0.0.0.0:4021 \
-raft-addr=0.0.0.0:4022 \
-join=http://127.0.0.1:4001 \
/tmp/rqlite/node3
# 方法 2: 从备份恢复后加入
# 先从备份加载数据,再加入集群
6.5.3 节点替换(更换节点 ID)
# 1. 从集群移除旧节点
curl -XPOST 'localhost:4001/remove' \
-H 'Content-Type: application/json' \
-d '{"id": "node3"}'
# 2. 清除旧节点数据
rm -rf /tmp/rqlite/node3/*
# 3. 使用新节点 ID 加入
rqlited -node-id=node3-new \
-http-addr=0.0.0.0:4021 \
-raft-addr=0.0.0.0:4022 \
-join=http://127.0.0.1:4001 \
/tmp/rqlite/node3
6.6 集群健康检查脚本
#!/bin/bash
# cluster-health.sh — rqlite 集群健康检查
NODES=("localhost:4001" "localhost:4011" "localhost:4021")
echo "=== rqlite 集群健康检查 ==="
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
for node in "${NODES[@]}"; do
echo "--- 节点: $node ---"
# 状态检查
status=$(curl -s -o /dev/null -w "%{http_code}" "http://$node/status" --connect-timeout 3)
if [ "$status" = "200" ]; then
echo " 状态: ✅ 在线"
# 获取 Raft 状态
raft_state=$(curl -s "http://$node/status" | python3 -c "
import json, sys
data = json.load(sys.stdin)
store = data.get('store', {})
print(store.get('raft_state', 'unknown'))
" 2>/dev/null)
echo " Raft 状态: $raft_state"
else
echo " 状态: ❌ 离线 (HTTP $status)"
fi
echo ""
done
# 检查 Leader
echo "--- Leader 检查 ---"
leader=$(curl -s "http://${NODES[0]}/nodes" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for node in data.get('nodes', []):
if node.get('leader'):
print(f\" Leader: {node['id']} ({node['api_addr']})\")
break
else:
print(' Leader: ⚠️ 未找到 Leader')
" 2>/dev/null)
echo "$leader"
6.7 业务场景:电商平台高可用方案
┌─────────────────┐
│ 负载均衡器 │
│ (Nginx/HAProxy)│
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ Beijing │ │Shanghai │ │ Guangzhou│
│ (Leader)│ │Follower │ │Follower │
└─────────┘ └─────────┘ └─────────┘
数据: 订单、库存、用户地址
写入: 全部路由到 Leader
读取: 可从 Follower 读(none/weak 级别)
关键配置:
# Node 1 (Beijing — Leader)
rqlited -node-id=beijing \
-http-addr=0.0.0.0:4001 \
-raft-addr=0.0.0.0:4002 \
-disco-mode=off \
/var/lib/rqlite/data
# Node 2 (Shanghai — Follower)
rqlited -node-id=shanghai \
-http-addr=0.0.0.0:4001 \
-raft-addr=0.0.0.0:4002 \
-join=http://beijing-node:4001 \
/var/lib/rqlite/data
# Node 3 (Guangzhou — Follower)
rqlited -node-id=guangzhou \
-http-addr=0.0.0.0:4001 \
-raft-addr=0.0.0.0:4002 \
-join=http://beijing-node:4001 \
/var/lib/rqlite/data
读写策略:
| 操作类型 | 路由目标 | 一致性级别 |
|---|---|---|
| 创建订单 | Leader | 强一致性 |
| 扣减库存 | Leader | 强一致性 |
| 查询商品列表 | 任意 Follower | none |
| 查询订单详情 | Leader | strong |
| 查询统计数据 | 任意 Follower | none |
6.8 集群运维速查表
| 操作 | 命令 |
|---|---|
| 查看节点列表 | curl localhost:4001/nodes?pretty |
| 查看集群状态 | curl localhost:4001/status?pretty |
| 添加节点 | curl -XPOST localhost:4001/join -d '{"id":"n4","addr":"x:4002"}' |
| 移除节点 | curl -XPOST localhost:4001/remove -d '{"id":"n4"}' |
| 转移 Leader | curl -XPOST localhost:4001/raft/transfer-leadership |
| 健康检查 | curl localhost:4001/status/ready |
| Leader 检查 | curl localhost:4001/status/leader |
6.9 本章小结
| 要点 | 内容 |
|---|---|
| 集群规模 | 推荐 3 或 5 节点(奇数) |
| Leader 选举 | Raft 自动选举,支持手动转移 |
| 数据同步 | 日志复制 + 快照传输 |
| 网络分区 | 多数派分区继续工作,少数派停止写入 |
| 节点管理 | API 方式动态加入/移除节点 |
| 滚动升级 | 逐节点升级,先升级 Follower 再升级 Leader |
上一章:第 5 章:HTTP API 详解 下一章:第 7 章:备份与恢复