3. ClickHouse安装与部署

部署 ClickHouse,说白了就是两件事:选对模式,配好参数。我见过不少团队,单机跑得飞起,一上集群就各种玄学问题。其实大部分坑,都是 ZooKeeper 和配置细节没处理好。

这一章,我们从头捋一遍。从单机到分片副本集群,再到 ZooKeeper/Keeper 的选型与调优。最后我会把常见的部署问题列出来,都是我在项目中踩过的坑。

3.1 单机部署:最简方案

单机部署没什么难度。但有几个细节,我建议你注意一下。

3.1.1 安装方式选择

方式 适用场景 我的建议
RPM/DEB 包 生产环境,CentOS/Ubuntu 首选。systemd 管理,升级方便
Docker 测试、CI/CD、快速验证 别用于生产,除非你懂网络和存储
二进制 tar.gz 定制化安装、无 root 权限 手动配置多,容易漏
源码编译 二次开发、特殊 CPU 指令集 不推荐。编译一次半小时起步

我个人习惯用 RPM 包。一条命令装完,服务自动注册到 systemd,省心。

核心配置:/etc/clickhouse-server/config.xml

单机部署,你只需要改两个地方:

  • <listen_host>::</listen_host> — 允许外部访问。默认只监听 localhost。
  • <path>/var/lib/clickhouse/</path> — 数据目录。建议挂载独立磁盘,别用系统盘。

3.1.2 启动与验证

# 启动服务
sudo systemctl start clickhouse-server
sudo systemctl enable clickhouse-server

# 检查状态
sudo systemctl status clickhouse-server

# 连接客户端
clickhouse-client --host 127.0.0.1 --port 9000

# 验证
SELECT version(), uptime();

嗯,这里要注意:如果连不上,先看防火墙。9000 端口是 native 协议,8123 是 HTTP 协议。别搞混了。

3.2 集群部署:分片 + 副本

单机搞定了,我们上集群。ClickHouse 的集群模式,核心是两个概念:分片(Shard) 和 副本(Replica)。

  • 分片:把数据水平切分,每个分片存一部分数据。解决单机容量瓶颈。
  • 副本:每个分片的数据存多份。解决高可用问题。

说白了,分片解决「装不下」,副本解决「挂不掉」。两者通常一起用。

3.2.1 集群拓扑设计

我见过最常用的方案是 2 分片 2 副本,也就是 4 台机器。每台机器既是一个分片的主节点,又是另一个分片的副本节点。

我的经验:别一上来就搞 8 分片 16 副本。从 2 分片 2 副本起步,跑通了再扩。集群规模越大,ZooKeeper 的压力越大。

下面这张图,展示了 2 分片 2 副本的架构:

ClickHouse 2分片2副本集群架构 分片 1 (Shard 1) 节点 A 主副本 节点 B 副本 分片 2 (Shard 2) 节点 C 副本 节点 D 主副本 ZooKeeper / ClickHouse Keeper 集群 3 节点,负责元数据与副本协调 说明:每个分片有 1 个主副本 + 1 个副本,主副本负责写入,副本负责查询与容灾 ZooKeeper/Keeper 负责记录哪个节点是主副本,以及副本间的数据同步状态

3.2.2 配置文件详解

集群配置主要在 /etc/clickhouse-server/config.d/ 目录下。我习惯把集群配置拆成独立文件,方便管理。

第一步:配置 metrika.xml(集群拓扑)

<?xml version="1.0"?>
<yandex>
    <remote_servers>
        <my_cluster>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>node1.example.com</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node2.example.com</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>node3.example.com</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>node4.example.com</host>
                    <port>9000</port>
                </replica>
            </shard>
        </my_cluster>
    </remote_servers>

    <zookeeper>
        <node>
            <host>zk1.example.com</host>
            <port>2181</port>
        </node>
        <node>
            <host>zk2.example.com</host>
            <port>2181</port>
        </node>
        <node>
            <host>zk3.example.com</host>
            <port>2181</port>
        </node>
    </zookeeper>

    <macros>
        <shard>01</shard>
        <replica>node1</replica>
    </macros>
</yandex>

注意:internal_replication 设为 true 时,写入由 ClickHouse 自己管理副本同步。设为 false 的话,你需要外部工具(如 Proxy)来分发写入。我建议始终设为 true。

第二步:配置每个节点的 macros

每个节点的 macros 必须不同。它决定了这个节点属于哪个分片、叫什么名字。写错了,副本同步会乱套。

<!-- 节点 A -->
<macros>
    <shard>01</shard>
    <replica>node1</replica>
</macros>

<!-- 节点 B -->
<macros>
    <shard>01</shard>
    <replica>node2</replica>
</macros>

3.2.3 创建分布式表

集群配好了,怎么用?你需要建两张表:本地表和分布式表。

-- 1. 在每台节点上创建本地表
CREATE TABLE my_db.events_local ON CLUSTER my_cluster
(
    event_date Date,
    event_type String,
    value UInt32
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type);

-- 2. 在任意一台节点上创建分布式表
CREATE TABLE my_db.events_distributed ON CLUSTER my_cluster
AS my_db.events_local
ENGINE = Distributed('my_cluster', 'my_db', 'events_local', rand());

为什么本地表要用 ReplicatedMergeTree?因为只有这个引擎支持副本同步。普通 MergeTree 没有副本能力。

分布式表的最后一个参数 rand() 是分片键。你可以换成 cityHash64(event_type) 之类的,让相同类型的数据落到同一个分片。

3.3 ZooKeeper 与 ClickHouse Keeper

副本同步离不开协调服务。早期只能用 ZooKeeper,现在 ClickHouse 官方推出了 Keeper,兼容 ZooKeeper 协议,但更轻量。

3.3.1 选型对比

特性 ZooKeeper ClickHouse Keeper
部署复杂度 中等,需要独立 JVM 低,ClickHouse 自带
资源消耗 高,Java 进程吃内存 低,C++ 实现
兼容性 标准 ZooKeeper 协议 兼容 ZooKeeper 协议
运维成本 需要单独维护 随 ClickHouse 一起升级
生产成熟度 非常成熟 较新,但足够稳定

我的建议:新项目直接用 ClickHouse Keeper。少维护一套 Java 环境,少操一份心。老项目如果 ZooKeeper 已经跑得很稳,没必要迁移。

3.3.2 Keeper 配置示例

Keeper 的配置在 /etc/clickhouse-server/config.d/keeper.xml:

<yandex>
    <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>
            <raft_logs_level>information</raft_logs_level>
        </coordination_settings>

        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>k1.example.com</hostname>
                <port>9444</port>
            </server>
            <server>
                <id>2</id>
                <hostname>k2.example.com</hostname>
                <port>9444</port>
            </server>
            <server>
                <id>3</id>
                <hostname>k3.example.com</hostname>
                <port>9444</port>
            </server>
        </raft_configuration>
    </keeper_server>

    <!-- 启用 Keeper 替代 ZooKeeper -->
    <zookeeper>
        <node>
            <host>k1.example.com</host>
            <port>9181</port>
        </node>
        <node>
            <host>k2.example.com</host>
            <port>9181</port>
        </node>
        <node>
            <host>k3.example.com</host>
            <port>9181</port>
        </node>
    </zookeeper>
</yandex>

注意 server_id 每个节点必须不同。Raft 协议需要奇数个节点,最少 3 个。

3.4 常见部署问题排查

这部分是我最想分享的。我在项目中踩过的坑,列出来给你参考。

3.4.1 节点无法加入集群

现象:新节点启动后,日志一直报 Connection refused 或 Timeout。

原因:多半是防火墙、hostname 解析、或者端口没开。

排查步骤:

  1. 检查 telnet node_ip 9000 是否通。
  2. 检查 /etc/hosts 里 hostname 是否解析到正确的 IP。
  3. 检查 listen_host 是否设为 :: 或 0.0.0.0。

3.4.2 副本同步延迟

现象:查询分布式表,不同节点返回的数据不一致。

原因:ZooKeeper 压力大,或者网络带宽不够。

我曾经遇到过:一次线上事故,ZooKeeper 的磁盘 IO 被打满,导致副本同步延迟了 10 分钟。后来我把 ZooKeeper 的数据目录换成了 SSD,问题解决。

避坑指南:ZooKeeper/Keeper 的数据目录一定要用 SSD。机械盘扛不住频繁的写操作。

3.4.3 数据目录磁盘写满

现象:ClickHouse 进程挂掉,日志报 No space left on device。

原因:数据量增长超出预期,或者合并线程来不及合并临时数据。

解决方案:

  • 配置 max_bytes_before_external_group_by 和 max_memory_usage,防止内存溢出时写大量临时文件。
  • 设置 storage_policy,使用多级存储(热数据在 SSD,冷数据在 HDD)。
  • 监控磁盘使用率,设置告警阈值 80%。

3.4.4 分布式查询报错

现象:查询分布式表时报 Table doesn't exist 或 Different schema。

原因:各节点的本地表结构不一致,或者表没建全。

我的习惯:每次建表都用 ON CLUSTER my_cluster,确保所有节点同步执行。别手动一台一台建,容易漏。

3.4.5 ZooKeeper 会话超时

现象:ClickHouse 日志频繁出现 Session expired,然后副本同步中断。

原因:ZooKeeper 的 session_timeout 设置太小,或者 ZooKeeper 本身负载过高。

调整建议:

<!-- 在 config.xml 中调大超时时间 -->
<zookeeper>
    <session_timeout_ms>60000</session_timeout_ms>
    <operation_timeout_ms>30000</operation_timeout_ms>
</zookeeper>

总结一下:部署 ClickHouse 集群,最核心的三件事:

  1. 网络通 — 防火墙、hostname、端口。
  2. ZK/Keeper 稳 — SSD 磁盘、奇数节点、超时时间合理。
  3. 配置一致 — macros、表结构、集群定义,所有节点对齐。

这三件事做好了,集群基本不会出大问题。