强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

dqlite 分布式 SQLite 教程 / 第 10 章:生产最佳实践

第 10 章:生产最佳实践

本章总结 dqlite 在生产环境中的最佳实践,包括技术选型决策、容量规划、监控告警、运维 SOP 和常见陷阱。


10.1 何时选择 dqlite

10.1.1 决策树

需要分布式数据库?
    │
    ├── 否 → 直接用 SQLite
    │
    └── 是 → 数据量多大?
              │
              ├── > 10GB → 考虑 PostgreSQL / CockroachDB
              │
              └── ≤ 10GB → 需要 HTTP API?
                            │
                            ├── 是 → 考虑 rqlite
                            │
                            └── 否 → 需要嵌入式?
                                      │
                                      ├── 是 → 平台是 Linux?
                                      │        │
                                      │        ├── 是 → ✅ 选择 dqlite
                                      │        │
                                      │        └── 否 → 考虑 rqlite 或 etcd
                                      │
                                      └── 否 → 考虑 rqlite / Consul / etcd

10.1.2 dqlite 适用场景详细分析

场景 评分 说明
容器管理器/运行时 ⭐⭐⭐⭐⭐ LXD 验证的最佳场景
Kubernetes 数据存储 ⭐⭐⭐⭐ MicroK8s 使用,适合中小规模
边缘计算/IoT ⭐⭐⭐⭐ 轻量级,资源占用小
嵌入式 Linux 设备 ⭐⭐⭐⭐ 无外部依赖
配置管理 ⭐⭐⭐ 可用,但 etcd 可能更成熟
Web 应用后端 ⭐⭐ rqlite 更合适(HTTP API)
高吞吐数据管道 不适合,考虑 Kafka + 数据库
多数据中心 不适合,考虑 CockroachDB

10.1.3 与替代方案对比

维度 dqlite etcd Consul rqlite
存储模型 关系型 (SQL) KV KV 关系型 (SQL)
查询能力 完整 SQL 有限 完整 SQL
嵌入式
依赖 libuv
数据量上限 ~10GB ~8GB ~10GB ~10GB
成熟度 最高
复杂度

10.2 容量规划

10.2.1 节点规格建议

规模 节点数 CPU 内存 存储 网络
开发测试 1 1 核 512MB 1GB SSD 任意
小型生产 3 2 核 1GB 10GB SSD 千兆
中型生产 3 4 核 4GB 50GB SSD 千兆
大型生产 5 8 核 8GB 100GB SSD 万兆

10.2.2 数据容量评估

存储空间计算:

数据库大小 = 原始数据 + 索引 + WAL 缓冲 + Raft 日志

示例计算:
  原始数据:        1GB
  索引 (约 30%):   0.3GB
  WAL 缓冲:        0.1GB
  Raft 日志 (2x):  0.2GB (快照后保留)
  安全余量 (20%):  0.32GB
  ─────────────────────
  总计:            ~2GB

磁盘建议: 原始数据 × 3 倍

10.2.3 性能容量计算

写入容量(3 节点集群):
  单条写 QPS ≈ 1000-2000 (取决于硬件和网络)
  批量写 QPS ≈ 5000-20000 (100 条/事务)

读取容量:
  单节点读 QPS ≈ 10000-50000
  3 节点分散读 ≈ 30000-150000

连接数:
  推荐 10-50 个连接(SQLite 写入串行)

并发事务:
  写事务: 串行(1 个并发)
  读事务: 并发(受限于连接数)

10.2.4 容量规划表

指标 公式 示例
所需磁盘 数据量 × 3 10GB 数据 → 30GB 磁盘
所需内存 max(1GB, 数据量 × 0.1 + 256MB) 10GB 数据 → 1.25GB
所需 CPU 基础 1 核 + 每 1000 QPS 加 1 核 5000 QPS → 6 核
网络带宽 写入 QPS × 平均事务大小 × 节点数 1000 × 1KB × 3 = 3MB/s

10.3 监控告警

10.3.1 关键监控指标

类别 指标 阈值 告警级别
可用性 节点存活数 < Quorum P0 (严重)
可用性 Leader 是否存在 无 Leader > 5s P0
性能 写延迟 P99 > 10ms P1 (警告)
性能 读延迟 P99 > 5ms P1
性能 写 QPS > 容量 80% P2 (关注)
存储 磁盘使用率 > 80% P1
存储 数据库大小 > 容量 70% P1
Raft 日志滞后 > 1000 条 P1
Raft 快照耗时 > 30s P2
网络 节点间延迟 > 10ms P1

10.3.2 Prometheus 监控配置

# prometheus/alert-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: dqlite-alerts
spec:
  groups:
    - name: dqlite
      rules:
        # 节点存活检查
        - alert: DqliteNodeDown
          expr: up{job="dqlite"} == 0
          for: 1m
          labels:
            severity: critical
          annotations:
            summary: "dqlite 节点 {{ $labels.instance }} 不可用"
            description: "节点已离线超过 1 分钟"

        # Leader 缺失
        - alert: DqliteNoLeader
          expr: dqlite_raft_leader_id == 0
          for: 5s
          labels:
            severity: critical
          annotations:
            summary: "dqlite 集群无 Leader"

        # 写延迟过高
        - alert: DqliteHighWriteLatency
          expr: histogram_quantile(0.99, dqlite_write_latency_seconds_bucket) > 0.01
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "dqlite 写延迟 P99 > 10ms"

        # 磁盘空间
        - alert: DqliteDiskSpaceHigh
          expr: (dqlite_disk_used_bytes / dqlite_disk_total_bytes) > 0.8
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "dqlite 磁盘使用率 > 80%"

        # Raft 日志滞后
        - alert: DqliteRaftLogLag
          expr: dqlite_raft_log_entries - dqlite_raft_commit_index > 1000
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Raft 日志滞后 > 1000 条"

10.3.3 Grafana Dashboard

关键面板建议:

┌─────────────────────────────────────────────────────────────┐
│                    dqlite Dashboard                          │
├─────────────┬─────────────┬─────────────┬──────────────────┤
│ 节点状态     │ Leader 状态  │ 集群大小    │ 当前任期          │
│  ● 3/3     │  ● Node 1   │  ● 3       │  ● 42           │
├─────────────┴─────────────┴─────────────┴──────────────────┤
│ 写延迟 (P50/P95/P99)                                        │
│ ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░  2.1ms / 4.5ms / 8.2ms│
├─────────────────────────────────────────────────────────────┤
│ QPS (写入/读取)                                              │
│ ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅▆▇  写: 1.2K  读: 8.5K                 │
├──────────────┬──────────────┬──────────────────────────────┤
│ 磁盘使用率    │ 数据库大小    │ Raft 日志条目数               │
│ ▓▓▓▓▓░░░ 45% │  2.3GB       │  1,234                       │
├──────────────┴──────────────┴──────────────────────────────┤
│ 快照状态                                                    │
│ 上次快照: 2 分钟前  │  耗时: 1.2s  │  日志已清理: 1024 条     │
└─────────────────────────────────────────────────────────────┘

10.4 运维 SOP

10.4.1 日常巡检

#!/bin/bash
# daily-check.sh - dqlite 日常巡检脚本

set -euo pipefail

echo "=== dqlite Daily Check - $(date) ==="

# 1. 检查节点存活
echo -e "\n[1] Node Health:"
for node in "127.0.0.1:9001" "127.0.0.1:9002" "127.0.0.1:9003"; do
    if nc -z -w2 ${node/:/ } 2>/dev/null; then
        echo "  ✓ $node - UP"
    else
        echo "  ✗ $node - DOWN"
    fi
done

# 2. 检查 Leader
echo -e "\n[2] Leader Status:"
# 通过客户端查询集群信息
echo "  (执行集群状态查询...)"

# 3. 检查磁盘使用
echo -e "\n[3] Disk Usage:"
for dir in /var/lib/dqlite/node{1,2,3}; do
    if [ -d "$dir" ]; then
        usage=$(du -sh "$dir" 2>/dev/null | cut -f1)
        echo "  $dir: $usage"
    fi
done

# 4. 检查日志
echo -e "\n[4] Recent Errors:"
journalctl -u dqlite --since "24 hours ago" --priority=err --no-pager | tail -5

# 5. 检查数据库大小
echo -e "\n[5] Database Size:"
find /var/lib/dqlite -name "*.db" -exec ls -lh {} \;

echo -e "\n=== Check Complete ==="

10.4.2 备份 SOP

#!/bin/bash
# backup.sh - dqlite 备份脚本

set -euo pipefail

BACKUP_DIR="/backup/dqlite/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

echo "Starting dqlite backup to $BACKUP_DIR..."

# 方式 1:使用 dqlite dump 工具
dqlite-dump \
    --address "127.0.0.1:9001" \
    --output "$BACKUP_DIR/dump.sql"

# 方式 2:复制数据文件(需要先暂停写入或使用一致性快照)
# 停止一个 Follower 节点,复制其数据目录
# systemctl stop dqlite-node-3
# cp -r /var/lib/dqlite/node3/* "$BACKUP_DIR/"
# systemctl start dqlite-node-3

# 压缩
gzip "$BACKUP_DIR/dump.sql"

# 上传到远程存储
# aws s3 sync "$BACKUP_DIR" "s3://backups/dqlite/$(basename $BACKUP_DIR)/"

# 清理 30 天前的备份
find /backup/dqlite -maxdepth 1 -mtime +30 -exec rm -rf {} \;

echo "Backup completed: $BACKUP_DIR"

10.4.3 恢复 SOP

#!/bin/bash
# restore.sh - dqlite 恢复脚本

set -euo pipefail

BACKUP_FILE="$1"
TARGET_ADDRESS="${2:-127.0.0.1:9001}"

if [ ! -f "$BACKUP_FILE" ]; then
    echo "Error: Backup file not found: $BACKUP_FILE"
    exit 1
fi

echo "=== dqlite Restore Procedure ==="
echo "Backup: $BACKUP_FILE"
echo "Target: $TARGET_ADDRESS"
echo ""
echo "WARNING: This will overwrite existing data!"
read -p "Continue? (yes/no): " confirm

if [ "$confirm" != "yes" ]; then
    echo "Aborted."
    exit 0
fi

# 1. 停止所有节点
echo "[1] Stopping all dqlite nodes..."
# systemctl stop dqlite-node-1 dqlite-node-2 dqlite-node-3

# 2. 清空数据目录
echo "[2] Clearing data directories..."
# rm -rf /var/lib/dqlite/node1/* /var/lib/dqlite/node2/* /var/lib/dqlite/node3/*

# 3. 在第一个节点恢复数据
echo "[3] Restoring data..."
# dqlite-restore --backup "$BACKUP_FILE" --data-dir /var/lib/dqlite/node1

# 4. 启动第一个节点
echo "[4] Starting first node..."
# systemctl start dqlite-node-1

# 5. 等待节点就绪
echo "[5] Waiting for node to be ready..."
# sleep 5

# 6. 重新初始化集群
echo "[6] Re-initializing cluster..."
# dqlite-add-node --leader 127.0.0.1:9001 --id 2 --address 127.0.0.1:9002
# dqlite-add-node --leader 127.0.0.1:9001 --id 3 --address 127.0.0.1:9003

# 7. 启动其他节点
echo "[7] Starting remaining nodes..."
# systemctl start dqlite-node-2 dqlite-node-3

echo ""
echo "Restore completed. Please verify data integrity."

10.4.4 滚动重启 SOP

#!/bin/bash
# rolling-restart.sh - 滚动重启 dqlite 集群

set -euo pipefail

NODES=("dqlite-node-1" "dqlite-node-2" "dqlite-node-3")

for node in "${NODES[@]}"; do
    echo "=== Restarting $node ==="

    # 1. 确认集群健康(当前节点不是唯一 Leader)
    echo "[1] Checking cluster health..."
    # ... 检查逻辑 ...

    # 2. 如果是 Leader,先转移
    echo "[2] Transferring leadership if needed..."
    # ... 转移逻辑 ...

    # 3. 停止节点
    echo "[3] Stopping $node..."
    systemctl stop "$node"

    # 4. 等待集群稳定
    echo "[4] Waiting for cluster to stabilize..."
    sleep 5

    # 5. 启动节点
    echo "[5] Starting $node..."
    systemctl start "$node"

    # 6. 等待节点加入集群
    echo "[6] Waiting for node to rejoin..."
    sleep 10

    # 7. 验证节点状态
    echo "[7] Verifying node status..."
    # ... 验证逻辑 ...

    echo "=== $node restarted successfully ==="
    echo ""
done

echo "All nodes restarted."

10.5 常见陷阱与避坑指南

10.5.1 部署陷阱

陷阱 后果 正确做法
使用偶数节点 不增加容错,浪费资源 始终使用 3、5、7 节点
跨机房部署 Raft 对延迟敏感 同机房或同城部署
使用 NFS 存储 文件锁问题,数据损坏 使用本地 SSD
不做备份 数据丢失无法恢复 定期备份 + 验证恢复
不同步时钟 选举异常,日志不一致 使用 NTP 同步

10.5.2 开发陷阱

陷阱 后果 正确做法
每条 INSERT 一个事务 性能极差 批量操作在同一事务中
不处理 BUSY 错误 偶发失败 实现指数退避重试
读取不走 Leader 数据陈旧 确认一致性需求
不使用参数绑定 SQL 注入风险 始终使用 ? 参数
连接池配置不当 连接泄漏或过少 合理配置 MaxOpenConns

10.5.3 运维陷阱

陷阱 后果 正确做法
不监控集群状态 故障不可感知 配置告警规则
不测试恢复流程 真正恢复时手忙脚乱 定期演练恢复
一次性变更多个节点 Quorum 混乱 一次只变更一个
不检查磁盘空间 写入失败 监控磁盘使用率
不更新版本 错过安全补丁 跟踪版本发布

10.6 性能调优速查表

优化项 配置 预期收益
批量写入 100-500 条/事务 10-100x
预编译语句 Prepare + Execute 2-3x
同步策略 NORMAL PRAGMA synchronous=NORMAL 2-3x
内存缓存 PRAGMA cache_size=-16000 读性能提升
内存映射 PRAGMA mmap_size=256MB 大数据集读提升
WAL 自动检查点 PRAGMA wal_autocheckpoint=1000 控制 WAL 大小
SSD 存储 使用 NVMe SSD 显著降低 I/O 延迟
同机房部署 节点延迟 < 1ms 降低写延迟

10.7 故障排查速查表

症状 可能原因 排查步骤 解决方案
写入超时 Leader 不可达 1. 检查集群状态 2. 检查网络 恢复 Leader 或网络
读取数据陈旧 读了 Follower 1. 确认读一致性需求 配置 Leader 读
集群无法启动 配置不一致 1. 对比各节点配置 统一配置
节点反复选举 网络不稳定 1. 检查节点间延迟 改善网络
磁盘空间不足 数据增长 1. 检查数据库大小 清理数据或扩容
内存占用高 缓存配置过大 1. 检查 PRAGMA cache_size 减小缓存
快照失败 I/O 错误 1. 检查磁盘健康 修复磁盘

10.8 生产检查清单

10.8.1 上线前检查

检查项 状态 说明
□ 节点数为奇数 3、5、7
□ TLS 已启用 双向认证
□ 防火墙已配置 仅允许必要端口
□ 数据目录权限正确 专用用户运行
□ 备份策略已实施 定期备份 + 异地存储
□ 监控已部署 Prometheus + Grafana
□ 告警规则已配置 节点故障、Leader 丢失等
□ 日志收集已配置 ELK / Loki
□ 时钟同步 NTP 已配置
□ 存储类型正确 SSD,非 NFS
□ 容量规划完成 磁盘、内存、CPU
□ 恢复流程已测试 定期演练
□ 滚动更新流程已验证 无停机更新
□ 文档已更新 运维手册

10.8.2 定期检查(每月)

检查项 动作
数据库大小增长趋势 预测何时需要扩容
备份恢复测试 验证备份可用性
证书有效期 提前 30 天续期
安全补丁 更新到最新版本
性能基准测试 确认性能未退化

本章小结

实践领域 关键要点
技术选型 dqlite 适合嵌入式 Linux 场景,数据量 < 10GB
容量规划 磁盘 3 倍数据量,内存 = 数据量 × 0.1 + 256MB
监控 节点存活、Leader 状态、延迟、磁盘、Raft 日志
备份 每日备份 + 异地存储 + 定期恢复演练
避坑 奇数节点、同机房、SSD、NTP、批量事务

全书完

恭喜你完成了 dqlite 分布式 SQLite 教程的全部 10 章学习!

快速回顾

章节 内容
第 1 章 dqlite 是什么,与 rqlite 对比
第 2 章 安装编译,Docker 快速上手
第 3 章 Raft 共识,日志复制,快照
第 4 章 CRUD 操作,连接管理,事务
第 5 章 C API,Go 绑定,错误处理
第 6 章 集群搭建,Leader 选举,故障转移
第 7 章 批量写入,读优化,同步策略
第 8 章 TLS,认证,访问控制
第 9 章 Docker,Kubernetes,持久化
第 10 章 最佳实践,监控,运维 SOP

进一步学习

如有问题,欢迎在 dqlite GitHub Issues 中提问。