ClickHouse 高可用部署:副本同步延迟的排查与止损方案
ClickHouse 高可用部署副本同步延迟的排查与止损方案大家好我是朱大喜。今天聊一个真实的半夜惊魂场景凌晨两点业务方发现看板数据比实时少了 30%一查——ClickHouse 副本同步延迟了 15 分钟。这篇文章带你从排查到止损一套带走。一、为什么 ClickHouse 的副本会慢半拍ClickHouse 用了 ReplicatedMergeTree 引擎家族来实现副本同步底层依赖 ZooKeeper或 ClickHouse Keeper做元数据协调。但很多同学对它的同步机制有个误解以为它是主从复制——错的ClickHouse 是多主架构任何副本都可以接收写入。graph TB subgraph ClickHouse多主副本架构 A[写入请求] -- B[Node1br/Replica 1br/Leader in ZK] A -- C[Node2br/Replica 2br/Follower] B -- D[写入本地磁盘] B -- E[ZooKeeperbr/记录Part元数据] C -- F[监听ZK节点变化] F -- G[发现新Part] G -- H[从Node1拉取数据] H -- I[写入本地磁盘] end subgraph 可能出现延迟的环节 J[ZK响应慢] -.- F K[网络带宽不足] -.- H L[磁盘IO瓶颈] -.- I end style J fill:#faa,stroke:#333 style K fill:#faa,stroke:#333 style L fill:#faa,stroke:#333写入流程看起来简单但延迟会卡在三个关键环节ZooKeeper 压力每次写入都要更新 ZK 节点写入量大时 ZK 成为瓶颈网络传输副本之间拉取 part 数据带宽不够就排队副本端磁盘 IOFollower 下载完 part 后还要做 merge磁盘慢直接拖后腿。为什么多主架构比主从架构更容易出现延迟主从架构只有 Leader 写入Follower 是被动接收——写入速度由 Leader 控制Follower 追不上最多是数据延迟不会产生冲突。多主架构下Node1 和 Node2 可以同时接受写入各自生成 PartZooKeeper 需要在两个副本之间仲裁谁的 Part 是对的。如果两个副本之间的 ZK session 出现频繁超时比如网络抖动ZK 会反复触发 Leader 选举期间所有副本的元数据操作全部阻塞——这就是为什么多主架构下 ZK 的性能直接决定了整个集群的可用性上限。二、延迟排查四步走从现象到根因第一步确认延迟有多大-- 查看各副本的同步延迟情况 -- system.replication_queue 是排查延迟的第一入口 SELECT database, table, -- 最老的待同步任务创建时间如果超过 5 分钟就要关注 min(create_time) as oldest_task, -- 待同步任务总数持续增长说明跟不上 count() as pending_tasks, -- 累计重试次数频繁重试说明有问题 sum(num_tries) as total_retries FROM system.replication_queue GROUP BY database, table ORDER BY pending_tasks DESC;第二步看是源头慢还是目的地慢-- 在 Leader 节点上检查写入是否有积压 -- system.metrics 是实时指标和 Prometheus 的数据一致 SELECT metric, value, description FROM system.metrics WHERE metric IN ( Merge, -- 合并操作数太多说明后台 merge 压力大 PartMutation, -- 变更操作ALTER DELETE/UPDATE 会触发 DelayedInserts, -- 延迟写入数写不进去才会延迟 ReplicatedFetch -- 副本拉取数多少 part 在等待同步 ); -- 在 Follower 节点上查同步状态 -- system.replicas 告诉你某个副本差了多少 SELECT database, table, is_readonly, -- 副本是否降级为只读 absolute_delay, -- 延迟的秒数 queue_size, -- 待同步的 part 数 -- 判断卡在哪个阶段的关键指标 inserts_in_queue, -- 写入队列积压 merges_in_queue -- 合并队列积压 FROM system.replicas WHERE absolute_delay 60 -- 只关注延迟超过 1 分钟的 ORDER BY absolute_delay DESC;第三步检查 ZooKeeper 状态很多时候延迟的根源不在 ClickHouse 本身而在 ZooKeeper-- ClickHouse 内置的 ZK 健康检查 -- 为什么单独查这个ZK 卡顿时所有副本都会懵表现为全局延迟 SELECT name, value FROM system.events WHERE name LIKE %ZooKeeper% AND name NOT LIKE %Request% -- 排除请求计数只看异常指标 ORDER BY value DESC;第四步看系统层面的瓶颈# 在 ClickHouse 节点上直接看 IO 状况 # iostat 是排查磁盘瓶颈的最快方式 iostat -x 2 5 # 关注指标 # awaitIO 等待时间超过 20ms 说明磁盘慢 # %utilIO 利用率接近 100% 就是磁盘瓶颈 # r/s, w/s读写 IOPS和磁盘规格对比-- ClickHouse 自身也暴露了 IO 指标 SELECT event, value, -- 除以运行时间得到每秒速率 value / uptime() as per_second FROM system.events WHERE event IN ( ReadBufferFromFileDescriptorReadBytes, WriteBufferFromFileDescriptorWriteBytes, DiskReadElapsedMicroseconds, DiskWriteElapsedMicroseconds );三、止损三板斧从紧急止血到长期修复当你确认延迟确实存在且正在恶化按优先级执行第一板斧暂停非必要写入止血-- 临时把 Follower 设成只读防止延迟进一步扩大 -- 适用场景延迟已经超过 10 分钟且还在拉大 -- 风险读请求会全部打到其他副本 SYSTEM STOP FETCHES; -- 停止从其他副本拉数据先消化积压 SYSTEM STOP MERGES; -- 暂停后台 merge释放 IO 给同步用等一下——这里可能有人会困惑STOP FETCHES不是会停止同步吗怎么还要用其实逻辑是这样的当延迟已经大到追不上的地步Follower 一边下载新的 part 一边还要做 merge磁盘 IO 完全饱和。先停止拉新数据和 merge让 Follower 集中所有 IO 消化已有的积压消化完了再恢复。为什么先停 FETCHES 再停 MERGES 有讲究如果你只停 FETCHES 不停 MERGESClickHouse 后台会继续 merge 那些刚同步过来的小 part磁盘 IO 还是忙。如果你只停 MERGES 不停 FETCHES新的 part 还在源源不断拉进来后台 merge 队列只会越积越多。两个一起停Follower 的磁盘 IO 全部释放出来消化积压速度是最快的。恢复的时候要反过来先 START MERGES 把积压的 merge 清掉再 START FETCHES 恢复同步——这个顺序搞反了刚恢复 FETCHES 拉进来一堆新 partmerge 队列又爆炸了。-- 积压消化完后恢复 SYSTEM START FETCHES; SYSTEM START MERGES;第二板斧调整同步并行度加速-- 增大并发取数线程 -- 默认只有 1 个 fetch 线程大 part 传输时其他 part 全排队 -- 为什么是 8经验值再多会导致网络和磁盘竞争反而变慢 ALTER TABLE db.order_events MODIFY SETTING max_fetch_partition_retries_count 8; -- 降低后台 merge 的并发把 IO 让给同步 -- 默认 16数据量大时 merge 会抢占同步的 IO 带宽 ALTER TABLE db.order_events MODIFY SETTING background_pool_size 4;第三板斧跳过问题分区兜底-- 如果某个分区怎么都同步不过去part 损坏、不兼容先跳过 -- 风险警告跳过会导致该分区数据不一致只能作为最后手段 SELECT database, table, partition_id, part_name, num_tries, last_exception FROM system.replication_queue WHERE num_tries 10 -- 重试超过 10 次的大概率真有毛病 ORDER BY num_tries DESC; -- 确实要跳过的话 -- SYSTEM SYNC REPLICA db.table STRICT; -- 强制同步但不跳过 -- SYSTEM RESTART REPLICA; -- 重启副本进程四、从根上解决架构层面的优化策略防止延迟复发需要在架构上做三件事策略一ClickHouse Keeper 替代 ZooKeeper!-- config.xml -- !-- 为什么换 KeeperZooKeeper 是 Java 进程内存模型和 ClickHouse 完全不搭 大写入量时 ZK 的 GC 停顿会导致所有副本的元数据操作超时 -- clickhouse keeper_server tcp_port9181/tcp_port server_id1/server_id !-- Keeper 使用 ClickHouse 自己的存储引擎吞吐是 ZK 的 3~5 倍 -- log_storage_path/data/clickhouse/keeper/coordination/log/log_storage_path snapshot_storage_path/data/clickhouse/keeper/coordination/snapshots/snapshot_storage_path coordination_settings operation_timeout_ms10000/operation_timeout_ms session_timeout_ms30000/session_timeout_ms /coordination_settings /keeper_server /clickhouse为什么 ClickHouse Keeper 比 ZooKeeper 快 3~5 倍不是因为 Keeper 的算法更先进——两个都是 Raft 协议实现。差距来自语言和存储。ZooKeeper 用 Java 文件系统 snapshot每次 snapshot 都要走 JVM 序列化一个 200MB 的 snapshot 可能触发 Full GC 停顿数秒。ClickHouse Keeper 用 C 写存储直接复用 MergeTree 引擎的 WAL 压缩机制snapshot 是增量写入的没有 Java GC 的不可预测停顿。在每天百万级 part 写入的场景下ZK 的 GC 停顿是延迟的主要来源——换 Keeper 就等于把这个来源直接消除。策略二写入侧限流-- 控制单次插入的 part 数量 -- 为什么要限制频繁小批写入会产生大量小 part同步开销远超数据本身 SET max_partitions_per_insert_block 100; SET min_insert_block_size_rows 100000; -- 至少 10 万行才落盘 SET min_insert_block_size_bytes 10000000; -- 或至少 10MB策略三分层存储 异步同步graph LR A[实时写入] -- B[Hot Tier: SSDbr/最近7天数据] B -- C[Cold Tier: HDDbr/7天后自动迁移] D[Replica 1br/SSD] -.-|实时同步| E[Replica 2br/SSD] F[Replica 1br/HDD] -.-|异步同步br/允许延迟| G[Replica 2br/HDD] style B fill:#afa,stroke:#333 style D fill:#afa,stroke:#333 style E fill:#afa,stroke:#333把热数据最近 7 天放在 SSD 上做实时同步冷数据放在 HDD 上做异步同步——毕竟没人会盯着去年的数据要实时对比。 踩坑提醒SYSTEM STOP MERGES停了之后记得恢复否则磁盘会爆炸。Merge 停了后台不再合并小 part每个 INSERT 都会生成新 part。一天下来可能积压几千个小 part查询时要扫描的文件数从几十变成几千性能直接腰斩。建议用定时脚本监控system.merges的状态如果发现 merge 被停了超过 30 分钟自动告警。ZooKeeper 的snapCount配置是隐性炸弹。默认 100000 次事务打一次 snapshot在高频写入场景比如 QPS 1 万一小时就触发一次 snapshot。ZooKeeper 打 snapshot 的时候内存翻倍 CPU 飙升所有连接超时。解决办法是调大snapCount到 500000同时配合定时任务在低峰期手动触发 snapshot。max_fetch_partition_retries_count不是越大越好。你设成 16 个 fetch 线程看起来拉得快但 16 个线程同时往磁盘写数据磁盘的 IOPS 是固定的比如 SSD 10000 IOPS平均每个线程只能分到 625 IOPS。8 个线程时每个能分 1250 IOPS总吞吐反而更高。一个简单的公式最优 fetch 线程数 磁盘顺序写吞吐(MB/s) / 单个 Channel 网络带宽(MB/s)上限不超过 CPU 核心数。五、总结ClickHouse 副本延迟的本质是生产速度和消费速度的不匹配。排查时从四个维度入手系统表看延迟量、ZK 看协调层、磁盘看 IO 层、系统监控看资源层。止损的核心思路是先止血暂停写入、再加速调高并发、最后兜底跳过异常分区。长期来看三件事能从根本上解决延迟问题ClickHouse Keeper 换掉 ZooKeeper——性能提升最明显写入侧做好限流和批量控制——从源头减少小 part冷热分层存储——把有限的 SSD IO 留给真正需要实时同步的数据。下篇我们换个话题聊聊怎么把训练好的 AI 模型封装成数据分析师能调用的 API不见不散