集群是副本和分片的基础。副本防止数据丢失(冗余存储),分片实现数据水平切分。
1.1 概述
ClickHouse 集群配置非常灵活:
单集群或多集群
每个集群的节点、分区和副本数量可以各不相同
副本 = 数据完全相同的多个节点
分片 = 数据完全不同的多个节点
1.2 数据副本
使用 ReplicatedMergeTree 系列表引擎实现副本能力。
1.2.1 副本的特点
| 特点 | 说明 |
|---|---|
| 依赖 ZooKeeper | INSERT 和 ALTER 通过 ZK 协同 |
| 表级别的副本 | 每张表独立定义副本 |
| 多主架构 | 任意副本可执行 INSERT/ALTER |
| Block 数据块 | 数据写入基本单元,默认 max_insert_block_size 行 |
| 原子性 | 一个 Block 要么全部成功,要么全部失败 |
| 唯一性 | 通过 Hash 摘要防止重复写入 |
1.2.2 ZooKeeper 的配置方式
在 /etc/clickhouse-server/config.d/metrika.xml 中配置:
<yandex>
<zookeeper-servers>
<node index="1">
<host>hdp1.nauu.com</host>
<port>2181</port>
</node>
</zookeeper-servers>
</yandex>在 config.xml 中引入:
<include_from>/etc/clickhouse-server/config.d/metrika.xml</include_from>
<zookeeper incl="zookeeper-servers" optional="false"/>通过 system.zookeeper 代理表查询 ZooKeeper:
SELECT * FROM system.zookeeper WHERE path = '/';
SELECT * FROM system.zookeeper WHERE path = '/clickhouse';1.2.3 副本的定义形式
ENGINE = ReplicatedMergeTree('zk_path', 'replica_name')命名约定:
/clickhouse/tables/{shard}/table_name/clickhouse/tables/:固定前缀{shard}:分片编号table_name:表名
replica_name: 每个副本唯一,通常使用服务器域名。
-- 1 分片 1 副本
ReplicatedMergeTree('/clickhouse/tables/01/test_1', 'ch5.nauu.com')
ReplicatedMergeTree('/clickhouse/tables/01/test_1', 'ch6.nauu.com')
-- 多分片 1 副本
ReplicatedMergeTree('/clickhouse/tables/01/test_1', 'ch5.nauu.com')
ReplicatedMergeTree('/clickhouse/tables/01/test_1', 'ch6.nauu.com')
ReplicatedMergeTree('/clickhouse/tables/02/test_1', 'ch7.nauu.com')
ReplicatedMergeTree('/clickhouse/tables/02/test_1', 'ch8.nauu.com')1.3 ReplicatedMergeTree 原理解析
1.3.1 ZooKeeper 数据结构
/clickhouse/tables/{shard}/{table}/ ├── metadata -- 主键、分区键、采样表达式 ├── columns -- 列字段信息 ├── replicas -- 副本名称列表 ├── leader_election -- 主副本选举 ├── blocks -- Block 的 Hash 摘要(去重) ├── block_numbers -- 按写入顺序记录 ├── quorum -- 写入确认数量(insert_quorum) ├── log -- 操作日志(持久顺序型) │ ├── log-0000000000 │ └── log-0000000001 ├── mutations -- MUTATION 操作日志 │ └── 0000000000 └── replicas/{replica_name}/ ├── queue -- 任务队列 ├── log_pointer -- 日志指针 └── mutation_pointer -- MUTATION 指针
LogEntry 核心属性
| 属性 | 说明 |
|---|---|
| source_replica | 发送指令的副本来源 |
| type | get(下载分区)/ merge(合并)/ mutate(修改) |
| block_id | 当前分区的 BlockID |
| partition_name | 分区目录名称 |
MutationEntry 核心属性
| 属性 | 说明 |
|---|---|
| source_replica | 发送指令的副本来源 |
| commands | ALTER DELETE / ALTER UPDATE |
| mutation_id | 版本号 |
| partition_id | 分区目录 ID |
1.3.2 副本协同的核心流程
INSERT 流程
创建副本实例: 初始化 ZK 节点 → 注册副本 → 参与选举
创建其他副本实例: 同样注册和选举
写入数据: 本地写入分区 → 记录 block_id → 去重检查
推送 Log 日志: 执行 INSERT 的副本向
/log推送(type=get)拉取 Log 日志: 其他副本监听 → 更新 log_pointer → 入队
下载数据: 选择最优远端副本(log_pointer 最大 + queue 最少)
响应下载: 通过 DataPartsExchange 服务发送分区数据
本地写入: 写入临时目录 → 重命名完成
原则: ZooKeeper 不传输表数据,只分发指令。数据通过 HTTP 点对点传输。
MERGE 流程
非主副本远程连接到主副本
主副本接收连接
主副本制定合并计划 → 推送 Log(type=merge)
各副本拉取 Log 到队列
各副本在本地执行 MERGE
合并由主副本统一制定计划,各副本本地执行。
MUTATION 流程
执行 ALTER DELETE/UPDATE → 创建 MutationEntry 到 ZK
/mutations所有副本监听 mutations 节点
只有主副本响应 → 转换为 LogEntry 推送到
/log(type=mutate)各副本拉取 Log 到队列
各副本在本地执行 MUTATION
ALTER 流程
修改 ZK 共享元数据(/metadata, /columns)→ 版本号提升
各副本监听变更 → 本地执行更新
确认所有副本完成
ALTER 不涉及
/log分发,直接修改共享元数据。
1.4 数据分片
数据分片需要结合 Distributed 表引擎使用。
1.4.1 集群的配置方式
形式一:不包含副本的分片(使用 node 标签)
<clickhouse_remote_servers>
<shard_2>
<node>
<host>ch5.nauu.com</host>
<port>9000</port>
</node>
<node>
<host>ch6.nauu.com</host>
<port>9000</port>
</node>
</shard_2>
</clickhouse_remote_servers>形式二:自定义分片与副本(使用 shard/replica 标签)
<!-- 2 分片 0 副本 -->
<sharding_simple>
<shard>
<replica><host>ch5.nauu.com</host><port>9000</port></replica>
</shard>
<shard>
<replica><host>ch6.nauu.com</host><port>9000</port></replica>
</shard>
</sharding_simple>
<!-- 1 分片 1 副本 -->
<sharding_simple_1>
<shard>
<replica><host>ch5.nauu.com</host><port>9000</port></replica>
<replica><host>ch6.nauu.com</host><port>9000</port></replica>
</shard>
</sharding_simple_1>
<!-- 2 分片 1 副本(高可用) -->
<sharding_ha>
<shard>
<replica><host>ch5.nauu.com</host><port>9000</port></replica>
<replica><host>ch6.nauu.com</host><port>9000</port></replica>
</shard>
<shard>
<replica><host>ch7.nauu.com</host><port>9000</port></replica>
<replica><host>ch8.nauu.com</host><port>9000</port></replica>
</shard>
</sharding_ha>验证集群配置:
SELECT cluster, host_name FROM system.clusters;1.4.2 基于集群实现分布式 DDL
CREATE TABLE test_1_local ON CLUSTER shard_2 (
id UInt64
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test_1', '{replica}')
ORDER BY id;
DROP TABLE test_1_local ON CLUSTER shard_2;宏变量: 在 config.xml 中为每个节点定义:
<macros>
<shard>01</shard>
<replica>ch5.nauu.com</replica>
</macros>分布式 DDL 在 ZK 的路径:/clickhouse/task_queue/ddl
DDLLogEntry 核心属性
| 属性 | 说明 |
|---|---|
| query | DDL 执行语句 |
| hosts | 指定集群的主机列表 |
| initiator | 发起主机名称 |
1.5 Distributed 原理解析
1.5.1 定义形式
ENGINE = Distributed(cluster, database, table [, sharding_key])完整示例:
-- 分布式表
CREATE TABLE test_shard_2_all ON CLUSTER sharding_simple (
id UInt64
) ENGINE = Distributed(sharding_simple, default, test_shard_2_local, rand());
-- 本地表
CREATE TABLE test_shard_2_local ON CLUSTER sharding_simple (
id UInt64
) ENGINE = MergeTree()
ORDER BY id
PARTITION BY id;1.5.2 查询分类
| 类型 | 操作 |
|---|---|
| 作用于本地表 | INSERT、SELECT |
| 只影响 Distributed | CREATE、DROP、RENAME、ALTER(不含分区操作) |
| 不支持 | ALTER DELETE、ALTER UPDATE |
1.5.3 分片规则
分片键计算:
slot = shard_value % sum_weightshard_value:分片键取值sum_weight:所有分片权重之和slot 按权重区间映射到对应分片
-- 按用户 ID 的余数
Distributed(cluster, database, table, userid)
-- 按随机数
Distributed(cluster, database, table, rand())
-- 按散列值
Distributed(cluster, database, table, intHash64(userid))1.5.4 分布式写入流程
接收 INSERT → 按分片规则划分数据
本地分片数据直接写入本地表
远端分片数据写入临时 bin 文件(
/database@host:port/[num].bin)DirectoryMonitor 线程异步发送远端数据(传输前压缩)
远端节点接收并写入本地表
确认完成
同步/异步: insert_distributed_sync 参数控制(默认 false=异步)
副本数据复制的两种方式
| 方式 | 说明 |
|---|---|
| Distributed 复制 | Distributed 直接写入每个 replica(可能成瓶颈) |
| ReplicatedMergeTree 复制 | internal_replication=true,Distributed 只写一个 replica |
<shard>
<internal_replication>true</internal_replication>
<replica>...</replica>
<replica>...</replica>
</shard>1.5.5 分布式查询流程
多副本路由规则(load_balancing)
| 算法 | 说明 |
|---|---|
| random(默认) | 选 errors_count 最少的,相同则随机 |
| nearest_hostname | 优先选 host 名称最相似的 |
| in_order | 按配置顺序选择 |
| first_or_random | 选第一个,不可用时随机 |
多分片查询
接收 SELECT → 转换为各分片的子查询
并行查询本地(One)和远端分片(Remote)
汇总各分片结果(Aggregator)
GLOBAL 优化分布式子查询
问题:不使用 GLOBAL 时查询放大 N² 倍
-- ❌ 查询放大 N² 倍(N = 分片数量)
SELECT uniq(id) FROM test_query_all WHERE repo = 100
AND id IN (SELECT id FROM test_query_all WHERE repo = 200);
-- ✅ 使用 GLOBAL 优化
SELECT uniq(id) FROM test_query_all WHERE repo = 100
AND id GLOBAL IN (SELECT id FROM test_query_all WHERE repo = 200);GLOBAL IN 流程:
将 IN 子句单独提出,发起一次分布式查询
汇总子句结果 → 放入临时 Memory 表
将 Memory 表分发到远端分片节点
各分片使用本地临时数据执行完整 SQL
汇总最终结果
⚠️ GLOBAL 在网络间分发数据,IN/JOIN 子句返回数据不宜过大。可使用 DISTINCT 去重。
1.6 小结
| 概念 | 作用 | 实现 |
|---|---|---|
| 副本 | 防止数据丢失,读写分摊 | ReplicatedMergeTree + ZooKeeper |
| 分片 | 水平切分,突破单表容量 | Distributed + 集群配置 |
| 集群 | 逻辑拓扑定义 |
0 条评论