复制表与跨机房容灾:ReplicatedMergeTree、双活架构与脑裂防护

系统讲解 ClickHouse 复制与跨机房容灾:ReplicatedMergeTree 的复制原理(基于 ClickHouse Keeper/ZooKeeper 的日志复制)、多副本读写路径、副本间数据同步延迟、ClickHouse Keeper 与 ZooKeeper 对比、跨机房部署拓扑(同城双活/异地多活)、脑裂(Split Brain)成因与防护(Keeper 仲裁/quorum)、以及故障切换演练与数据一致性校验。

1. 复制模型概览

ClickHouse 的复制是表级的:只有使用 ReplicatedMergeTree 家族引擎(ReplicatedMergeTree、ReplicatedSummingMergeTree 等)的表才有副本。复制依赖一个协调服务(ZooKeeper 或 ClickHouse Keeper)记录日志,让每个副本都朝同一状态收敛。

┌────────────┐   写入    ┌─────────────────────┐
│  应用写入   │ ────────▶ │ ReplicatedMergeTree │
└────────────┘          │  (每个副本独立接收)   │
                        └─────────┬───────────┘
                                  │ 通过 Keeper 复制日志同步
        ┌────────────┬────────────┼────────────┐
        ▼            ▼            ▼            ▼
   ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
   │ replica1 │  │ replica2 │  │ replica3 │  │ replicaN │
   └─────────┘  └─────────┘  └─────────┘  └─────────┘

1.1 关键特性

特性说明
多主写入每个副本都可独立接收写入,无单一写入点
最终一致所有副本最终收敛到同一数据状态
无锁合并合并(merge)在副本间协调,避免重复
依赖协调者Keeper 或 ZooKeeper 是复制中枢

1.2 创建复制表

-- 副本 1
CREATE TABLE events (
  ts DateTime,
  user_id UInt64,
  event String
) ENGINE = ReplicatedMergeTree(
  '/clickhouse/tables/{shard}/events',  -- 副本路径(Keeper 中)
  '{replica}'                            -- 副本名
)
ORDER BY (user_id, ts);

-- 副本 2 使用同一路径、不同副本名
-- /clickhouse/tables/{shard}/events + replica2

一句话总结:复制在表级别生效,靠 Keeper 日志协调多副本朝同一状态收敛——理解"多主 + 最终一致"是理解复制表一切行为的前提。


2. 写入路径与日志复制

2.1 一次写入的完整路径

应用写入 replica1
  → replica1 落盘 Part(不可变数据块)
  → 向 Keeper 提交"新 Part 记录"(复制日志条目)
  → Keeper 分发日志到 replica2/replica3
  → 各副本拉取 Part 或元数据 → 本地落地
  → 各副本确认 → 写入完成

2.2 读写可用性

场景表现
写入任意存活副本可写(多主)
读取从任意副本读(分布式表自动路由)
单副本宕机其他副本照常读写
Keeper 宕机无法协调,副本间暂停复制(本地仍可读写)

2.3 同步延迟指标

-- 查看各副本落后主副本的延迟
SELECT
  database, table, replica_name,
  absolute_delay,   -- 绝对延迟(秒)
  queued_queries
FROM system.replicas
ORDER BY absolute_delay DESC;

一句话总结:写入 = 落盘 Part + 向 Keeper 记日志 + 各副本拉取确认;复制延迟是排障的第一观察指标。


3. ZooKeeper 与 ClickHouse Keeper

3.1 为什么需要协调服务

复制表需要协调:合并任务的分配、Part 记录的广播、副本状态登记。ZooKeeper 曾是唯一选择,但存在运维复杂、可用性敏感等问题。ClickHouse Keeper 是原生替代(C++ 实现、协议兼容 ZooKeeper)。

3.2 对比

维度ZooKeeperClickHouse Keeper
实现语言JavaC++
内存占用高(JVM)低(~1GB/节点)
部署方式独立集群可内嵌 clickhouse-server
协议ZK 原生兼容 ZK
适用规模中大规模与 ClickHouse 同生共长
运维复杂度较高较低

3.3 Keeper 配置要点

<clickhouse>
  <keeper_server>
    <tcp_port>9181</tcp_port>
    <server_id>1</server_id>
    <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
    <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
    <coordination_settings>
      <operation_timeout_ms>10000</operation_timeout_ms>
      <session_timeout_ms>30000</session_timeout_ms>
    </coordination_settings>
    <raft_configuration>
      <server>
        <id>1</id>
        <hostname>keeper1</hostname>
        <port>9234</port>
      </server>
      <server>
        <id>2</id>
        <hostname>keeper2</hostname>
        <port>9234</port>
      </server>
      <server>
        <id>3</id>
        <hostname>keeper3</hostname>
        <port>9234</port>
      </server>
    </raft_configuration>
  </keeper_server>
</clickhouse>

一句话总结:协调服务是复制的"仲裁者";ClickHouse Keeper 用更低成本提供了与 ZooKeeper 兼容的协调能力,是新建集群的首选。


4. 跨机房容灾拓扑

4.1 同城双活

机房 A ────────────── 机房 B
  ├ replica1          ├ replica2
  ├ replica3          ├ replica4
  └ Keeper1           └ Keeper2
                     Keeper3(第三地/仲裁)
  • 数据在双机房各持副本,任意机房故障可继续服务
  • 关键:Keeper 仲裁放第三地,避免双机房各半失联

4.2 异地多活

数据中心1(主)   数据中心2(备)   数据中心3(备)
  replica×2          replica×2        replica×2
  写入热点          低频写入/只读   低频写入/只读
  • 异地延迟高,适合"主写 + 异地只读容灾"
  • 主动-被动模式:故障后提升备机房为主

4.3 拓扑决策表

场景副本数Keeper 布局写入模型
单机房高可用2-3单机房 3 节点任意副本
同城双活每机房 2双机房 + 第三地仲裁任意副本
异地容灾主 2 + 备 2主机房为主主写备读
多地多活每地 2跨地域 raft就近写入

一句话总结:跨机房拓扑的核心不是"多几份数据",而是 Keeper 仲裁怎么放、写入点怎么定——仲裁位置的错误选择比数据丢失更致命。


5. 脑裂(Split Brain)与防护

5.1 脑裂成因

当 Keeper 集群分裂成两个无法互通的组,且两组各自选主时,副本们可能朝两个不同状态收敛——这就是脑裂。

场景:Keeper 3 节点,机房间网络中断
  组 A:Keeper1 + Keeper2(多数派)
  组 B:Keeper3(少数派)
  → 多数派仍可服务(raft quorum)
  → 少数派失联,不再参与 → 通常不会双写
  真正风险:配置错误 / 人为 failover 命令

5.2 防护措施

防护做法
奇数节点Keeper 用 3/5 奇数,避免平票
严格 quorum写入需要多数派确认(raft 天然保证)
勿手动主备切换依赖 raft 自动选主
网络分区预案监控 Keeper 成员可达性
心跳超时调优session_timeout 合理设置

5.3 双主误判的检测

-- 检查副本间数据偏差
SELECT
  replica_name,
  parts_count,
  total_rows
FROM system.replicas;

-- 用哈希校验分布一致性
SELECT
  database, table,
  count() AS shards,
  uniqExact(pairwiseHash) AS distinct_hashes
FROM (
  SELECT
    database, table,
    cityHash64(groupArray(tuple(part_name))) AS pairwiseHash
  FROM system.parts
  GROUP BY database, table, replica
)
GROUP BY database, table;

一句话总结:脑裂的根子多在仲裁配置而非网络;用奇数 Keeper + 依赖 raft quorum,把"人为双活"变成不可能,是防脑裂的第一原则。


6. 故障切换演练

6.1 演练清单

1. 单副本宕机演练
   - 停止 replica1 → 写入仍成功(走 replica2/3)
   - 查询仍可用 → 检查 absolute_delay
   - 恢复 replica1 → 自动追赶复制日志
2. 机房级故障演练
   - 切断机房 A → 机房 B 独立服务
   - 验证 Keeper 仲裁仍存活
   - 验证写入/查询的 P99 延迟
3. 数据一致性验证
   - 双机房对拍 sum/hash → 确认收敛
4. 恢复后回切
   - 机房 A 恢复 → 自动补同步 → 切回正常拓扑

6.2 演练工具

# 模拟副本故障:暂停服务进程
systemctl stop clickhouse-server

# 观察复制状态
clickhouse-client --query "SELECT * FROM system.replicas FORMAT Pretty"

# 手动触发数据一致性校验
clickhouse-client --query "SYSTEM SYNC REPLICA"

6.3 RTO / RPO 目标

指标目标
RPO(数据丢失容忍)取决于写入确认策略;多主写入时损失窗口 ≈ 同步延迟
RTO(恢复时间)秒级(副本自动接管);机房级看拓扑

一句话总结:容灾不是"买了副本就行",而是"演练出来的"——周期性故障注入是验证拓扑正确性的唯一可靠手段。


7. 一致性校验与数据对账

7.1 常见校验手段

-- 按表聚合对比
SELECT 'events' AS table,
  sum(total_rows) AS rows,
  sum(total_bytes) AS bytes
FROM system.parts
WHERE database = 'default' AND active;

-- 按分区校验
SELECT partition,
  count() AS parts,
  sum(rows) AS rows
FROM system.parts
WHERE table = 'events'
GROUP BY partition
ORDER BY partition;

7.2 深校验:抽样哈希

-- 抽 10000 行对比关键字段指纹
SELECT
  count(),
  uniqExact(concat(toString(user_id), '_', toString(ts)))
FROM events
SAMPLE 0.001

7.3 对账自动化

调度任务(cron / 数据质量平台)
  → 对每张复制表做 count/hash 对拍
  → 偏差超过阈值 → 告警
  → 定位差异分区 → 用备份回补或重建副本

一句话总结:副本的正确性靠"持续对账"而非"一次验证"——把 count/hash 对拍纳入日常监控,才能保证容灾体系真正可用。


8. 生产实践清单

主题核心结论
复制模型表级复制,多主写入,最终一致
协调服务优先 ClickHouse Keeper(3/5 奇数)
跨机房同城双活 + 第三地仲裁;异地主备
脑裂防护奇数节点 + raft quorum + 不手动切主
容灾演练周期注入故障,验证 RTO/RPO
一致性count/hash 对拍纳入日常监控
延迟监控盯 system.replicas 的 absolute_delay

跨机房容灾不是"多放几份数据",而是"协调层怎么放、写入怎么定、故障怎么切、一致性怎么证"四件事的完整闭环。把这四件事做扎实,ClickHouse 就能扛住机房级故障。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据库」更多文章

  1. 查询缓存与预热:缓存策略、热点治理与查询加速
  2. 时序分析最佳实践:时间序列建模、降采样与异常检测 SQL
  3. 字典与维度表 JOIN:Dictionaries、dictGet 与星型模型优化