Clickhouse集群实践指南

作者声明:内容由AI辅助创作

本文使用 ChatGpt 辅助完成:

  • 文章结构规划
  • 配置及示例生成
  • 技术细节解释优化

所有配置均经过实际测试验证。

信息

当前测试环境:

  • Ubuntu 24.04
  • ClickHouse server version 25.11.2.24 (official build)

1. 写在前面:这篇文章适合谁?

  • ✔ 第一次接触 ClickHouse 集群
  • ✔ 想从一开始就用“正确姿势”
  • ✔ 不想以后因为早期决策返工
  • ✔ 需要一份 可长期复用的模板

不适合

  • 只想临时跑个 demo
  • 不关心扩容 / 高可用 / 运维

2. 官方安装指南

https://clickhouse.com/docs/zh/install

3. ClickHouse 集群的整体认知

ClickHouse 中,“集群”不是一个开关,而是一整套能力的组合

graph TD
    %% 客户端层
    C[客户端 / App] -->|负载均衡/重试| D[Distributed 表]

    %% 分布式表到数据节点的路由
    subgraph DataNodes [ClickHouse 数据节点集群]
        D --> S1[Shard 1:  MergeTree/Replicas]
        D --> S2[Shard 2:  MergeTree/Replicas]
    end

    %% 协调层关系
    subgraph ControlPlane [协调与元数据]
        K[ClickHouse Keeper / ZooKeeper]
    end

    %% 节点与 Keeper 的心跳和同步
    S1 <--> K
    S2 <--> K

    %% 样式美化
    style C fill:#f9f,stroke:#333
    style D fill:#bbf,stroke:#333
    style K fill:#fff4dd,stroke:#d4a017
graph TD
    %% 客户端层
    C[客户端 / App] -->|负载均衡/重试| D[Distributed 表]

    %% 分布式表到数据节点的路由
    subgraph DataNodes [ClickHouse 数据节点集群]
        D --> S1[Shard 1:  MergeTree/Replicas]
        D --> S2[Shard 2:  MergeTree/Replicas]
    end

    %% 协调层关系
    subgraph ControlPlane [协调与元数据]
        K[ClickHouse Keeper / ZooKeeper]
    end

    %% 节点与 Keeper 的心跳和同步
    S1 <--> K
    S2 <--> K

    %% 样式美化
    style C fill:#f9f,stroke:#333
    style D fill:#bbf,stroke:#333
    style K fill:#fff4dd,stroke:#d4a017
graph TD
    %% 客户端层
    C[客户端 / App] -->|负载均衡/重试| D[Distributed 表]

    %% 分布式表到数据节点的路由
    subgraph DataNodes [ClickHouse 数据节点集群]
        D --> S1[Shard 1:  MergeTree/Replicas]
        D --> S2[Shard 2:  MergeTree/Replicas]
    end

    %% 协调层关系
    subgraph ControlPlane [协调与元数据]
        K[ClickHouse Keeper / ZooKeeper]
    end

    %% 节点与 Keeper 的心跳和同步
    S1 <--> K
    S2 <--> K

    %% 样式美化
    style C fill:#f9f,stroke:#333
    style D fill:#bbf,stroke:#333
    style K fill:#fff4dd,stroke:#d4a017

记住一句话

ClickHouse 的高可用 = 数据可复制 + 元数据不丢 + 访问不中断


4. 为什么“单节点也要用集群模式初始化”

4.1. 常见误区

“现在只有一台机器,用单机模式更简单吧?”

这是 ClickHouse 中最容易留下技术债的决定。


4.2. 正确做法(推荐)

即使只有 1 台机器,也要:

  • 配置 cluster.xml
  • 配置 keeper.xml
  • 所有 DDL 使用 ON CLUSTER

原因:

好处 解释
表结构不返工 以后加节点不用重建
用法统一 单机 / 集群 SQL 一样
符合 25.x 设计 ON CLUSTER 强依赖 Keeper

5. 集群配置:cluster.xml

5.1. 文件位置(约定)

1
/etc/clickhouse-server/config.d/cluster.xml

5.2. 示例(单节点起步)

  • 关键说明

    • cluster 名字非常重要 后续所有 ON CLUSTER xxx 都依赖它
    • shard ≠ replica
      • shard:数据水平切分
      • replica:同一数据的副本
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    <clickhouse>
        <remote_servers>
            <!-- 集群名称,后续 SQL 会用到 -->
            <default_cluster>
                <!-- shard:数据分片
                        shard 数量 = 数据水平切分的数量
                        一个 shard 可以包含多个 replica(副本节点) -->
                <shard>
                    <!-- replica:副本 -->
                    <replica>
                        <!-- 本机最好也设置是内网IP -->
                        <host>127.0.0.1</host>
                        <port>9000</port>
                    </replica>
                </shard>
            </default_cluster>
        </remote_servers>
    </clickhouse>

6. Keeper:为什么必须有 ? & 怎么配 ?

6.1. Keeper 是干什么的?

Keeper 负责:

  • 分布式 DDL(ON CLUSTER
  • 副本元数据
  • 表锁
  • 副本状态协调

没有 Keeper,集群会“看起来存在,但不能用”


6.2. keeper.xml(最小可用,单节点)

  • /etc/clickhouse-server/config.d/keeper.xml

     1
     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
    
    <clickhouse>
    
        <!-- Keeper 服务本身 -->
        <keeper_server>
            <!-- Keeper 对外端口 -->
            <tcp_port>9181</tcp_port>
    
            <!-- 每台机器唯一,不能重复 -->
            <server_id>1</server_id>
    
            <!-- Keeper 自己的数据目录 -->
            <log_storage_path>
                /var/lib/clickhouse/coordination/log
            </log_storage_path>
            <snapshot_storage_path>
                /var/lib/clickhouse/coordination/snapshots
            </snapshot_storage_path>
    
            <!-- Raft 成员(单节点也要写) -->
            <raft_configuration>
                <!-- 多少个节点(成员),多少个 server , 其中id与对应的 server_id 保持一致 -->
                <server>
                    <id>1</id>
                    <hostname>127.0.0.1</hostname>
                    <!-- 9234 是 Keeper 节点之间 Raft 通信端口 -->
                    <port>9234</port>
                </server>
            </raft_configuration>
        </keeper_server>
    
        <!-- ClickHouse Server 通过这里访问 Keeper -->
        <zookeeper>
            <!-- 同样多少个节点,多少个node -->
            <node>
                <host>127.0.0.1</host>
                <!-- 9181 是 ClickHouse Server 连接 Keeper 的端口 -->
                <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.xml
     1
     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>
        <!-- ClickHouse 数据根目录 -->
        <!-- 所有表、元数据都在这里 -->
        <path>/data/clickhouse/</path>
    
        <!-- 多磁盘配置 -->
        <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)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CREATE TABLE analytics.events
ON CLUSTER default_cluster
(
    event_time DateTime,
    user_id UInt64,
    event_type String,
    payload String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id)
TTL event_time + INTERVAL 7 DAY TO DISK 'cold'
SETTINGS
    storage_policy = 'hot_to_cold';

8.2. 高可用版本(推荐,生产)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE analytics.events
ON CLUSTER default_cluster
(
    event_time DateTime,
    user_id UInt64,
    event_type String,
    payload String
)
ENGINE = ReplicatedMergeTree(
    -- 必须要有 macros.xml 配置
    -- {shard} / {replica} 是 ClickHouse 内置宏
    -- 在 ON CLUSTER 执行时由系统自动替换
    -- 不要手写成固定值
    '/clickhouse/tables/{shard}/events',
    '{replica}'
)
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id)
TTL event_time + INTERVAL 7 DAY TO DISK 'cold'
SETTINGS
    storage_policy = 'hot_to_cold';

📌 为什么一定要 ReplicatedMergeTree?

  • 扩容后自动补数据
  • 节点挂掉不丢数据
  • 支持滚动升级

9. Distributed 表(统一读写入口)

1
2
3
4
5
6
7
8
9
CREATE TABLE analytics.events_all
ON CLUSTER default_cluster
AS analytics.events
ENGINE = Distributed(
    default_cluster,
    analytics,
    events,
    cityHash64(user_id)
);

使用原则

  • 写 → events_all
  • 查 → events_all
  • 不直接访问本地表

10. 备份与恢复

10.1. 推荐工具:clickhouse-backup

部署位置: 👉 每个 ClickHouse 节点(或运维机)


10.2. 创建备份

包含:

  • 表结构
  • 数据(支持增量)
1
clickhouse-backup create daily_2025_12_18

10.3. 恢复示例(单表)

1
clickhouse-backup restore --table analytics.events daily_2025_12_18

10.4. 恢复整个数据库

1
clickhouse-backup restore daily_2025_12_18

10.5. 其他

  • 备份 /var/lib/clickhouse 目录(最后防线,但可能不靠谱)

11. 升级和迁移

📌 要求:

  • 副本 ≥ 2
  • Keeper 存活

11.1. 正确姿势:滚动升级

1
2
node1 升级 → OK
node2 升级 → OK

11.2. 升级前必做:

1
SYSTEM SYNC REPLICA;

11.3. 迁移(换机器 / 扩容)

正确方式:

  • 新节点上线
  • 加入 cluster.xml
  • Replicated 表自动同步
  • 移除旧节点

12. 高可用

12.1. 自动重试 9000 端口

12.2. HAProxy

  • 支持 TCP
  • 健康检查好
  • 性能稳定
1
2
3
4
5
6
7
8
frontend clickhouse
    bind *:9000
    default_backend ch_nodes

backend ch_nodes
    balance roundrobin
    server n1 172.31.10.11:9000 check
    server n2 172.31.10.110:9000 check

12.3. 不推荐 Nginx(除非 HTTP)

Nginx

  • 更适合 8123
  • 不适合高并发 TCP 查询

13. 扩容与自动复制是如何发生的?

  1. 新节点上线
  2. 加入 cluster.xml
  3. 新节点配置 Keeper
  4. Replicated 表自动:
    • 注册副本
    • 拉取历史数据

可手动触发:

1
SYSTEM SYNC REPLICA analytics.events;

14. 总结:一套“不会后悔”的 ClickHouse 模板

  • 集群模式 从第一天开始
  • Keeper 是地基,不是附加品
  • 冷热分层是抽象,不是硬件
  • 表是否可复制,取决于第一次建表

15. 其他

  1
  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
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
-- 查看集群状态
SELECT * FROM system.clusters;

-- 查看 Keeper 中的 ZK 路径数据
SELECT * FROM system.zookeeper;

-- 查看副本状态
SELECT * FROM system.replicas;

-- 验证集群是否生效(在任意一台执行)
SELECT * FROM system.clusters WHERE cluster = 'default_cluster';

-- 创建数据库(集群)
CREATE DATABASE test_cluster ON CLUSTER default_cluster;
-- 验证
SHOW DATABASES;

-- 集群拓扑
SELECT
    cluster,
    shard_num,
    replica_num,
    host_name,
    host_address,
    port
FROM system.clusters
ORDER BY shard_num, replica_num;


--当前节点身份(宏是否生效)
SELECT getMacro('shard')   AS shard, getMacro('replica') AS replica;

--副本整体状态
--健康参考: 
--is_readonly = 0
--queue_size ≈ 0
--inserts_in_queue ≈ 0
SELECT
    database,
    table,
    is_leader,
    is_readonly,
    future_parts,
    parts_to_check,
    queue_size,
    inserts_in_queue
FROM system.replicas;

--复制延迟 / 堆积详情
--判断是否存在副本 lag
--升级 / 扩容前必须检查

SELECT
    database,
    table,
    absolute_delay,
    log_max_index,
    log_pointer
FROM system.replicas;

-- -- 强制修复常用命令
--使用建议: 
--1. 先 SYNC
--2. 再 RESTART
--3. 不要随便 DROP REPLICA

-- SYSTEM SYNC REPLICA db.table;
-- SYSTEM RESTART REPLICA db.table;



--磁盘状态
--用途: 
-- - 验证 hot / cold 是否加载
-- - 排查磁盘空间问题

SELECT
    name,
    path,
    free_space,
    total_space
FROM system.disks;

-- 存储策略
SELECT * FROM system.storage_policies;

-- 表实际使用的磁盘
-- - 用途: 
-- - 验证 TTL 是否真的 move 到 cold
-- - 冷热分层是否生效

SELECT
    database,
    table,
    disk_name,
    sum(bytes_on_disk) AS bytes
FROM system.parts
GROUP BY database, table, disk_name
ORDER BY bytes DESC;

--分区 / part 状态 
-- - 用途: 
-- - parts 数过多 = merge 压力
-- - 运维调优的基础指标

SELECT
    database,
    table,
    count() AS parts,
    sum(bytes_on_disk) AS size
FROM system.parts
WHERE active
GROUP BY database, table;

--当前正在执行的查询
SELECT
    query_id,
    user,
    elapsed,
    memory_usage,
    query
FROM system.processes;


-- 最近慢查询
SELECT
    query_duration_ms,
    read_rows,
    result_rows,
    query
FROM system.query_log
ORDER BY query_duration_ms DESC
LIMIT 10;

--数据库级容量
SELECT
    database,
    sum(bytes_on_disk) AS size
FROM system.parts
WHERE active
GROUP BY database
ORDER BY size DESC;
0%