Clickhouse集群实践指南
当前测试环境:
Ubuntu 24.04ClickHouse server version 25.11.2.24 (official build)
1. 写在前面:这篇文章适合谁?
- ✔ 第一次接触 ClickHouse 集群
- ✔ 想从一开始就用“正确姿势”
- ✔ 不想以后因为早期决策返工
- ✔ 需要一份 可长期复用的模板
不适合:
- 只想临时跑个 demo
- 不关心扩容 / 高可用 / 运维
2. 官方安装指南
3. ClickHouse 集群的整体认知
在 ClickHouse 中,“集群”不是一个开关,而是一整套能力的组合:
flowchart TB
%% ================= 客户端 =================
Client["客户端
INSERT / SELECT"]
style Client fill:#4A90E2,color:#fff,stroke:#2C5AA0,stroke-width:2px
%% ================= Distributed 表 =================
DT["Distributed 表
统一访问入口 / 路由层"]
style DT fill:#9B59B6,color:#fff,stroke:#6C3483,stroke-width:2px
%% 客户端请求
Client -->|写入请求 INSERT
查询请求 SELECT| DT
%% ================= 分片层 =================
subgraph SHARDS["ClickHouse 分片层"]
direction LR
style SHARDS fill:#F4F6F7,stroke:#95A5A6,stroke-width:2px
subgraph S1["Shard 1"]
R11["副本 1-1
ReplicatedMergeTree"]
R12["副本 1-2
ReplicatedMergeTree"]
end
subgraph S2["Shard 2"]
R21["副本 2-1
ReplicatedMergeTree"]
R22["副本 2-2
ReplicatedMergeTree"]
end
subgraph S3["Shard 3"]
R31["副本 3-1
ReplicatedMergeTree"]
R32["副本 3-2
ReplicatedMergeTree"]
end
end
%% 副本样式
style R11 fill:#2ECC71,color:#fff,stroke:#1E8449
style R12 fill:#2ECC71,color:#fff,stroke:#1E8449
style R21 fill:#2ECC71,color:#fff,stroke:#1E8449
style R22 fill:#2ECC71,color:#fff,stroke:#1E8449
style R31 fill:#2ECC71,color:#fff,stroke:#1E8449
style R32 fill:#2ECC71,color:#fff,stroke:#1E8449
%% 写入路由
DT -->|按分片键路由写入数据| R11
DT -->|按分片键路由写入数据| R21
DT -->|按分片键路由写入数据| R31
%% 副本复制
R11 -->|异步复制(通过 Keeper 拉取增量 parts)| R12
R21 -->|异步复制(通过 Keeper 拉取增量 parts)| R22
R31 -->|异步复制(通过 Keeper 拉取增量 parts)| R32
%% ================= Keeper =================
subgraph KEEPERS["ClickHouse Keeper 集群"]
direction LR
K1["Keeper 节点 1
元数据 / 复制日志"]
K2["Keeper 节点 2
元数据 / 复制日志"]
end
style K1 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
style K2 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
%% Keeper 协调
R11 <-->|注册副本 / 拉取复制日志| K1
R12 <-->|注册副本 / 拉取复制日志| K1
R21 <-->|注册副本 / 拉取复制日志| K2
R22 <-->|注册副本 / 拉取复制日志| K2
R31 <-->|注册副本 / 拉取复制日志| K1
R32 <-->|注册副本 / 拉取复制日志| K2
%% 查询返回
R11 -.->|返回本 Shard 查询结果| DT
R21 -.->|返回本 Shard 查询结果| DT
R31 -.->|返回本 Shard 查询结果| DT
DT -.->|汇总所有 Shard 结果并返回| Client
%% ================= 线路颜色 =================
%% 写入链路(红色)
linkStyle 0 stroke:#E74C3C,stroke-width:2px
linkStyle 1 stroke:#E74C3C,stroke-width:2px
linkStyle 2 stroke:#E74C3C,stroke-width:2px
linkStyle 3 stroke:#E74C3C,stroke-width:2px
%% 副本复制(绿色)
linkStyle 4 stroke:#27AE60,stroke-width:2px
linkStyle 5 stroke:#27AE60,stroke-width:2px
linkStyle 6 stroke:#27AE60,stroke-width:2px
%% Keeper 协调(橙色)
linkStyle 7 stroke:#F39C12,stroke-width:2px
linkStyle 8 stroke:#F39C12,stroke-width:2px
linkStyle 9 stroke:#F39C12,stroke-width:2px
linkStyle 10 stroke:#F39C12,stroke-width:2px
linkStyle 11 stroke:#F39C12,stroke-width:2px
linkStyle 12 stroke:#F39C12,stroke-width:2px
%% 查询链路(蓝色虚线)
linkStyle 13 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 14 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 15 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 16 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5flowchart TB
%% ================= 客户端 =================
Client["客户端
INSERT / SELECT"]
style Client fill:#4A90E2,color:#fff,stroke:#2C5AA0,stroke-width:2px
%% ================= Distributed 表 =================
DT["Distributed 表
统一访问入口 / 路由层"]
style DT fill:#9B59B6,color:#fff,stroke:#6C3483,stroke-width:2px
%% 客户端请求
Client -->|写入请求 INSERT
查询请求 SELECT| DT
%% ================= 分片层 =================
subgraph SHARDS["ClickHouse 分片层"]
direction LR
style SHARDS fill:#F4F6F7,stroke:#95A5A6,stroke-width:2px
subgraph S1["Shard 1"]
R11["副本 1-1
ReplicatedMergeTree"]
R12["副本 1-2
ReplicatedMergeTree"]
end
subgraph S2["Shard 2"]
R21["副本 2-1
ReplicatedMergeTree"]
R22["副本 2-2
ReplicatedMergeTree"]
end
subgraph S3["Shard 3"]
R31["副本 3-1
ReplicatedMergeTree"]
R32["副本 3-2
ReplicatedMergeTree"]
end
end
%% 副本样式
style R11 fill:#2ECC71,color:#fff,stroke:#1E8449
style R12 fill:#2ECC71,color:#fff,stroke:#1E8449
style R21 fill:#2ECC71,color:#fff,stroke:#1E8449
style R22 fill:#2ECC71,color:#fff,stroke:#1E8449
style R31 fill:#2ECC71,color:#fff,stroke:#1E8449
style R32 fill:#2ECC71,color:#fff,stroke:#1E8449
%% 写入路由
DT -->|按分片键路由写入数据| R11
DT -->|按分片键路由写入数据| R21
DT -->|按分片键路由写入数据| R31
%% 副本复制
R11 -->|异步复制(通过 Keeper 拉取增量 parts)| R12
R21 -->|异步复制(通过 Keeper 拉取增量 parts)| R22
R31 -->|异步复制(通过 Keeper 拉取增量 parts)| R32
%% ================= Keeper =================
subgraph KEEPERS["ClickHouse Keeper 集群"]
direction LR
K1["Keeper 节点 1
元数据 / 复制日志"]
K2["Keeper 节点 2
元数据 / 复制日志"]
end
style K1 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
style K2 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
%% Keeper 协调
R11 <-->|注册副本 / 拉取复制日志| K1
R12 <-->|注册副本 / 拉取复制日志| K1
R21 <-->|注册副本 / 拉取复制日志| K2
R22 <-->|注册副本 / 拉取复制日志| K2
R31 <-->|注册副本 / 拉取复制日志| K1
R32 <-->|注册副本 / 拉取复制日志| K2
%% 查询返回
R11 -.->|返回本 Shard 查询结果| DT
R21 -.->|返回本 Shard 查询结果| DT
R31 -.->|返回本 Shard 查询结果| DT
DT -.->|汇总所有 Shard 结果并返回| Client
%% ================= 线路颜色 =================
%% 写入链路(红色)
linkStyle 0 stroke:#E74C3C,stroke-width:2px
linkStyle 1 stroke:#E74C3C,stroke-width:2px
linkStyle 2 stroke:#E74C3C,stroke-width:2px
linkStyle 3 stroke:#E74C3C,stroke-width:2px
%% 副本复制(绿色)
linkStyle 4 stroke:#27AE60,stroke-width:2px
linkStyle 5 stroke:#27AE60,stroke-width:2px
linkStyle 6 stroke:#27AE60,stroke-width:2px
%% Keeper 协调(橙色)
linkStyle 7 stroke:#F39C12,stroke-width:2px
linkStyle 8 stroke:#F39C12,stroke-width:2px
linkStyle 9 stroke:#F39C12,stroke-width:2px
linkStyle 10 stroke:#F39C12,stroke-width:2px
linkStyle 11 stroke:#F39C12,stroke-width:2px
linkStyle 12 stroke:#F39C12,stroke-width:2px
%% 查询链路(蓝色虚线)
linkStyle 13 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 14 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 15 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 16 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5flowchart TB
%% ================= 客户端 =================
Client["客户端
INSERT / SELECT"]
style Client fill:#4A90E2,color:#fff,stroke:#2C5AA0,stroke-width:2px
%% ================= Distributed 表 =================
DT["Distributed 表
统一访问入口 / 路由层"]
style DT fill:#9B59B6,color:#fff,stroke:#6C3483,stroke-width:2px
%% 客户端请求
Client -->|写入请求 INSERT
查询请求 SELECT| DT
%% ================= 分片层 =================
subgraph SHARDS["ClickHouse 分片层"]
direction LR
style SHARDS fill:#F4F6F7,stroke:#95A5A6,stroke-width:2px
subgraph S1["Shard 1"]
R11["副本 1-1
ReplicatedMergeTree"]
R12["副本 1-2
ReplicatedMergeTree"]
end
subgraph S2["Shard 2"]
R21["副本 2-1
ReplicatedMergeTree"]
R22["副本 2-2
ReplicatedMergeTree"]
end
subgraph S3["Shard 3"]
R31["副本 3-1
ReplicatedMergeTree"]
R32["副本 3-2
ReplicatedMergeTree"]
end
end
%% 副本样式
style R11 fill:#2ECC71,color:#fff,stroke:#1E8449
style R12 fill:#2ECC71,color:#fff,stroke:#1E8449
style R21 fill:#2ECC71,color:#fff,stroke:#1E8449
style R22 fill:#2ECC71,color:#fff,stroke:#1E8449
style R31 fill:#2ECC71,color:#fff,stroke:#1E8449
style R32 fill:#2ECC71,color:#fff,stroke:#1E8449
%% 写入路由
DT -->|按分片键路由写入数据| R11
DT -->|按分片键路由写入数据| R21
DT -->|按分片键路由写入数据| R31
%% 副本复制
R11 -->|异步复制(通过 Keeper 拉取增量 parts)| R12
R21 -->|异步复制(通过 Keeper 拉取增量 parts)| R22
R31 -->|异步复制(通过 Keeper 拉取增量 parts)| R32
%% ================= Keeper =================
subgraph KEEPERS["ClickHouse Keeper 集群"]
direction LR
K1["Keeper 节点 1
元数据 / 复制日志"]
K2["Keeper 节点 2
元数据 / 复制日志"]
end
style K1 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
style K2 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
%% Keeper 协调
R11 <-->|注册副本 / 拉取复制日志| K1
R12 <-->|注册副本 / 拉取复制日志| K1
R21 <-->|注册副本 / 拉取复制日志| K2
R22 <-->|注册副本 / 拉取复制日志| K2
R31 <-->|注册副本 / 拉取复制日志| K1
R32 <-->|注册副本 / 拉取复制日志| K2
%% 查询返回
R11 -.->|返回本 Shard 查询结果| DT
R21 -.->|返回本 Shard 查询结果| DT
R31 -.->|返回本 Shard 查询结果| DT
DT -.->|汇总所有 Shard 结果并返回| Client
%% ================= 线路颜色 =================
%% 写入链路(红色)
linkStyle 0 stroke:#E74C3C,stroke-width:2px
linkStyle 1 stroke:#E74C3C,stroke-width:2px
linkStyle 2 stroke:#E74C3C,stroke-width:2px
linkStyle 3 stroke:#E74C3C,stroke-width:2px
%% 副本复制(绿色)
linkStyle 4 stroke:#27AE60,stroke-width:2px
linkStyle 5 stroke:#27AE60,stroke-width:2px
linkStyle 6 stroke:#27AE60,stroke-width:2px
%% Keeper 协调(橙色)
linkStyle 7 stroke:#F39C12,stroke-width:2px
linkStyle 8 stroke:#F39C12,stroke-width:2px
linkStyle 9 stroke:#F39C12,stroke-width:2px
linkStyle 10 stroke:#F39C12,stroke-width:2px
linkStyle 11 stroke:#F39C12,stroke-width:2px
linkStyle 12 stroke:#F39C12,stroke-width:2px
%% 查询链路(蓝色虚线)
linkStyle 13 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 14 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 15 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 16 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5flowchart TB
%% ================= 客户端 =================
Client["客户端
INSERT / SELECT"]
style Client fill:#4A90E2,color:#fff,stroke:#2C5AA0,stroke-width:2px
%% ================= Distributed 表 =================
DT["Distributed 表
统一访问入口 / 路由层"]
style DT fill:#9B59B6,color:#fff,stroke:#6C3483,stroke-width:2px
%% 客户端请求
Client -->|写入请求 INSERT
查询请求 SELECT| DT
%% ================= 分片层 =================
subgraph SHARDS["ClickHouse 分片层"]
direction LR
style SHARDS fill:#F4F6F7,stroke:#95A5A6,stroke-width:2px
subgraph S1["Shard 1"]
R11["副本 1-1
ReplicatedMergeTree"]
R12["副本 1-2
ReplicatedMergeTree"]
end
subgraph S2["Shard 2"]
R21["副本 2-1
ReplicatedMergeTree"]
R22["副本 2-2
ReplicatedMergeTree"]
end
subgraph S3["Shard 3"]
R31["副本 3-1
ReplicatedMergeTree"]
R32["副本 3-2
ReplicatedMergeTree"]
end
end
%% 副本样式
style R11 fill:#2ECC71,color:#fff,stroke:#1E8449
style R12 fill:#2ECC71,color:#fff,stroke:#1E8449
style R21 fill:#2ECC71,color:#fff,stroke:#1E8449
style R22 fill:#2ECC71,color:#fff,stroke:#1E8449
style R31 fill:#2ECC71,color:#fff,stroke:#1E8449
style R32 fill:#2ECC71,color:#fff,stroke:#1E8449
%% 写入路由
DT -->|按分片键路由写入数据| R11
DT -->|按分片键路由写入数据| R21
DT -->|按分片键路由写入数据| R31
%% 副本复制
R11 -->|异步复制(通过 Keeper 拉取增量 parts)| R12
R21 -->|异步复制(通过 Keeper 拉取增量 parts)| R22
R31 -->|异步复制(通过 Keeper 拉取增量 parts)| R32
%% ================= Keeper =================
subgraph KEEPERS["ClickHouse Keeper 集群"]
direction LR
K1["Keeper 节点 1
元数据 / 复制日志"]
K2["Keeper 节点 2
元数据 / 复制日志"]
end
style K1 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
style K2 fill:#F39C12,color:#fff,stroke:#B9770E,stroke-width:1px
%% Keeper 协调
R11 <-->|注册副本 / 拉取复制日志| K1
R12 <-->|注册副本 / 拉取复制日志| K1
R21 <-->|注册副本 / 拉取复制日志| K2
R22 <-->|注册副本 / 拉取复制日志| K2
R31 <-->|注册副本 / 拉取复制日志| K1
R32 <-->|注册副本 / 拉取复制日志| K2
%% 查询返回
R11 -.->|返回本 Shard 查询结果| DT
R21 -.->|返回本 Shard 查询结果| DT
R31 -.->|返回本 Shard 查询结果| DT
DT -.->|汇总所有 Shard 结果并返回| Client
%% ================= 线路颜色 =================
%% 写入链路(红色)
linkStyle 0 stroke:#E74C3C,stroke-width:2px
linkStyle 1 stroke:#E74C3C,stroke-width:2px
linkStyle 2 stroke:#E74C3C,stroke-width:2px
linkStyle 3 stroke:#E74C3C,stroke-width:2px
%% 副本复制(绿色)
linkStyle 4 stroke:#27AE60,stroke-width:2px
linkStyle 5 stroke:#27AE60,stroke-width:2px
linkStyle 6 stroke:#27AE60,stroke-width:2px
%% Keeper 协调(橙色)
linkStyle 7 stroke:#F39C12,stroke-width:2px
linkStyle 8 stroke:#F39C12,stroke-width:2px
linkStyle 9 stroke:#F39C12,stroke-width:2px
linkStyle 10 stroke:#F39C12,stroke-width:2px
linkStyle 11 stroke:#F39C12,stroke-width:2px
linkStyle 12 stroke:#F39C12,stroke-width:2px
%% 查询链路(蓝色虚线)
linkStyle 13 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 14 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 15 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5
linkStyle 16 stroke:#3498DB,stroke-width:2px,stroke-dasharray:5 5记住一句话:
ClickHouse的高可用 =数据可复制+元数据不丢+访问不中断
4. 为什么“单节点也要用集群模式初始化”
4.1. 常见误区
“现在只有一台机器,用单机模式更简单吧?”
这是 ClickHouse 中最容易留下技术债的决定。
4.2. 正确做法(推荐)
即使只有 1 台机器,也要:
- 配置
cluster.xml - 配置
keeper.xml - 配置
macros.xml - 配置
replicated_access.xml - 所有 DDL 使用
ON CLUSTER - 所有的表都使用
ReplicatedMergeTree
原因:
| 好处 | 解释 |
|---|---|
| 表结构不返工 | 以后加节点不用重建 |
| 用法统一 | 单机 / 集群 SQL 一样 |
| 符合 25.x 设计 | ON CLUSTER 强依赖 Keeper |
5. 集群配置:cluster.xml
| |
关键说明:
- cluster 名字非常重要 后续所有
ON CLUSTER xxx都依赖它 - shard ≠ replica
- shard:数据水平切分
- replica:同一数据的副本
6. Keeper:为什么必须有 ? & 怎么配 ?
6.1. Keeper 是干什么的?
Keeper 负责:
- 分布式 DDL(
ON CLUSTER) - 副本元数据
- 表锁
- 副本状态协调
没有 Keeper,集群会“看起来存在,但不能用”
6.2. keeper.xml(最小可用,单节点)
/etc/clickhouse-server/config.d/keeper.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43<clickhouse> <!-- Keeper 的配置,只要保持各个节点在 raft_configuration 和 zookeeper 中即可,无关顺序节点类型 --> <!-- Keeper 服务本身 --> <keeper_server> <!-- Keeper 对外端口 --> <tcp_port>9181</tcp_port> <!-- 每台机器唯一,不能重复, 需要与 raft_configuration - server - id 保持一致 --> <server_id>1</server_id> <coordination_settings> <operation_timeout_ms>10000</operation_timeout_ms> <session_timeout_ms>30000</session_timeout_ms> </coordination_settings> <!-- Raft 成员(单节点也要写) --> <raft_configuration> <!-- 多少个节点(成员), 多少个 server , 其中id与对应的 server_id 保持一致 --> <server> <id>1</id> <hostname>172.31.10.12</hostname> <!-- 9234 是 Keeper 节点之间 Raft 通信端口 --> <port>9234</port> </server> <!-- <server> <id>2</id> <hostname>172.31.10.12</hostname> <port>9234</port> </server> --> </raft_configuration> </keeper_server> <!-- ClickHouse Server 通过这里访问 Keeper --> <zookeeper> <!-- 同样多少个节点,多少个node --> <node> <host>172.31.10.12</host> <!-- 9181 是 ClickHouse Server 连接 Keeper 的端口 --> <port>9181</port> </node> <!-- <node> <host>172.31.10.12</host> <port>9181</port> </node> --> </zookeeper> </clickhouse>易错点
server_id每台必须不同- Keeper 的目录 不要和数据目录混在一起
- 即使单节点,也必须写
raft_configuration
7. 多磁盘 / 冷热分层(初始化就该做)
7.1. 为什么要一开始就配置?
- 冷热分层是 表级能力
- 表一旦建好,后面再补策略会很痛苦
- Disk 在 ClickHouse 中只是目录抽象
👉 所以:可以先“假装有多块盘”
7.2. storage.xml
/etc/clickhouse-server/config.d/storage.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66<clickhouse> <!-- ClickHouse 数据根目录 --> <!-- 所有表、元数据都在这里 --> <path>/data/_clickhouse/</path> <!-- sudo mkdir -p /data/_clickhouse/coordination/{log,snapshots} sudo chown -R clickhouse:clickhouse /data/_clickhouse --> <!-- 访问控制与权限存储路径 --> <!-- 此项设定实际上已过时(低版本兼容可用),交由 user_directories.local_directory 进行控制 --> <!-- <access_control_path>/data/_clickhouse/access/</access_control_path> --> <!-- 用户文件沙盒路径 --> <user_files_path>/data/_clickhouse/user_files/</user_files_path> <!-- 格式结构定义路径 --> <format_schema_path>/data/_clickhouse/format_schemas/</format_schema_path> <!-- Keeper 服务数据存储路径 --> <keeper_server> <!-- 存放 Keeper 的 Raft 事务日志 --> <log_storage_path>/data/_clickhouse/coordination/log</log_storage_path> <!-- 存放 Keeper 的 状态快照 --> <snapshot_storage_path>/data/_clickhouse/coordination/snapshots</snapshot_storage_path> </keeper_server> <!-- 多磁盘配置 --> <storage_configuration> <!-- 定义磁盘(逻辑) --> <disks> <!-- 热数据盘(模拟 SSD) --> <hot> <type>local</type> <path>/data/_clickhouse/hot/</path> </hot> <!-- 冷数据盘(模拟 HDD) --> <cold> <type>local</type> <path>/data/_clickhouse/cold/</path> </cold> </disks> <!-- 存储策略 --> <policies> <!-- 冷热分层策略名称 --> <hot_to_cold> <volumes> <!-- 新数据优先写这里 --> <hot_volume> <disk>hot</disk> </hot_volume> <!-- 冷数据移动到这里 --> <cold_volume> <disk>cold</disk> </cold_volume> </volumes> <!-- hot 使用率 > 80% 时允许 move --> <move_factor>0.2</move_factor> </hot_to_cold> </policies> </storage_configuration> </clickhouse>注意
- 所有节点的
disk/policy名称必须一致 - 后续换真实
SSD/HDD只改path
8. 标准建表模板(冷热分层 + 可扩展)
8.1. 单副本(入门 / 非 HA)
| |
8.2. 高可用版本(推荐,生产)
更多内容查看«集群架构设计与元数据管理»
| |
| |
- 为什么一定要 ReplicatedMergeTree?
- 扩容后自动补数据
- 节点挂掉不丢数据
- 支持滚动升级
注: 根据已搜集到的信息说明,单节点和高可用模式尽量不要混用,否则容易后期的数据同步异常(未测试)
9. Distributed 表(统一读写入口)
| |
使用原则:
- 写 →
test_table_all - 查 →
test_table_all - 不直接访问本地表
10. 表命名规范
10.1. 行业级数仓表命名总规范(核心)
- 统一命名模型(强烈推荐)
1<数据层>_<来源系统>_<业务域>_<业务实体>_<加工类型>_<时间粒度>
表名本身就是一份“元数据”
- 字段含义说明
组成 含义 示例 数据层 数仓分层 ods / dwd / dws / dim / ads 来源系统 数据来源系统 crm / erp / app / pay / log 业务域 一级业务域 trade / user / content 业务实体 核心业务对象 order / user / sku 加工类型 明细/汇总/拉链等 detail / snapshot / acc 时间粒度 数据粒度 day / hour / month / full
10.2. ClickHouse 表类型命名规范
ReplicatedMergeTree(本地表)
命名规则
1<数据层>_<来源系统>_<业务域>_<业务实体>_<加工类型>_<时间粒度>_local示例
场景 表名 订单明细事实表 dwd_erp_trade_order_detail_day_local用户行为日志 ods_app_log_user_action_detail_local商品维度表 dim_erp_product_sku_snapshot_full_local用户日汇总 dws_app_user_login_acc_day_local
Distributed(分布式表)
- 命名规则
1<数据层>_<来源系统>_<业务域>_<业务实体>_<加工类型>_<时间粒度>_(all|dist) - 示例
Distributed 表 对应 Local dwd_erp_trade_order_detail_day_alldwd_erp_trade_order_detail_day_localods_app_log_user_action_detail_allods_app_log_user_action_detail_localdim_erp_product_sku_snapshot_full_alldim_erp_product_sku_snapshot_full_local
_all代表全量数据(跨shard)_dist分布式路由
- 如何抉择
_all/_dist?数仓/业务/BI/数据服务应该只认_all。工程/运维/底层路由可以存在_dist, 但不应用于业务使用。
10.3. 数据层级命名(行业标准)
| 层级 | 含义 |
|---|---|
ods | 贴源数据(准实时/离线) |
dwd | 明细事实(业务语义清洗) |
dws | 轻度汇总(公共指标) |
dim | 维度表 |
ads | 应用层 / 报表层 |
- ClickHouse 通常以 dwd / dws / ads 为主
10.4. 来源系统命名规范
命名原则
- 使用 稳定系统标识
- 不使用部署名、环境名
- 多源不混用
示例
系统 命名 App 埋点 appERP erpCRM crm支付 pay日志平台 log第三方 third
10.5 业务域 & 业务实体规范
业务域(Domain)
1trade / user / content / finance / risk业务实体(Entity)
1order / refund / user / sku / coupon实体名必须是“名词”
10.5. 加工类型(行业强约定)
| 类型 | 含义 | 说明 |
|---|---|---|
detail | 明细事实 | 一行一业务事件 |
snapshot | 快照 | 当前状态 |
acc | 累计 | 期间累计 |
delta | 增量 | 变更数据 |
zipper | 拉链表 | 状态变更历史 |
10.7. 时间粒度命名
| 粒度 | 示例 |
|---|---|
| 明细 | full / detail |
| 小时 | hour |
| 天 | day |
| 月 | month |
10.8. ZooKeeper 路径命名规范(ClickHouse 特有)
| |
- 与 Distributed 表同名
- 不带
_local
10.9. 完整示例
ReplicatedMergeTree(本地表)1 2 3 4 5 6 7 8 9 10 11 12 13CREATE TABLE dbtest.dwd_erp_trade_order_detail_day_local ( order_id UInt64, user_id UInt64, order_amount Decimal(18,2), order_time DateTime ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dbtest/dwd_erp_trade_order_detail_day', '{replica}' ) PARTITION BY toYYYYMM(order_time) ORDER BY (order_id);Distributed 表1 2 3 4 5 6 7 8CREATE TABLE dbtest.dwd_erp_trade_order_detail_day_dist AS dwd_erp_trade_order_detail_day_local ENGINE = Distributed( ck_cluster, default, dwd_erp_trade_order_detail_day_local, cityHash64(order_id) );
10.10. 命名反例(行业强禁止)
order_detail(缺少层级、来源)ods_trade_order_v2(版本号禁止)user_order_local(语义不完整)trade_order_dist(表类型不进表名)prod_dwd_order(环境不进表名)
10.11. 推荐最终对照表
| 类型 | 表名 |
|---|---|
| 本地表 | dwd_erp_trade_order_detail_day_local |
| 分布式表 | dwd_erp_trade_order_detail_day |
| ZK Path | /clickhouse/tables/{shard}/<db_name>/<table_name> |
11. 备份与恢复(未测试)
11.1. 推荐工具:clickhouse-backup
部署位置:
👉 每个 ClickHouse 节点(或运维机)
11.2. 创建备份
包含:
- 表结构
- 数据(支持增量)
| |
11.3. 恢复示例(单表)
| |
11.4. 恢复整个数据库
| |
11.5. 其他
- 备份
/var/lib/clickhouse目录(最后防线,但可能不靠谱)
12. 升级和迁移(未测试)
- 要求:
- 副本 ≥ 2
- Keeper 存活
12.1. 正确姿势:滚动升级
| |
12.2. 升级前必做:
| |
12.3. 迁移(换机器 / 扩容)
正确方式:
- 新节点上线
- 加入
cluster.xml Replicated表自动同步- 移除旧节点
13. 高可用(未测试)
13.1. 自动重试 9000 端口
13.2. HAProxy
- 支持
TCP - 健康检查好
- 性能稳定
| |
13.3. 不推荐 Nginx(除非 HTTP)
Nginx:
- 更适合
8123 - 不适合高并发
TCP查询
14. 扩容与自动复制是如何发生的?
14.1. 如何新增节点?
所有需要调整的配置项,在配置文件模板中均有定义,可以自行参考阅读
主节点和新增节点的
cluster.xml、keeper.xml、macros.xml、storage.xml配置文件需要保持高度一致,以下是需要修改的地方cluster.xmlinterserver_http_host项, 值对应当前节点的IPshard项, 在下面添加一个replica副本节点(注: 若是分片就添加一个shard)
keeper.xml,keeper.xml配置文件对于无论对于哪一个节点来说,除了server_id不一样外,其他应完全一样server_id项, 各个节点保持唯一raft_configuration项, 在下面添加一个server节点, 值为对应节点的IP和server_idzookeeper项, 在下面添加一个node节点
macros.xmlreplica项,保持设置节点的唯一性
storage.xml- 此配置每个节点完全一致(仅测试时候,实际应用中可能由于服务器环境不同,需要根据实际情况修改)
复制主节点自定义配置文件到新节点的
/etc/clickhouse-server/config.d, 修改上述内容配置同步后,重启主节点, 然后重启新节点(这个重启顺序应该没什么关系,实际测试时候是先重启主节点、在重新的新节点)
登陆到新节点,等待同步完成,如果没有进行同步那么就需要手动创建与主节点相同的数据库、相同的表结构(注: 建表和建库的时候,需要保持与主节点的
ZK路径完全一致,否则难以自动同步数据,这也是为什么在见表的时候要求手动指定ZK路径的原因)
14.2. 如何自动复制?
- 当你有一个
ReplicatedMergeTree表时:- 每个副本在
ZooKeeper注册自己/clickhouse/tables/{shard}/dbtest/test_table
- 写入数据
- 客户端写到任意副本
- 副本会生成
mutation/insert log放入ZooKeeper队列
- 其他副本监听
ZooKeeper- 自动检测到新的 insert
- 拉取对应数据块(parts)并写入本地存储
- Merge 操作
- 每个副本独立进行 Merge
- 保持本地数据块高效存储
- 每个副本在
核心点:所有副本最终一致,但可以临时存在延迟(异步复制)
14.3. 节点扩容时发生什么?
- 新 shard / 新副本加入
- 新副本在 ZooKeeper 注册
- 系统会识别它缺少哪些数据块(parts)
- 旧副本会将已有数据块复制到新副本
- 新副本完成数据同步后,自动参与 Merge 和查询
注意:这个过程是增量拉取,不会重复写入全量数据。
14.4. 新 shard 加入(分片扩容)
- 分片扩容相对复杂
ClickHouse原生不支持自动重分片- 操作步骤:
- 创建新
shard的local表 - 根据分片
key手动迁移部分数据(或者使用INSERT SELECT) - 更新
Distributed表的shard配置
- 创建新
- 所以 shard 扩容通常是 人工触发的迁移 + 配置更新
- 副本扩容则是 自动的
总结:副本复制自动,分片迁移手动
15. 扩容节点时候如何选择是分片还是副本?
15.1. 场景 1:读压力过大(查询多、查询慢)
症状:
- 大量并发查询请求
- CPU 使用率高
- 查询排队等待
解决方案:增加副本(Replica)
1 2 3 4 5 6 7<!-- 1分片3副本 --> <shard> <internal_replication>true</internal_replication> <replica><host>node1</host></replica> <replica><host>node2</host></replica> <replica><host>node3</host></replica> <!-- 新增副本 --> </shard>效果:
- 读请求可以分散到多个副本
- 每个副本都有完整数据,可独立处理查询
- 提高查询吞吐量
- 不增加存储容量(每个副本存相同数据)
15.2. 场景 2:存储容量不足(数据量太大)
症状:
- 磁盘空间不足
- 单表数据量达到 TB 级
- 单节点存储瓶颈
解决方案:增加分片(Shard)
1 2 3 4 5 6 7 8 9 10 11<!-- 2分片,每分片2副本 = 4节点 --> <shard> <internal_replication>true</internal_replication> <replica><host>node1</host></replica> <replica><host>node2</host></replica> </shard> <shard> <internal_replication>true</internal_replication> <replica><host>node3</host></replica> <replica><host>node4</host></replica> </shard>效果:
- 数据分散存储,每个分片存一部分
- 存储容量翻倍(2分片 = 2倍容量)
- 写入压力分散
- 并行查询性能提升(多分片并行处理)
15.3. 场景 3:写入压力过大(插入速度慢)
症状:
- 大量数据写入
- 写入队列积压
- Merge 操作频繁
解决方案:增加分片(Shard)
- 原因:
- 副本之间需要数据同步,写入不会更快
- 分片可以并行写入,每个分片独立处理
- 原因:
15.4. 场景 4:既有读压力又有存储压力
- 解决方案:增加分片 + 每个分片有多个副本
1 2 3 4 5 6 7 8 9 10 11<!-- 2分片,每分片3副本 = 6节点 --> <shard> <replica><host>node1</host></replica> <replica><host>node2</host></replica> <replica><host>node3</host></replica> </shard> <shard> <replica><host>node4</host></replica> <replica><host>node5</host></replica> <replica><host>node6</host></replica> </shard>
15.5. 决策流程图
graph TD
A[单节点压力过大] --> B[判断压力类型]
B --> C[读压力
查询慢、并发高]
B --> D[存储/写入压力
容量不足、写入慢]
C --> E[增加副本
同分片多副本]
D --> F[增加分片
多分片多副本]
E --> G1[1分片2副本]
E --> G2[1分片3副本]
F --> H1[2分片2副本]
F --> H2[3分片2副本]
style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
style B fill:#4dabf7,stroke:#1971c2,stroke-width:2px,color:#fff
style C fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff
style D fill:#ffd43b,stroke:#f59f00,stroke-width:2px,color:#000
style E fill:#a9e34b,stroke:#5c940d,stroke-width:2px,color:#000
style F fill:#ff922b,stroke:#d9480f,stroke-width:2px,color:#fff
style G1 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style G2 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style H1 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000
style H2 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000graph TD
A[单节点压力过大] --> B[判断压力类型]
B --> C[读压力
查询慢、并发高]
B --> D[存储/写入压力
容量不足、写入慢]
C --> E[增加副本
同分片多副本]
D --> F[增加分片
多分片多副本]
E --> G1[1分片2副本]
E --> G2[1分片3副本]
F --> H1[2分片2副本]
F --> H2[3分片2副本]
style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
style B fill:#4dabf7,stroke:#1971c2,stroke-width:2px,color:#fff
style C fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff
style D fill:#ffd43b,stroke:#f59f00,stroke-width:2px,color:#000
style E fill:#a9e34b,stroke:#5c940d,stroke-width:2px,color:#000
style F fill:#ff922b,stroke:#d9480f,stroke-width:2px,color:#fff
style G1 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style G2 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style H1 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000
style H2 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000graph TD
A[单节点压力过大] --> B[判断压力类型]
B --> C[读压力
查询慢、并发高]
B --> D[存储/写入压力
容量不足、写入慢]
C --> E[增加副本
同分片多副本]
D --> F[增加分片
多分片多副本]
E --> G1[1分片2副本]
E --> G2[1分片3副本]
F --> H1[2分片2副本]
F --> H2[3分片2副本]
style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
style B fill:#4dabf7,stroke:#1971c2,stroke-width:2px,color:#fff
style C fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff
style D fill:#ffd43b,stroke:#f59f00,stroke-width:2px,color:#000
style E fill:#a9e34b,stroke:#5c940d,stroke-width:2px,color:#000
style F fill:#ff922b,stroke:#d9480f,stroke-width:2px,color:#fff
style G1 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style G2 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style H1 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000
style H2 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000graph TD
A[单节点压力过大] --> B[判断压力类型]
B --> C[读压力
查询慢、并发高]
B --> D[存储/写入压力
容量不足、写入慢]
C --> E[增加副本
同分片多副本]
D --> F[增加分片
多分片多副本]
E --> G1[1分片2副本]
E --> G2[1分片3副本]
F --> H1[2分片2副本]
F --> H2[3分片2副本]
style A fill:#ff6b6b,stroke:#c92a2a,stroke-width:3px,color:#fff
style B fill:#4dabf7,stroke:#1971c2,stroke-width:2px,color:#fff
style C fill:#51cf66,stroke:#2f9e44,stroke-width:2px,color:#fff
style D fill:#ffd43b,stroke:#f59f00,stroke-width:2px,color:#000
style E fill:#a9e34b,stroke:#5c940d,stroke-width:2px,color:#000
style F fill:#ff922b,stroke:#d9480f,stroke-width:2px,color:#fff
style G1 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style G2 fill:#d0ebff,stroke:#1971c2,stroke-width:2px,color:#000
style H1 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#000
style H2 fill:#ffe8cc,stroke:#d9480f,stroke-width:2px,color:#00015.6. 实际扩展示例
15.6.1. 当前状态:1节点
| |
15.6.2. 扩展方案 A:增加副本(应对读压力)
| |
15.6.3. 扩展方案 B:增加分片(应对存储/写入压力)
| |
16. 一些常用的sql
| |