ClickHouse集群部署与分布式表引擎实战指南

说明:本文将从零开始,带你一步步完成 ClickHouse 集群的部署和配置,重点讲解如何利用分布式表(Distributed)引擎实现跨节点的数据分片和查询。文中包含配置文件示例、SQL 代码示例,以及图解帮助你快速理解集群拓扑和引擎原理。

目录

  1. 前言
  2. ClickHouse 集群架构概览

    • 2.1 集群节点类型
    • 2.2 集群拓扑示意图
  3. 环境准备

    • 3.1 系统要求与依赖
    • 3.2 网络与防火墙配置
  4. 节点安装与基础配置

    • 4.1 单节点安装步骤
    • 4.2 配置文件结构说明
    • 4.3 常用参数详解
  5. 集群级别配置

    • 5.1 ZooKeeper 集群部署(可选但推荐)
    • 5.2 ClickHouse 配置联动 ZooKeeper
    • 5.3 拓扑文件 (cluster.xml) 配置示例
  6. 分布式表引擎原理与实战

    • 6.1 分布式表(Distributed)引擎基础
    • 6.2 本地引擎(MergeTree)与分布式引擎配合
    • 6.3 拉取数据与查询路由
    • 6.4 具体示例:创建本地表和分布式表
  7. 数据导入与查询示例

    • 7.1 数据插入到本地分片
    • 7.2 通过分布式表进行全局查询
    • 7.3 并行查询优化与监控指标
  8. 高可用与负载均衡

    • 8.1 ZooKeeper 保持节点状态与 Failover
    • 8.2 Proxy 层常见方案(例如 HAProxy/Nginx)
    • 8.3 查询路由示意图
  9. 总结与参考文档

1. 前言

ClickHouse 是一款由 Yandex 开源的高性能列式分布式 OLAP 数据库,擅长海量数据的实时分析与查询。单机部署就能获得非常快的查询速度,而集群化部署则可以水平扩展,支持更大规模的数据存储与并行计算。
本文重点关注:

  • 如何从零搭建一个简单的 ClickHouse 集群
  • 如何使用分布式表(Distributed)引擎将数据分片到多个节点
  • 如何针对高并发查询进行性能优化与监控

通过阅读本文,你将了解 ClickHouse 的集群配置逻辑、分布式表的使用方法,以及集群高可用的最佳实践。


2. ClickHouse 集群架构概览

2.1 集群节点类型

一个典型的 ClickHouse 集群通常包含以下几种角色:

  1. ZooKeeper 节点(可选,推荐)

    • 作用:负责存储集群元数据(如分片信息、复制队列等),协调各 ClickHouse 节点之间的分布式一致性。
    • 推荐配置:3 节点或以上的 ZooKeeper 集群,保证高可用。
  2. ClickHouse 数据节点(Data Node)

    • 作用:存储并处理数据,多数使用 MergeTree 系列引擎。
    • 特点:数据根据分片判定规则分布到不同数据节点,节点之间通过 ZooKeeper 协调写操作和复制。
  3. ClickHouse 查询(或 Proxy)节点(可选)

    • 作用:接收客户端查询请求,将 SQL 语句路由到下游数据节点,汇总结果后返回客户端。
    • 优点:可以屏蔽客户端对集群内部拓扑的感知,实现负载均衡与高可用。

本文示例采用最简化拓扑:

  • 一个 ZooKeeper 集群(3 个节点)
  • 两个 Data Node,分别作为分片的两个副本
  • 一个 Proxy Node 作为统一入口

2.2 集群拓扑示意图

              ┌───────────────────┐
              │   Client (CLI/UI) │
              └────────┬──────────┘
                       │  (1) SQL 请求
                       ▼
             ┌─────────────────────┐
             │  Proxy Node (CH-P)  │
             │  clickhouse-server  │
             └──────────┬──────────┘
                        │ (2) 根据 cluster.xml 路由
      ┌─────────────────┴─────────────────┐
      │                                   │
      ▼                                   ▼
┌──────────────┐                   ┌──────────────┐
│ ClickHouse   │                   │ ClickHouse   │
│ Data Node 1  │                   │ Data Node 2  │
│  (Shard 1)   │                   │  (Shard 2)   │
│ merge_tree1  │                   │ merge_tree1  │
└─────┬────────┘                   └─────┬────────┘
      │                                   │
      │                                   │
      │    ┌─────────────────────────┐    │
      └───▶│    ZooKeeper Cluster   ◀────┘
           │  zk1, zk2, zk3 (3 节点) │
           └─────────────────────────┘
  • 步骤 (1):Client 将 SQL 请求发送给 Proxy Node。
  • 步骤 (2):Proxy Node 根据 /etc/clickhouse-server/config.d/cluster.xml 中定义的集群拓扑,将请求分发到对应的 Data Node(Shard)。
  • Data Node:各自保存本地分片数据,并在 ZooKeeper 中完成分片间的复制协调。
  • ZooKeeper:存储分片分配信息、复制队列等集群元数据,保证写入的一致性和容错。

3. 环境准备

3.1 系统要求与依赖

  1. 操作系统

    • 建议使用 CentOS 7/8、Ubuntu 18.04/20.04 或者 Debian 9/10。
    • 这里以 Ubuntu 20.04 LTS 为示例,其他 Linux 发行版类似。
  2. 机器配置(Data Node)

    • CPU:4 核及以上
    • 内存:16 GB 及以上
    • 磁盘:SSD(至少 200 GB)
    • 网络:千兆以太网,保证低延迟
  3. ZooKeeper机器(各 3 节点)

    • CPU:2 核
    • 内存:4 GB
    • 磁盘:机械盘即可,只存储少量元数据
    • 配置为三台独立的机器,以保证 ZooKeeper 集群的高可用性
  4. 依赖软件

    • OpenJDK 8/11(ZooKeeper 依赖)
    • wget、curl、tar 等常用命令行工具

3.2 网络与防火墙配置

  • 确保各节点之间可以互通,默认端口:

    • ClickHouse:TCP 9000(native),HTTP 8123,TCP 9009(interserver)
    • ZooKeeper:TCP 2181(客户端连接),TCP 2888/3888(集群内部通信)
  • 如果启用了防火墙(ufwfirewalld),需开放相应端口。示例(Ubuntu 下采用 ufw):
# 允许 ClickHouse native 协议、HTTP 协议与 interserver 通信
sudo ufw allow 9000/tcp
sudo ufw allow 8123/tcp
sudo ufw allow 9009/tcp

# 允许 ZooKeeper 端口
sudo ufw allow 2181/tcp
sudo ufw allow 2888/tcp
sudo ufw allow 3888/tcp

sudo ufw enable

4. 节点安装与基础配置

4.1 单节点安装步骤

以下示例以 Ubuntu 20.04 为例,演示如何安装 ClickHouse 二进制包。

# 1. 添加 ClickHouse 官方仓库 GPG Key
curl https://packages.clickhouse.com/CLICKHOUSE-KEY.GPG | sudo apt-key add -

# 2. 添加仓库地址
sudo sh -c 'echo "deb https://packages.clickhouse.com/deb stable main" > /etc/apt/sources.list.d/clickhouse.list'

# 3. 更新并安装 clickhouse-server 与 clickhouse-client
sudo apt update
sudo apt install -y clickhouse-server clickhouse-client

# 4. 启动并设置为开机自启
sudo systemctl enable clickhouse-server
sudo systemctl start clickhouse-server

# 5. 验证服务状态
sudo systemctl status clickhouse-server

安装完成后,ClickHouse 默认会在 /etc/clickhouse-server/ 下生成以下关键目录:

  • config.xml:ClickHouse 全局配置文件
  • users.xml:用户权限配置文件
  • config.d/:可放置自定义的扩展配置
  • users.d/:可放置自定义的用户配置
  • macros.xml:变量宏定义(常用于集群配置)

4.2 配置文件结构说明

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

    • 定义 HTTP 服务端口、Logging、Zookeeper、Interserver 通信等全局参数。
    • 示例(简化):
<yandex>
    <!-- 监听端口 -->
    <tcp_port>9000</tcp_port>
    <http_port>8123</http_port>
    <interserver_http_port>9009</interserver_http_port>

    <!-- 日志与临时目录 -->
    <logger>
        <level>information</level>
        <log>/var/log/clickhouse-server/clickhouse-server.log</log>
        <errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
    </logger>
    <path>/var/lib/clickhouse/</path>
    <tmp_path>/var/lib/clickhouse/tmp/</tmp_path>

    <!-- ZooKeeper 配置(后文将补充) -->
</yandex>
  1. /etc/clickhouse-server/users.xml

    • 定义用户及其权限,默认包含一个 default 用户,密码为空,可访问所有数据库。
    • 这里最好创建一个强密码的管理员用户,并限制 default 用户只读或禁用。
  2. /etc/clickhouse-server/macros.xml

    • 定义集群相关宏(如 {cluster}, {shard}, {replica} 等),在 cluster.xml 中会引用这些宏。
    • 示例:
<yandex>
    <macros>
        <!-- 在服务器自己的 config.d/cluster.xml 中,如果需要使用宏可以在此定义 -->
        <cluster>my_clickhouse_cluster</cluster>
        <shard>shard1</shard>
        <replica>replica1</replica>
    </macros>
</yandex>

4.3 常用参数详解

  • <path><tmp_path>

    • path:ClickHouse 数据文件存储路径,主存储目录。
    • tmp_path:临时文件存储路径,如临时排序文件。
  • <max_concurrent_queries>, <max_memory_usage>

    • 可以根据机器资源进行调整,避免单个查询占满全部内存或资源。
  • <listen_host>

    • 如果只希望监听特定网卡,可以设置;默认为 0.0.0.0 全网段监听。
  • <zookeeper>

    • 用于指定 ZooKeeper 集群地址(多个节点可使用逗号分隔),示例可在下一节详解。

5. 集群级别配置

5.1 ZooKeeper 集群部署(可选但推荐)

ClickHouse 的副本(Replicated MergeTree)和分布式表(Distributed)很大程度依赖于 ZooKeeper 来实现一致性与协调。若只是做测试,也可以省略 ZooKeeper,但不推荐在生产环境省略。

以下以三台服务器(IP 假设为 10.0.0.1, 10.0.0.2, 10.0.0.3)为例,部署 ZooKeeper 3.7.x。

  1. 安装 Java(以 OpenJDK 11 为例)

    sudo apt update
    sudo apt install -y openjdk-11-jre-headless
  2. 下载并解压 ZooKeeper

    wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz
    tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz
    sudo mv apache-zookeeper-3.7.1-bin /opt/zookeeper
  3. 配置 zoo.cfg

    /opt/zookeeper/conf/zoo.cfg 中写入:

    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/var/lib/zookeeper
    clientPort=2181
    
    # 下面三行用于集群通信
    server.1=10.0.0.1:2888:3888
    server.2=10.0.0.2:2888:3888
    server.3=10.0.0.3:2888:3888
    • dataDir:保存 ZooKeeper 元数据的路径,需提前创建并赋予 zookeeper 用户权限。
    • server.X:集群内部通信地址,X 为 ID(从 1 起)。
  4. 设置 myid 文件

    sudo mkdir -p /var/lib/zookeeper
    echo "1" | sudo tee /var/lib/zookeeper/myid   # 对于 IP 10.0.0.1 上填入 1
    # 第二台 IP 10.0.0.2: echo "2" > /var/lib/zookeeper/myid
    # 第三台 IP 10.0.0.3: echo "3" > /var/lib/zookeeper/myid
  5. 启动 ZooKeeper

    cd /opt/zookeeper
    bin/zkServer.sh start
  6. 验证状态

    bin/zkServer.sh status

    如果显示 Mode: followerMode: leader 即可,说明集群已初始化成功。

5.2 ClickHouse 配置联动 ZooKeeper

在每个 ClickHouse Data Node(假设在 10.0.0.1110.0.0.12)上,需要编辑 /etc/clickhouse-server/config.d/zookeeper.xml,将 ZooKeeper 信息写入:

<yandex>
    <zookeeper>
        <!-- 可以指定多个节点,格式:host:port -->
        <node>
            <host>10.0.0.1</host>
            <port>2181</port>
        </node>
        <node>
            <host>10.0.0.2</host>
            <port>2181</port>
        </node>
        <node>
            <host>10.0.0.3</host>
            <port>2181</port>
        </node>
        <!-- 可选:设置会话超时时间 -->
        <session_timeout_ms>300000</session_timeout_ms>
    </zookeeper>
</yandex>
  • 重启 ClickHouse 服务使配置生效:

    sudo systemctl restart clickhouse-server

5.3 拓扑文件(cluster.xml)配置示例

在集群模式下,需要在每台 Data Node 上的 /etc/clickhouse-server/config.d/cluster.xml 中定义集群拓扑。例如,假设集群名称为 my_cluster,有两个分片(shard1、shard2),每个分片有两个副本(replica1、replica2),实际 IP 如下:

  • Shard1:

    • Replica1: 10.0.0.11
    • Replica2: 10.0.0.12
  • Shard2:

    • Replica1: 10.0.0.13
    • Replica2: 10.0.0.14

在所有节点的 /etc/clickhouse-server/config.d/cluster.xml 中,写入:

<yandex>
    <remote_servers>
        <my_cluster>
            <!-- Shard 1 定义 -->
            <shard>
                <replica>
                    <host>10.0.0.11</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>10.0.0.12</host>
                    <port>9000</port>
                </replica>
            </shard>
            <!-- Shard 2 定义 -->
            <shard>
                <replica>
                    <host>10.0.0.13</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>10.0.0.14</host>
                    <port>9000</port>
                </replica>
            </shard>
        </my_cluster>
    </remote_servers>

    <!-- 定义用于 SQL 中引用的宏 -->
    <macros>
        <cluster>my_cluster</cluster>
        <!-- 注意每个节点还需要在自己的 macros.xml 中定义 shard 与 replica 的值 -->
    </macros>
</yandex>

说明

  • <remote_servers>:用于定义集群中可访问的节点分组,名字 my_cluster 可以自定义。
  • 每个 <shard> 下可以定义多个 <replica>,ClickHouse 在写入时会向每个 shard 内的 replica 同步数据。
  • 所有节点都需要能够互相读取到同一份 cluster.xml,否则查询时会出现节点不可达或配置不一致错误。

6. 分布式表引擎原理与实战

6.1 分布式表(Distributed)引擎基础

在 ClickHouse 集群中,通常会结合以下两种引擎来实现分布式写入与查询:

  • 本地引擎

    • 最常用的是 MergeTree(及其变体,比如 ReplicatedMergeTree)。
    • 数据存储在节点本地文件系统,支持二级索引、分区、分桶、TTL 等。
  • 分布式引擎(Distributed)

    • 用于将 SQL 查询路由到多个节点的本地表,并将结果合并后返回给客户端。
    • 其核心配置包括:

      • cluster:要路由到的集群名(即 cluster.xml 中定义的 <remote_servers>)。
      • database:本地数据库名。
      • table:本地表名。
      • sharding_key(可选):用于将写入请求按哈希算法路由到不同 shard。

当你向分布式表插入数据时,ClickHouse 会根据 sharding_key 计算出应该插入到哪个 shard,再把这条数据落到对应 shard 的本地表中(若没有明确 sharding_key,则轮询或全部写入)。
当你从分布式表查询时,ClickHouse 会拆分查询,将子查询同时发往各个 shard,然后将各个节点返回的结果做合并、排序、聚合等处理后返回给客户端。

6.2 本地引擎(MergeTree)与分布式引擎配合

下面以 events 表为例,演示如何先在每个节点上创建一个本地的 MergeTree 表,再创建对应的 Distributed 表。

6.2.1 本地表(采用 ReplicatedMergeTree)

在每个 Data Node(假设执行环境是 clickhouse-client 已登录到每个节点)上,先创建一个数据库(若未创建):

CREATE DATABASE IF NOT EXISTS analytics;

然后在每个节点上执行(注意:{cluster}, {shard}, {replica} 宏需要在各节点的 macros.xml 中预先定义):

CREATE TABLE analytics.events_local
(
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type String,
    event_properties String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{cluster}/events_local', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
TTL event_date + INTERVAL 30 DAY  -- 示例:30 天后自动清理
SETTINGS index_granularity = 8192;
  • /clickhouse/tables/{cluster}/events_local:ZooKeeper 路径,用于存储副本队列等元数据。
  • {replica}:宏定义,每台服务器需要在 macros.xml 中设置自己对应的 replica1replica2 等。
  • PARTITION BY toYYYYMM(event_date):按月份分区。
  • ORDER BY (event_date, user_id):常见的排序键,可加速基于日期或用户的查询。

执行成功后,系统会在 ZooKeeper 中创建对应的目录结构,并在各副本之间进行数据同步。

6.2.2 分布式表(Distributed)创建

分布式表不存储数据,仅负责查询路由与合并。我们在同一个 analytics 数据库下执行:

CREATE TABLE analytics.events
(
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type String,
    event_properties String
)
ENGINE = Distributed(
    my_cluster,         -- 与 cluster.xml 中 remote_servers 定义保持一致
    analytics,          -- 本地数据库
    events_local,       -- 本地表
    rand()              -- 随机函数,用于插入时随机负载到不同 shard
);
  • my_cluster:集群名称,对应 cluster.xml<my_cluster>
  • analytics:本地库名。
  • events_local:本地物理表名。
  • rand():作为简单示例,将插入的行随机分发到两个 shard;也可以使用更复杂的分片键,比如 user_id % 2 等。

6.3 拉取数据与查询路由

  1. 写入数据
    向分布式表 analytics.events 插入数据时:

    INSERT INTO analytics.events VALUES
    ('2025-06-03', now(), 1001, 'page_view', '{"url": "/home"}'),
    ('2025-06-03', now(), 1002, 'click', '{"button": "signup"}');

    ClickHouse 会计算 rand() 或者 sharding_key 决定这两条记录应该插往哪个 shard,然后把它对应的 INSERT 请求转发给目标 shard 的某个副本上执行。

  2. 查询数据
    当你执行:

    SELECT event_type, count() 
    FROM analytics.events 
    WHERE event_date = '2025-06-03'
    GROUP BY event_type;

    ClickHouse 会将此查询拆分成如下子任务:

    • 在 Shard1 上执行相同的 SELECT,得到部分聚合结果 [(page_view, 500), (click, 200)](示例)
    • 在 Shard2 上执行相同的 SELECT,得到部分聚合结果 [(page_view, 600), (click, 150)](示例)
    • Proxy Node(或客户端)接收到各个子结果后,进行二次合并:

      • page_view: 500 + 600 = 1100
      • click: 200 + 150 = 350
    • 最终返回给客户端:[(page_view, 1100), (click, 350)]

图解:分布式查询流程

┌───────────────────────────────────────────────────────────────────┐
│                         分布式查询 (Distributed)                 │
│                                                                   │
│  Client/Proxy                                                      │
│  │                                                                │
│  │  1. 下发查询请求                                                │
│  ▼                                                                │
│ +----------------------------+                                     │
│ | Distributed Table Routing  |                                     │
│ +----------------------------+                                     │
│  │                                                                │
│  │  2. 向各个 Shard 分发查询                                         │
│  ▼                                                                │
│  ┌───────────────┐             ┌───────────────┐                   │
│  │  Shard1 (2台) │             │  Shard2 (2台) │                   │
│  │  ┌─────────┐  │             │  ┌─────────┐  │                   │
│  │  │Replica1 │  │             │  │Replica1 │  │                   │
│  │  └─────────┘  │             │  └─────────┘  │                   │
│  │  ┌─────────┐  │             │  ┌─────────┐  │                   │
│  │  │Replica2 │  │             │  │Replica2 │  │                   │
│  │  └─────────┘  │             │  └─────────┘  │                   │
│  └───────────────┘             └───────────────┘                   │
│         ▲                            ▲                             │
│         │  3. 各副本执行聚合并返回部分结果  │                            │
│         │                            │                             │
│         └────── 4. 合并结果 ──────────┘                             │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

6.4 具体示例:创建本地表和分布式表

本地表(示例)

CREATE TABLE analytics.logs_local
(
    ts DateTime,
    level String,
    message String
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{cluster}/logs_local',
    '{replica}'
)
PARTITION BY toYYYYMM(ts)
ORDER BY ts
SETTINGS index_granularity = 4096;
  • 每个副本节点都要执行同样的建表语句。

分布式表(示例)

CREATE TABLE analytics.logs
(
    ts DateTime,
    level String,
    message String
)
ENGINE = Distributed(
    my_cluster,      -- cluster 名称
    analytics,       -- 本地库
    logs_local,      -- 本地表名
    sipHash64(message)  -- 推荐使用哈希函数,保证同一条日志恒定路由到同一 shard
);
  • 通过 sipHash64(message) 分片,能保证同一条日志按照 message 字符串散列值决定落到哪个 shard。
  • 也可使用 rand() 做均匀随机分片,但不保证同一 message 写到同一 shard。

7. 数据导入与查询示例

7.1 数据插入到本地分片

假设我们向分布式表 analytics.events 导入一批 CSV 文件,示例 CSV 文件 events_20250603.csv 内容如下:

2025-06-03,2025-06-03 10:00:00,1001,page_view,{"url":"/home"}
2025-06-03,2025-06-03 10:05:00,1002,click,{"button":"signup"}
2025-06-03,2025-06-03 10:10:00,1001,click,{"button":"purchase"}
2025-06-03,2025-06-03 10:15:00,1003,page_view,{"url":"/product"}
  1. 使用 clickhouse-client 导入 CSV

    clickhouse-client --query="INSERT INTO analytics.events FORMAT CSV" < events_20250603.csv
    • ClickHouse 会解析 CSV,并将每行数据根据分片策略写入到对应的本地表上。
    • 例如第一行的 user_id = 1001,若 rand() 模式下随机写入到 Shard1;若使用 user_id % 2 可能落到 Shard1(1001 % 2 = 1)。
  2. 验证本地分片写入情况

    • 登录 Shard1 的 Replica1 (10.0.0.11):

      clickhouse-client
    • 查询本地表 events_local 的数据量:

      SELECT 
          count() AS cnt, 
          shardNumber() AS shard_id
      FROM analytics.events_local
      GROUP BY shard_id;
    • 类似地,在 Shard2 (10.0.0.13) 上查看 events_local,对比两边的分布情况。

7.2 通过分布式表进行全局查询

  1. 简单聚合查询

    SELECT 
        event_type, 
        count() AS total_cnt 
    FROM analytics.events
    WHERE event_date = '2025-06-03'
    GROUP BY event_type 
    ORDER BY total_cnt DESC;
    • 该查询会并行发往各个 shard,然后在 Proxy/客户端做最终合并排序。
  2. 按用户统计访问量

    SELECT 
        user_id, 
        count() AS visits 
    FROM analytics.events
    WHERE event_date = '2025-06-03' 
      AND event_type = 'page_view' 
    GROUP BY user_id 
    HAVING visits > 1 
    ORDER BY visits DESC 
    LIMIT 10;
    • 充分利用 ORDER BY (event_date, user_id) 索引加速。

7.3 并行查询优化与监控指标

  • 并行流(Parallel Replicas)

    • 默认情况下,分布式表会读取每个 shard 上第一个可用的副本(顺序无保证)。
    • 若想在同一 shard 内的多个副本并行扫描,可设置 distributed_replica_read_mode = 'parallel'
    • 例如在客户端或者 users.xml 中配置:

      <profiles>
          <default>
              <distributed_replica_read_mode>parallel</distributed_replica_read_mode>
          </default>
      </profiles>
  • 监控指标

    • 在 ClickHouse 内部可以通过系统表 system.metricssystem.events 监控:

      • QueryThreads: 当前并发查询线程数
      • NetworkSendBytes, NetworkReceiveBytes: 网络吞吐
      • MergeTreeParts*: 后台合并状态
    • 例如:

      SELECT 
          metric, 
          value 
      FROM system.metrics 
      WHERE match(metric, 'Query|Network');

8. 高可用与负载均衡

8.1 ZooKeeper 保持节点状态与 Failover

  • 当某个 Data Node 宕机时,ZooKeeper 会检测到节点不可用,ClickHouse Client(或 Proxy)会自动路由到同 shard 下的其他可用副本进行查询与写入。
  • 写操作:写到 ReplicatedMergeTree 时,若当前副本短暂不可用,则写会被暂缓到 ZooKeeper 的队列中,待该副本恢复后自动同步;若整个 shard 下所有副本都不可用,则写入失败。

8.2 Proxy 层常见方案

  1. HAProxy

    • 可以配置 balance roundrobinbalance leastconn,将客户端请求分发给多个 ClickHouse 节点。
    • 示例 haproxy.cfg

      global
          log /dev/log    local0
          maxconn 4096
          daemon
      
      defaults
          log     global
          mode    tcp
          option  tcplog
          timeout connect 5s
          timeout client  50s
          timeout server  50s
      
      listen clickhouse
          bind *:9000
          mode tcp
          option tcp-check
          default-server inter 3s fall 3 rise 2
          server ch11 10.0.0.11:9000 check
          server ch12 10.0.0.12:9000 check
          server ch13 10.0.0.13:9000 check
          server ch14 10.0.0.14:9000 check
    • 这样客户端连接到 HAProxy 的 9000 端口,就相当于连接到了一个虚拟的 ClickHouse 集群入口。
  2. Nginx Stream 模块

    • nginx.conf 中启用 stream {} 区块,类似 HAProxy 做 TCP 负载均衡。

8.3 查询路由示意图

      ┌────────┐
      │ Client │
      └───┬────┘
          │
          ▼
   ┌───────────────────┐
   │  Load Balancer    │  (HAProxy/Nginx 等)
   │  10.0.0.100:9000  │
   └────────┬──────────┘
            │  (1) 随机或最少连接路由
            ▼
   ┌───────────────┐     ┌───────────────┐
   │ ClickHouse    │     │ ClickHouse    │
   │ Proxy Node    │     │ Data Node 1   │
   │ (Optional)    │     └───────────────┘
   └───────┬───────┘             ▲
           │                      │
           ▼  (2) 按 cluster.xml 路由
   ┌───────────────┐     ┌───────────────┐
   │ ClickHouse    │     │ ClickHouse    │
   │ Data Node 2   │     │ Data Node 3   │
   └───────────────┘     └───────────────┘
  1. 客户端连接到负载均衡器 IP,例如 10.0.0.100:9000
  2. 负载均衡器根据配置将请求转给 Proxy Node(若有)或直接给 Data Node。
  3. Proxy Node(若存在)再根据 cluster.xml 路由到对应的分片与副本。

9. 总结与参考文档

9.1 总结

本文详细介绍了如何在生产环境中构建一个基本的 ClickHouse 集群,内容包括:

  1. 环境准备与依赖安装:选择合适的操作系统,配置端口与防火墙。
  2. ZooKeeper 集群的部署与配置:保证 ClickHouse 副本间一致性的元数据存储。
  3. ClickHouse 节点安装与基础配置:理解 config.xmlusers.xmlmacros.xmlcluster.xml 等配置文件的作用。
  4. 集群级别配置:编写 cluster.xml 定义分片与副本节点,利用 macros.xml 简化配置。
  5. 分布式表引擎(Distributed)实战:先创建本地的 ReplicatedMergeTree 表,再在同库下创建分布式表,将数据分片并行化查询。
  6. 数据导入与查询示例:演示如何通过 CSV 导入测试数据,并使用分布式表进行跨分片聚合查询。
  7. 高可用与负载均衡:借助 ZooKeeper 实现副本自动切换,使用 HAProxy/Nginx 做查询入口的负载均衡。

通过上述步骤,你可以对 ClickHouse 的集群化部署有一个系统的认识,并掌握使用 Distributed 引擎将数据分布到多个节点、并行查询以提高性能的核心技能。

9.2 参考文档

  1. ClickHouse 官方文档
  2. ClickHouse ReplicatedMergeTree 引擎
  3. ClickHouse Distributed 引擎
  4. ZooKeeper 官方文档
  5. HAProxy 官方文档

ClickHouse分布式部署、表创建及数据迁移全攻略

在大数据时代,ClickHouse 以其高性能 OLAP 查询能力和良好的水平扩展性,逐渐成为海量时序与分析场景的首选数据库。要将 ClickHouse 用于生产环境,往往需要部署分布式集群,实现数据的分片与复制,并针对业务场景设计分布式表结构与数据迁移策略。本文将从分布式架构原理出发,结合代码示例与图解,全面介绍如何完成 ClickHouse 分布式集群的部署、表的创建(含复制表与分布式表)、以及数据迁移的多种手段,帮助你快速掌握 ClickHouse 在生产环境的使用要点。


目录

  1. ClickHouse 分布式架构概述
    1.1. 单节点 vs 分布式
    1.2. 分片(Shard)与副本(Replica)
    1.3. ZooKeeper 在分布式中的作用
  2. 环境准备与组件安装
    2.1. 系统与网络要求
    2.2. 安装 ZooKeeper 集群
    2.3. 安装 ClickHouse 节点
  3. 分布式集群部署示例
    3.1. 集群拓扑设计与图解
    3.2. ZooKeeper 配置
    3.3. ClickHouse config.xmlusers.xml 配置
    3.4. 启动 ClickHouse 服务与校验
  4. 分布式表引擎与表创建
    4.1. MergeTree 与 ReplicatedMergeTree 引擎
    4.2. Distributed 引擎原理与实现
    4.3. 本地表与分布式表创建示例
    4.4. 示例:查询分布式表的执行流程图解
  5. 数据写入、查询与负载均衡
    5.1. 写入到 ReplicatedMergeTree 且分片自动路由
    5.2. 分布式表查询流程详解
    5.3. Insert、Select 示例
  6. 数据迁移与同步策略
    6.1. 单机 ClickHouse 到分布式集群迁移
    6.2. MySQL 到 ClickHouse 的迁移示例(使用 Kafka 或 clickhouse-mysql
    6.3. clickhouse-copier 工具使用
    6.4. INSERT SELECT 与外部表引擎同步
    6.5. 实时同步示例:使用 Kafka 引擎 + Materialized View
  7. 运维与监控要点
    7.1. ZooKeeper 集群监控
    7.2. ClickHouse 节点健康检查
    7.3. 分片与副本恢复流程
    7.4. 备份与恢复策略
  8. 常见问题与优化建议
    8.1. 查询慢或分布式 JOIN 性能优化
    8.2. 数据倾斜与分片键设计
    8.3. 磁盘、内存、网络调优
  9. 总结

1. ClickHouse 分布式架构概述

在深入部署细节之前,首先要明确 ClickHouse 在分布式场景下的几大核心概念:分片(Shard)、副本(Replica)、ZooKeeper 元数据管理,以及分布式表(Distributed Engine)与本地表(MergeTree/ReplicatedMergeTree)的配合

1.1 单节点 vs 分布式

  • 单节点部署

    • 典型用于测试、小规模数据或单机分析。
    • 数据存储在本地 MergeTree 或其派生引擎(如 SummingMergeTree、AggregatingMergeTree 等)表中。
    • 缺点:无法横向扩展,无副本冗余,节点宕机即数据不可用。
  • 分布式部署

    • 通过将数据按某种分片策略均匀分布到多个实例(Shard)上,同时为每个 Shard 配置副本(Replica),实现高可用与水平扩展。
    • 查询时,客户端可通过分布式表路由到对应 Shard,或跨 Shard 聚合查询。
    • 核心组件:

      • ClickHouse 节点:负责存储与执行。
      • ZooKeeper:负责存储分布式元数据(表的分片 & 副本信息、DDL 同步)。

1.2 分片(Shard)与副本(Replica)

  • Shard(分片)

    • 将逻辑数据集按分片键(如用户 ID、时间范围或哈希值)均匀切分为多个子集,每个子集部署在不同的节点上。
    • 常见策略:

      • Hash 分片shard_key = cityHash64(user_id) % shard_count
      • 范围分片:根据时间/业务范围拆分。
  • Replica(副本)

    • 每个 Shard 下可部署多个 Replica,保证 Shard 内数据的一致性与高可用。
    • Replica 间基于 ZooKeeper 的复制队列自动同步数据。
    • 在一个 Replica 挂掉时,点击恢复或重启,其他 Replica 可继续提供服务。

图解:多 Shard / 多 Replica 架构示例

               ┌────────────────────────────────────────────────┐
               │               ZooKeeper 集群(3 节点)          │
               │  存储:/clickhouse/tables/{db}.{table}/shardN   │
               └────────────────────────────────────────────────┘
                      │                   │               │
     ┌────────────────┴─────┐     ┌─────────┴────────┐      │
     │ Shard 1              │     │ Shard 2           │      │
     │ ┌─────────┐ ┌───────┐ │     │ ┌─────────┐ ┌──────┐ │      │
     │ │Replica1 │ │Replica2│ │     │ │Replica1 │ │Replica2│ │      │
     │ │ Node A  │ │ Node B │ │     │ │ Node C  │ │ Node D │ │      │
     │ └─────────┘ └───────┘ │     │ └─────────┘ └──────┘ │      │
     └───────────────────────┘     └─────────────────────┘      │
                      │                   │                   │
                      │                   │                   │
                分布式表路由 / 跨 Shard 聚合查询              │
  • Shard 内部:Replica1、Replica2 两个副本互为冗余,Replica1、Replica2 分别部署在不同物理机上,以应对单点故障。
  • 跨 Shard:客户端通过分布式表(Distributed Engine)将查询分发至每个 Shard 下的副本,由 ZooKeeper 协调副本选择。

1.3 ZooKeeper 在分布式中的作用

ClickHouse 的分布式功能依赖 ZooKeeper 来保证以下核心功能:

  1. DDL 同步

    • 所有 Replica 在创建表、修改表结构时通过 ZooKeeper 写入变更路径,确保各节点同步执行 DDL。
  2. 复制队列管理(ReplicatedMergeTree)

    • 每个 Replica 会将本地插入/删除任务写入 ZooKeeper 中对应分片的队列节点,其他 Replica 订阅该队列并拉取任务执行,实现数据复制。
  3. 分布式表元数据

    • Distributed Engine 在 ZooKeeper 中读取集群信息,确定如何将某条 SQL 分发到各个分片。
  4. 副本故障检测与恢复

    • ZooKeeper 记录当前可用 Replica 列表,当某个 Replica 宕机或网络不可达,其他 Replica 会继续提供写入与查询。

ZooKeeper 目录示例(部分)

/clickhouse/
   ├─ tables/
   │    └─ default.hits/            # hits 表对应的节点
   │         ├─ shard1/             # Shard1 下的所有 Replica
   │         │    ├─ leader_election -> 存储当前 leader 信息
   │         │    └─ queue/Replica1  -> 存储 Replica1 的写入操作
   │         └─ shard2/             # Shard2 下
   │              └─ queue/Replica3
   ├─ macros/                       # 宏定义,可在配置中使用
   └─ replication_alter_columns/... # DDL 同步信息

2. 环境准备与组件安装

本文以 Ubuntu 20.04 为示例操作系统,假设即将部署 2 个 Shard,每个 Shard 2 个 Replica,共 4 台 ClickHouse 节点,并使用 3 节点 ZooKeeper 集群保障高可用。

2.1 系统与网络要求

  1. 操作系统

    • 建议使用 Debian/Ubuntu/CentOS 等 Linux 发行版,本文以 Ubuntu 20.04 为例。
  2. 网络连通性

    • 所有节点之间需互相能通:

      ping zk1 zk2 zk3
      ping click1 click2 click3 click4
    • 关闭防火墙或放通必要端口:

      • ZooKeeper:2181(客户端访问)、2888/3888(集群内部选举)。
      • ClickHouse:9000(TCP 协议,默认客户端端口)、8123(HTTP 接口)、9009(Keeper 通信,若启用 Keeper 模式,可忽略)。
  3. 时间同步

    • 建议使用 NTP 或 chrony 保证各节点时间同步,否则会影响 ReplicatedMergeTree 的副本选举与健康检查。

      sudo apt-get install chrony
      sudo systemctl enable chrony
      sudo systemctl start chrony

2.2 安装 ZooKeeper 集群

在 3 台节点(假设 IP 分别为 192.168.1.10/11/12)上完成 ZooKeeper 安装与集群配置。

2.2.1 下载与解压

# 在每台机器执行
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/
ln -s /opt/apache-zookeeper-3.7.1-bin /opt/zookeeper

2.2.2 配置 zoo.cfg

# 编辑 /opt/zookeeper/conf/zoo.cfg (如果目录下无 zoo.cfg 示例,可复制 conf/zoo_sample.cfg)
cat <<EOF > /opt/zookeeper/conf/zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper
clientPort=2181
# 集群内部通信端口(选举与同步)
server.1=192.168.1.10:2888:3888
server.2=192.168.1.11:2888:3888
server.3=192.168.1.12:2888:3888
EOF

2.2.3 创建 dataDir 与 myid

# 在每台机器分别执行
sudo mkdir -p /var/lib/zookeeper
sudo chown $(whoami):$(whoami) /var/lib/zookeeper

# 将编号写入 myid(与 zoo.cfg 中 server.N 对应)
# 机器 192.168.1.10
echo "1" > /var/lib/zookeeper/myid
# 机器 192.168.1.11
echo "2" > /var/lib/zookeeper/myid
# 机器 192.168.1.12
echo "3" > /var/lib/zookeeper/myid

2.2.4 启动 ZooKeeper

# 同步在 3 台节点上启动
/opt/zookeeper/bin/zkServer.sh start
# 检查集群状态
/opt/zookeeper/bin/zkServer.sh status
# 期望输出类似 “Mode: leader” 或 “Mode: follower”

至此,3 节点 ZooKeeper 集群已启动并形成仲裁,可支持多副本 ClickHouse 的元数据管理。

2.3 安装 ClickHouse 节点

在 4 台 ClickHouse 节点(假设 IP 为 192.168.1.20/21/22/23)上,按照以下步骤安装 ClickHouse:

2.3.1 安装 Yandex 官方仓库并安装

# 安装官方 GPG Key
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E0C56BD4
# 添加仓库
echo "deb https://repo.clickhouse.com/deb/stable/ main/" | sudo tee /etc/apt/sources.list.d/clickhouse.list
# 更新并安装
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client

2.3.2 配置防火墙与端口

# 放通 TCP 9000、8123、9009 端口(若使用 CentOS,可用 firewalld 或 iptables)
sudo ufw allow 9000/tcp
sudo ufw allow 8123/tcp
sudo ufw allow 9009/tcp
sudo ufw reload

2.3.3 启动 ClickHouse 服务

sudo systemctl enable clickhouse-server
sudo systemctl start clickhouse-server
# 查看日志,确认正常启动
sudo journalctl -u clickhouse-server -f
注意:此时 ClickHouse 还未配置分布式功能,仅是默认的单节点模式。

3. 分布式集群部署示例

下面以 2 Shard × 2 Replica 为例,演示如何将 4 个 ClickHouse 节点组成分布式集群。假设对应节点如下:

  • Shard1

    • Replica1:192.168.1.20(click1)
    • Replica2:192.168.1.21(click2)
  • Shard2

    • Replica1:192.168.1.22(click3)
    • Replica2:192.168.1.23(click4)

3.1 集群拓扑设计与图解

            ┌────────────────────────────────────────────────┐
            │                   ZooKeeper 3 节点           │
            │   [192.168.1.10, 11, 12] 端口 2181,2888,3888  │
            └────────────────────────────────────────────────┘
                        │              │              │
       ┌────────────────┴──────────────┴──────────────┴───────────────┐
       │                    ClickHouse 分布式集群                       │
       │ Shard1                                  Shard2                 │
       │ ┌───────────┐ ┌───────────┐         ┌───────────┐ ┌───────────┐ │
       │ │ click1    │ │ click2    │         │ click3    │ │ click4    │ │
       │ │ (Replica) │ │ (Replica) │         │ (Replica) │ │ (Replica) │ │
       │ │ zk:2181   │ │ zk:2181   │         │ zk:2181   │ │ zk:2181   │ │
       │ └───────────┘ └───────────┘         └───────────┘ └───────────┘ │
       └───────────────────────────────────────────────────────────────┘
               │                  │              │                  │
               │  ReplicatedMergeTree 本地表 (pathy)  │ Distributed 表 (path) │
               │  数据分片 & 自动复制                 │ 跨 Shard 查询路由     │
  • ZooKeeper:运行在 192.168.1.10/11/12:2181
  • click1/click2:Shard1 的 2 个 Replica,两个节点负责存储 Shard1 的数据,数据通过 ZooKeeper 自动复制。
  • click3/click4:Shard2 的 2 个 Replica,同理。

3.2 ZooKeeper 配置

上文已完成 ZooKeeper 集群搭建,确认集群健康后,ClickHouse 参考以下 ZooKeeper 连接方式即可。

<!-- /etc/clickhouse-server/config.xml (各节点相同,只需保证 zk 配置正确) -->
<yandex>
    <!-- 其他配置省略 -->
    <zookeeper>
        <node>
            <host>192.168.1.10</host>
            <port>2181</port>
        </node>
        <node>
            <host>192.168.1.11</host>
            <port>2181</port>
        </node>
        <node>
            <host>192.168.1.12</host>
            <port>2181</port>
        </node>
    </zookeeper>
    <!-- 更多配置... -->
</yandex>

3.3 ClickHouse config.xmlusers.xml 配置

为了实现 ReplicatedMergeTree 与 Distributed 引擎,需修改以下配置文件。

3.3.1 修改 config.xml

编辑 /etc/clickhouse-server/config.xml,在 <yandex> 节点内添加以下段落:

<yandex>
    <!-- ... 原有配置 ... -->

    <!-- ZooKeeper 节点 (已如上所示) -->
    <zookeeper>
        <node>
            <host>192.168.1.10</host>
            <port>2181</port>
        </node>
        <node>
            <host>192.168.1.11</host>
            <port>2181</port>
        </node>
        <node>
            <host>192.168.1.12</host>
            <port>2181</port>
        </node>
    </zookeeper>

    <!-- 为分布式部署添加 shards 与 replicas 定义 -->
    <remote_servers>
        <!-- 定义一个逻辑集群名 cluster1,包含 2 个 shard -->
        <cluster1>
            <shard>
                <replica>
                    <host>192.168.1.20</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>192.168.1.21</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <replica>
                    <host>192.168.1.22</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>192.168.1.23</host>
                    <port>9000</port>
                </replica>
            </shard>
        </cluster1>
    </remote_servers>

    <!-- 定义默认数据库 macros,方便在 SQL 中使用 {cluster} -->
    <macros>
        <cluster>cluster1</cluster>
        <shard>shard1</shard> <!-- 可留空,主要使用 macros.cluster -->
    </macros>

    <!-- 持久化参数,以及其他可选配置 -->
    <!-- ... -->
</yandex>
  • <remote_servers>

    • 定义逻辑集群名称 cluster1,下有两个 <shard> 节点,每个 <shard> 下有若干 <replica>
    • 在后续创建 Distributed 表时,会引用 cluster1,ClickHouse 自动根据此配置将查询分发到各 shard 下的一个副本。
  • <macros>

    • 定义了 {cluster} 宏,后续 SQL 可直接使用 remote('cluster1', ...){cluster}

修改完成后,重启 ClickHouse 节点以使配置生效:

sudo systemctl restart clickhouse-server

3.3.2 修改 users.xml(可选)

若需为分布式表访问设置白名单,建议修改 /etc/clickhouse-server/users.xml,在相应用户下添加 <networks>

<!-- users.xml 片段 -->
<profiles>
    <default>
        <!-- 其他配置 -->
    </default>
</profiles>

<users>
    <default>
        <password></password>
        <networks>
            <ip>::/0</ip> <!-- 允许任意 IP 访问 -->
        </networks>
        <profile>default</profile>
        <quota>default</quota>
    </default>
</users>

若公司内部有统一授权管理,可为特定用户专门配置分布式访问权限。

3.4 启动 ClickHouse 服务与校验

  1. 重启所有 ClickHouse 节点

    sudo systemctl restart clickhouse-server
  2. 校验 ZooKeeper 连接

    clickhouse-client --query="SELECT * FROM system.zookeeper WHERE path LIKE '/clickhouse/%' LIMIT 5;"
    • 若能正常返回节点信息,则表明 ClickHouse 成功连接到 ZooKeeper。
  3. 校验 remote_servers 配置是否生效
    在任意一台节点上执行:

    clickhouse-client --query="SELECT host_name(), version();"
    # 查看本地信息

    然后执行跨集群的 Hello 查询:

    clickhouse-client --query="SELECT * FROM remote('cluster1', system.one) LIMIT 4;"
    • 该查询会在 cluster1 下的每个 Replica 上执行 SELECT * FROM system.one LIMIT 1,汇总 4 条记录。如果能正常返回 4 条,则表示 remote\_servers 生效。

4. 分布式表引擎与表创建

在完成分布式部署后,需要了解 ClickHouse 提供的几种常见表引擎,并结合分布式场景设计合适的表结构。

4.1 MergeTree 与 ReplicatedMergeTree 引擎

  • MergeTree 系列

    • 最常用的引擎,适用于单机场景或非严格高可用需求。
    • 支持分区(PARTITION BY)、排序键(ORDER BY)、TTL、物化视图等。
    • 示例创建:

      CREATE TABLE default.events_mt (
        dt Date,
        user_id UInt64,
        action String,
        value Float32
      )
      ENGINE = MergeTree()
      PARTITION BY toYYYYMM(dt)
      ORDER BY (user_id, dt);
  • ReplicatedMergeTree 系列

    • 在 MergeTree 基础上,增加了通过 ZooKeeper 实现副本复制与容灾能力。
    • 需要传入两个重要参数:

      1. ZooKeeper 路径:例如 /clickhouse/tables/{database}.{table}/shardN
      2. Replica 名称:在同一 Shard 下需唯一,如 replica1replica2
    • 示例创建(在 Shard1 下的两个 Replica 分别执行):

      CREATE TABLE default.events_shard1_replica1 (
        dt Date,
        user_id UInt64,
        action String,
        value Float32
      )
      ENGINE = ReplicatedMergeTree(
        '/clickhouse/tables/default.events/shard1',  -- ZooKeeper 路径
        'replica1'                                   -- Replica 名称
      )
      PARTITION BY toYYYYMM(dt)
      ORDER BY (user_id, dt);
      
      CREATE TABLE default.events_shard1_replica2 (
        dt Date,
        user_id UInt64,
        action String,
        value Float32
      )
      ENGINE = ReplicatedMergeTree(
        '/clickhouse/tables/default.events/shard1',  -- 与 replica1 相同的路径
        'replica2'
      )
      PARTITION BY toYYYYMM(dt)
      ORDER BY (user_id, dt);
  • Shard2 下分别创建两个 Replica

    CREATE TABLE default.events_shard2_replica1 (
      dt Date,
      user_id UInt64,
      action String,
      value Float32
    )
    ENGINE = ReplicatedMergeTree(
      '/clickhouse/tables/default.events/shard2',
      'replica1'
    )
    PARTITION BY toYYYYMM(dt)
    ORDER BY (user_id, dt);
    
    CREATE TABLE default.events_shard2_replica2 (
      dt Date,
      user_id UInt64,
      action String,
      value Float32
    )
    ENGINE = ReplicatedMergeTree(
      '/clickhouse/tables/default.events/shard2',
      'replica2'
    )
    PARTITION BY toYYYYMM(dt)
    ORDER BY (user_id, dt);

说明

  • ZooKeeper 路径 '/clickhouse/tables/default.events/shard1' 与 Shard 名称保持一致,有助于后续维护。
  • 每个 Shard 下的 Replica 都指定相同的 ZooKeeper 路径,Replica 在同一路径上协调数据复制。

4.2 Distributed 引擎原理与实现

  • Distributed 引擎

    • 提供跨 Shard 的查询路由能力,本质上是一个逻辑视图,将查询分发到建在各 Shard 下的本地表,再在客户端聚合结果。
    • 创建时需要指定:

      1. 集群名称:与 config.xmlremote_servers 配置保持一致,如 cluster1
      2. 数据库和表名:在各 Replica 上实际存在的本地表名(如 default.events_shard1_replica1..._replica2...shard2_replica1...shard2_replica2)。
      3. 分片键(可选):用于将写入分发到某个 Shard,而不是广播到所有 Shard。
    • 示例创建:

      CREATE TABLE default.events_distributed (
        dt Date,
        user_id UInt64,
        action String,
        value Float32
      )
      ENGINE = Distributed(
        'cluster1',    -- 与 config.xml 中 remote_servers 的 <cluster1>
        'default',     -- 数据库名
        'events_local',-- 各 Shard 对应的本地表前缀(需在各节点上创建同名本地表)
        rand()         -- 分片键,可改为 cityHash64(user_id)
      );
    • 由于各 Shard 下的本地表可能使用 ReplicatedMergeTree 并加入了 Replica 后缀,为简化管理,可在各 local 表下创建一个同名别名表 events_local,指向当前 Replica。示例:

      每台节点(click1\~click4)都创建一个同名的本地别名表:

      CREATE TABLE default.events_local AS default.events_shard1_replica1;  -- click1
      CREATE TABLE default.events_local AS default.events_shard1_replica2;  -- click2
      CREATE TABLE default.events_local AS default.events_shard2_replica1;  -- click3
      CREATE TABLE default.events_local AS default.events_shard2_replica2;  -- click4

      这样,在 Distributed 引擎中只需引用 events_local,ClickHouse 会自动查找每个节点上对应的本地表。

4.3 本地表与分布式表创建示例

下面结合 Shard1/Shard2、Replica1/Replica2 全流程示例。

4.3.1 Shard1 Replica1 上创建本地表

-- 点击 click1 (Shard1 Replica1)
CREATE DATABASE IF NOT EXISTS default;

CREATE TABLE default.events_shard1_replica1 (
  dt Date,
  user_id UInt64,
  action String,
  value Float32
)
ENGINE = ReplicatedMergeTree(
  '/clickhouse/tables/default.events/shard1',
  'replica1'
)
PARTITION BY toYYYYMM(dt)
ORDER BY (user_id, dt);

4.3.2 Shard1 Replica2 上创建本地表

-- 点击 click2 (Shard1 Replica2)
CREATE DATABASE IF NOT EXISTS default;

CREATE TABLE default.events_shard1_replica2 (
  dt Date,
  user_id UInt64,
  action String,
  value Float32
)
ENGINE = ReplicatedMergeTree(
  '/clickhouse/tables/default.events/shard1',
  'replica2'
)
PARTITION BY toYYYYMM(dt)
ORDER BY (user_id, dt);

4.3.3 Shard2 Replica1 上创建本地表

-- 点击 click3 (Shard2 Replica1)
CREATE DATABASE IF NOT EXISTS default;

CREATE TABLE default.events_shard2_replica1 (
  dt Date,
  user_id UInt64,
  action String,
  value Float32
)
ENGINE = ReplicatedMergeTree(
  '/clickhouse/tables/default.events/shard2',
  'replica1'
)
PARTITION BY toYYYYMM(dt)
ORDER BY (user_id, dt);

4.3.4 Shard2 Replica2 上创建本地表

-- 点击 click4 (Shard2 Replica2)
CREATE DATABASE IF NOT EXISTS default;

CREATE TABLE default.events_shard2_replica2 (
  dt Date,
  user_id UInt64,
  action String,
  value Float32
)
ENGINE = ReplicatedMergeTree(
  '/clickhouse/tables/default.events/shard2',
  'replica2'
)
PARTITION BY toYYYYMM(dt)
ORDER BY (user_id, dt);

提示:在创建完上述本地表后,可使用以下命令检查副本同步是否正常:

-- 在任意节点执行
SELECT
  database,
  table,
  is_leader,
  queue_size,
  future_parts,
  parts_to_merge,
  last_queue_update,
  last_queue_update_time
FROM system.replicas
WHERE database = 'default' AND table LIKE 'events%';
  • 查看 is_leaderqueue_size 是否为 0,表示副本同步正常;若有积压任务,可等待或手动修复。

4.3.5 在每个节点上创建本地别名表

为了让分布式引擎统一使用同名本地表,建议在每个节点上都创建一个 events_local 别名表,指向上一步创建的 Replica 表。示例如下:

  • click1(Shard1 Replica1)

    CREATE TABLE default.events_local AS default.events_shard1_replica1;
  • click2(Shard1 Replica2)

    CREATE TABLE default.events_local AS default.events_shard1_replica2;
  • click3(Shard2 Replica1)

    CREATE TABLE default.events_local AS default.events_shard2_replica1;
  • click4(Shard2 Replica2)

    CREATE TABLE default.events_local AS default.events_shard2_replica2;
说明:别名表不会在存储目录再新建数据;它只是一个对 ReplicatedMergeTree 本地表的引用(ATTACH TABLE 方式)。如果希望更严格隔离,也可以使用 ATTACH TABLE 语法,但 AS ... 方式足够常见。

4.3.6 创建分布式表

在任意一台节点(建议使用 click1)上执行:

CREATE TABLE default.events_distributed (
  dt Date,
  user_id UInt64,
  action String,
  value Float32
)
ENGINE = Distributed(
  'cluster1',         -- 与 config.xml 中定义的集群名称
  'default',          -- 数据库名
  'events_local',     -- 各节点上本地表别名
  cityHash64(user_id) -- 分片键
);

关键说明

  • cityHash64(user_id):ClickHouse 内置的一种哈希函数,可将 user_id 映射到 [0, 2^64) 区间后再 % shard_count,分散写入到不同的 Shard。
  • 如果不填分片键(如填 rand()''),则 Insert 操作会自动将每条记录广播到所有 Shard。

到此,分布式表与本地 Replica 表的创建已完成。

4.4 示例:查询分布式表的执行流程图解

┌─────────────────────────────────────────────────────────────────────────┐
│                         ClickHouse Client                              │
│   SELECT user_id, count() FROM default.events_distributed GROUP BY user_id  │
└─────────────────────────────────────────────────────────────────────────┘
                             │
                   查询路由到 cluster1
                             │
        ┌────────────────────┴────────────────────┐
        │                                         │
┌───────────────┐                       ┌───────────────┐
│    Shard1     │                       │    Shard2     │
│ (click1/2)    │                       │ (click3/4)    │
│ Distributed   │                       │ Distributed   │
│ Engine Worker │                       │ Engine Worker │
└───────┬───────┘                       └───────┬───────┘
        │      查询对应本地表 events_local             │      查询对应本地表 events_local
        ▼                                         ▼
┌───────────────┐                       ┌───────────────┐
│ Local Table   │                       │ Local Table   │
│ events_local  │                       │ events_local  │
│ (Shard1 Data) │                       │ (Shard2 Data) │
│ ReplicatedMT  │                       │ ReplicatedMT  │
└───────┬───────┘                       └───────┬───────┘
        │                                         │
        │ 执行 group by、count() 本地聚合            │ 执行本地聚合
        │                                         │
        ▼                                         ▼
┌──────────────────┐                     ┌──────────────────┐
│ Partial Results  │                     │ Partial Results  │
│ (user_id, count) │                     │ (user_id, count) │
└──────────┬───────┘                     └──────────┬───────┘
           │                                         │
           │         将部分结果汇总到客户端并进行最终合并         │
           └───────────────┬─────────────────────────────────────┘
                           ▼
                    客户端合并聚合结果
                           │
                           ▼
               返回最终 (user_id, total_count) 列表
  • Shard1/Shard2:分布式表引擎仅充当调度者,真正的计算在各节点本地的 events_local
  • 本地聚合:为了减少网络传输,ClickHouse 默认会先在本地执行 GroupBy、聚合等操作,只有聚合后较小的中间结果通过网络返回再做最终合并。这样能显著提高分布式查询性能。

5. 数据写入、查询与负载均衡

完成表结构创建后,接下来演示如何将数据写入分布式表与查询,以及写入时如何自动分片或广播。

5.1 写入到 ReplicatedMergeTree 且分片自动路由

  • 使用分布式表写入

    • 推荐通过分布式表 events_distributed 写入,ClickHouse 会根据 cityHash64(user_id) % shard_count 自动将数据路由到相应 Shard 的 Replica(随机选择一个可用 Replica 写入)。
    • 示例插入 3 条数据,user\_id 为 1、2、3:

      INSERT INTO default.events_distributed VALUES
      ('2023-09-01', 1, 'click', 10.5),
      ('2023-09-01', 2, 'view', 5.0),
      ('2023-09-01', 3, 'purchase', 100.0);
      • 若 Shard Count=2,那么:

        • 对于 user_id = 1cityHash64(1) % 2 = 1(假设),路由到 Shard2;
        • user_id = 2%2 = 0,写入 Shard1;
        • user_id = 3%2 = 1,写入 Shard2。
  • 写入副本选择

    • Shard 内部多个 Replica 会随机选择一个可写 Replica;若写入的 Replica 挂掉,其他 Replica 会接受写入请求。写入后,Replica 间基于 ZooKeeper 自动同步数据。

5.2 分布式表查询流程详解

  • 查询 events_distributed

    • 当执行 SELECT * FROM events_distributed WHERE user_id = 2; 时,ClickHouse 会根据分片键 cityHash64(2) % 2 计算出目标 Shard(Shard1),并将查询请求发给 Shard1 的一个 Replica。
    • 然后在该 Replica 上查询 events_local(即 Shard1 本地的 ReplicatedMergeTree 表),返回结果。
    • 如果 Query 涉及跨 Shard(如 GROUP BY 或不带 WHERESELECT *),则请求会广播到所有 Shard,每个 Shard 返回部分结果,最后由客户端合并。
  • 分布式聚合与性能

    • 对于大表聚合查询,分布式表引擎会首先在每个 Shard 本地进行“部分聚合(partial aggregation)”,然后再把各 Shard 的部分结果收集到一个节点进行“最终聚合(final aggregation)”,大幅减少网络传输量。

5.3 Insert、Select 示例

  • 批量插入示例

    INSERT INTO default.events_distributed
    SELECT 
      toDate('2023-09-02') AS dt, 
      number AS user_id, 
      'auto' AS action, 
      number * 1.1 AS value
    FROM numbers(100000)  -- 生成 100,000 条测试数据
    WHERE number < 10000; -- 只写入前 10,000 条
  • 查询示例

    -- 查看 Shard1 上的数据量(仅在 Shard1 的 click1 或 click2 节点上执行)
    SELECT count(*) FROM default.events_shard1_replica1;
    SELECT count(*) FROM default.events_shard1_replica2;
    
    -- 查询分布式表中的总数据量
    SELECT count(*) FROM default.events_distributed;
    
    -- 分布式聚合示例
    SELECT user_id, count() AS cnt
    FROM default.events_distributed
    GROUP BY user_id
    ORDER BY cnt DESC
    LIMIT 10;
  • 验证数据一致性
    在 Shard1 Replica1 与 Replica2 上分别查询本地表,确认两者数据同步:

    SELECT count(*) FROM default.events_shard1_replica1;
    SELECT count(*) FROM default.events_shard1_replica2;

6. 数据迁移与同步策略

在实际生产中,经常需要将已有数据迁移到新的分布式 ClickHouse 集群,或与外部数据库(如 MySQL)集成,实现实时或离线数据同步。下面介绍几种常见迁移与同步方案。

6.1 单机 ClickHouse 到分布式集群迁移

假设已有一个单节点 ClickHouse(192.168.1.30),其中有表 default.events_single,需要将其数据迁移到上述分布式集群并保持不间断服务。

6.1.1 在新集群创建同结构的分布式表

  1. 在新集群创建 ReplicatedMergeTree 本地表与 Distributed 表(与前节示例一致)。
  2. 确保 events_distributed 已就绪。

6.1.2 使用 INSERT SELECT 迁移数据

在原单节点上执行以下操作,将数据复制到分布式表(通过 clickhouse-client 连接到分布式集群任一节点即可):

clickhouse-client --host=192.168.1.20 --query="
INSERT INTO default.events_distributed
SELECT * FROM remote('single_host', default, 'events_single')
"
  • 需先在 config.xmlremote_servers 中配置 single_host,以便分布式查询原节点数据。示例配置(在每个新集群节点的 /etc/clickhouse-server/config.xml 添加):

    <remote_servers>
        <single_host_cluster>
            <shard>
                <replica>
                    <host>192.168.1.30</host>
                    <port>9000</port>
                </replica>
            </shard>
        </single_host_cluster>
    </remote_servers>
  • 然后在新集群中执行:

    INSERT INTO default.events_distributed
    SELECT * FROM remote('single_host_cluster', default, 'events_single');
  • 上述操作会将单节点数据分批读取,并插入到分布式表,分布式表会自动分片到各 Shard。在数据量大的情况下,建议拆分范围分批执行,例如按照 dt 范围分区多次执行。

6.1.3 增量同步

在完成初次全量迁移后,可使用 ZooKeeper + Kafka 或持续抓取增量数据进入分布式表,以实现接近实时的迁移。

  • 方案一:Materialized View + Kafka

    • 在原单节点 ClickHouse 上创建一个 Kafka 引擎表,订阅写入事件;
    • 创建一个 Materialized View,将 Kafka 中的数据插入到新集群的分布式表。
  • 方案二:Debezium + Kafka Connect

    • 使用 Debezium 将 MySQL/ClickHouse 的 Binlog 推到 Kafka;
    • ClickHouse 侧使用 Kafka 引擎与 Materialized View 实时消费,插入分布式表。

6.2 MySQL 到 ClickHouse 的迁移示例(使用 Kafka 或 clickhouse-mysql

很多场景需要将 MySQL 中的业务表迁移到 ClickHouse 进行高性能 OLAP 查询。常用方案如下:

6.2.1 使用 Kafka + ClickHouse Kafka 引擎

  1. 在 MySQL 中开启 Binlog,并使用 Kafka Connect + Debezium 将数据写入 Kafka 主题(如 mysql.events)。
  2. 在 ClickHouse 集群上创建 Kafka 引擎表

    CREATE TABLE default.events_kafka (
      `dt` Date,
      `user_id` UInt64,
      `action` String,
      `value` Float32
    ) ENGINE = Kafka SETTINGS
      kafka_broker_list = 'kafka1:9092,kafka2:9092',
      kafka_topic_list = 'mysql.events',
      kafka_group_name = 'ch_consumer_group',
      kafka_format = 'JSONEachRow',
      kafka_num_consumers = 4;
  3. 创建 Materialized View

    • Materialized View 将消费 events_kafka,并将数据插入分布式表:

      CREATE MATERIALIZED VIEW default.events_mv TO default.events_distributed AS
      SELECT
        dt,
        user_id,
        action,
        value
      FROM default.events_kafka;
    • 这样,Kafka 中的新数据会自动被 MV 推送到分布式表,实现实时同步。

6.2.2 使用 clickhouse-mysql 工具

clickhouse-mysql 是社区提供的一个 Python 脚本,可直接将 MySQL 表结构与数据迁移到 ClickHouse。

  1. 安装依赖

    pip install clickhouse-mysql
  2. 执行迁移命令

    clickhouse-mysql --mysql-host mysql_host --mysql-port 3306 --mysql-user root --mysql-password secret \
      --clickhouse-host 192.168.1.20 --clickhouse-port 9000 --clickhouse-user default --clickhouse-password '' \
      --database mydb --table events --clickhouse-database default --clickhouse-table events_distributed
    • 默认会将 MySQL 表自动映射为 ClickHouse 表,如创建合适的 MergeTree 引擎表,再批量插入数据。
    • 对于分布式环境,可先在新集群创建分布式表,再指定 --clickhouse-table 为分布式表,脚本会自动往分布式表写入数据。

6.3 clickhouse-copier 工具使用

clickhouse-copier 是 ClickHouse 社区自带的工具,可在集群内部做分片间或集群间的数据搬迁。

  1. 准备复制任务的配置文件copier_config.xml

    <copy>
      <shard>
        <cluster>cluster1</cluster>
        <replica>click1</replica>
      </shard>
      <shard>
        <cluster>cluster1</cluster>
        <replica>click3</replica>
      </shard>
    
      <tables>
        <table>
          <database>default</database>
          <name>events_local</name>
        </table>
      </tables>
    </copy>
    • 上述示例将指定将 events_local 从 Shard1 的 click1 复制到 Shard2 的 click3,需根据实际场景配置更多 <shard><table>
  2. 执行复制

    clickhouse-copier --config /path/to/copier_config.xml --replication 0
    • --replication 0 表示不做 ReplicatedMergeTree 的基于日志复制,仅做一次全量迁移。
    • 适用于集群扩容、分片重平衡等操作。

6.4 INSERT SELECT 与外部表引擎同步

  • INSERT SELECT

    • 适用于跨集群、跨数据库全量复制:

      INSERT INTO default.events_distributed
      SELECT * FROM default.events_local WHERE dt >= '2023-09-01';
    • 可分批(按日期、ID 范围)多次执行。
  • 外部表引擎

    • ClickHouse 支持通过 MySQL 引擎访问 MySQL 表,如:

      CREATE TABLE mysql_events (
        dt Date,
        user_id UInt64,
        action String,
        value Float32
      )
      ENGINE = MySQL('mysql_host:3306', 'mydb', 'events', 'root', 'secret');
    • 然后可在 ClickHouse 侧做:

      INSERT INTO default.events_distributed
      SELECT * FROM mysql_events;
    • 外部表引擎适合数据量相对较小或批量一次性导入,若是实时增量同步,仍推荐 Kafka + Materialized View。

6.5 实时同步示例:使用 Kafka 引擎 + Materialized View

在 MySQL 侧将 Binlog 推到 Kafka 后,ClickHouse 侧通过 Kafka 引擎表 + MV,实现近实时同步。

  1. MySQL → Kafka

    • 部署 Kafka 集群。
    • 使用 Debezium Connector for MySQL,将 MySQL Binlog 写入 Kafka 主题 mysql.events_binlog
  2. ClickHouse 侧创建 Kafka 表

    CREATE TABLE default.events_binlog_kafka (
      dt Date,
      user_id UInt64,
      action String,
      value Float32
    ) ENGINE = Kafka SETTINGS
      kafka_broker_list = 'k1:9092,k2:9092',
      kafka_topic_list = 'mysql.events_binlog',
      kafka_group_name = 'ch_binlog_consumer',
      kafka_format = 'JSONEachRow',
      kafka_num_consumers = 4;
  3. 创建 Materialized View

    CREATE MATERIALIZED VIEW default.events_binlog_mv TO default.events_distributed AS
    SELECT dt, user_id, action, value
    FROM default.events_binlog_kafka;
    • 当 Kafka 有新消息(INSERT/UPDATE/DELETE)时,MV 自动触发,将数据写入分布式表。
    • 对于 UPDATE/DELETE,可根据具体业务需求将这些操作转化为 ClickHouse 的 MergeTree 修改或 VXIN 等逻辑。

7. 运维与监控要点

在生产环境下,ClickHouse 分布式集群的健壮性和性能调优尤为关键。以下介绍一些常见的运维与监控要点。

7.1 ZooKeeper 集群监控

  • 节点状态检查

    echo ruok | nc 192.168.1.10 2181  # 返回 imok 则正常
    echo stat | nc 192.168.1.10 2181  # 查看节点状态、客户端连接数
  • 集群状态检查

    echo srvr | nc 192.168.1.10 2181
    • 可查看是否有选举 leader、是否存在掉线节点等。
  • 监控指标

7.2 ClickHouse 节点健康检查

  • 系统表

    • system.replication_queue:查看各 Replica 的复制队列积压情况。

      SELECT database, table, is_currently_executing, parts_to_merge, queue_size 
      FROM system.replication_queue;
    • system.mutations:查看表的 mutations(更新/删除)状态。

      SELECT database, table, mutation_id, is_done, parts_to_do, parts_done 
      FROM system.mutations;
    • system.parts:查看数据分区与磁盘占用情况。

      SELECT database, table, partition, name, active, bytes_on_disk 
      FROM system.parts WHERE database='default' AND table LIKE 'events%';
    • system.metrics / system.events:监控 ClickHouse 实时指标,如 Query、Insert 吞吐量,Cache 命中率等。
  • 持续监控

7.3 分片与副本恢复流程

7.3.1 Replica 加入流程

  1. 新增 Replica

    • 在一个 Shard 下新增 Replica,先在 ZooKeeper 对应路径下创建新 Replica 的目录。
    • 在新节点上创建本地表(表结构需与原 Shard 保持一致),并指定新的 Replica 名称。
    • 启动 ClickHouse,该 Replica 会从 ZooKeeper 上的复制队列拉取现有数据,完成全量数据复制。
  2. Shard 扩容(横向扩容)

    • 如果要增加 Shard 数量(比如从 2 个 Shard 扩容到 3 个),则需:

      • 暂停写入,或者使用 clickhouse-copier 做分片重平衡。
      • 在新节点上创建对应的本地 ReplicatedMergeTree 表,指定新的 Shard 路径。
      • 使用 clickhouse-copier 或脚本将已有数据重分布到新的 Shard。

7.3.2 副本修复流程

当某个 Replica 节点发生故障并恢复后,需要让它重新同步数据:

  1. 重启节点,它会检测到 ZooKeeper 上已有的副本信息。
  2. Replica 恢复复制,从 Leader 主动拉取尚未复制的分区文件并恢复。
  3. 检查状态

    SELECT database, table, replica_name, is_leader, queue_size 
    FROM system.replicas WHERE database='default' AND table LIKE 'events%';
    • queue_size=0is_currently_executing=0 表示恢复完成。

7.4 备份与恢复策略

  • 备份工具

    • Altinity ClickHouse Backup:社区推荐备份工具。支持全量/增量备份与恢复。
    • 也可手动使用 clickhouse-client --query="SELECT * FROM table FORMAT Native" 导出,然后再用 clickhouse-client --query="INSERT INTO table FORMAT Native" 导入。
  • ZooKeeper 数据备份

    • 可使用 zkCli.sh 导出关键路径的节点数据,以及定期备份 /var/lib/zookeeper/version-2
  • 恢复流程

    1. 恢复 ZooKeeper 数据,保证 ReplicatedMergeTree 的队列信息完整。
    2. 重启 ClickHouse,Replica 会从 ZooKeeper 获取需要恢复的分区;
    3. 如果只想恢复部分数据,可手动删除对应的本地分区文件,再让 Replica 重新执行复制。

8. 常见问题与优化建议

在 ClickHouse 分布式生产环境中,经常会遇到性能瓶颈、数据倾斜、Shard 节点不均衡等问题。下面总结一些常见问题与优化技巧。

8.1 查询慢或分布式 JOIN 性能优化

  • 避免跨 Shard JOIN

    • ClickHouse 的分布式 JOIN 在多 Shard 场景下需要将数据从一个 Shard 拉取到另一个 Shard 进行 Join,网络 I/O 成本高。建议:

      • 数据预聚合(Denormalization):将需要关联的数据预先合并到同一个表中;
      • 使用物化视图:在本地 MergeTree 表上预先计算好关键信息;
      • 单 Shard 物理表:如果某个表非常小,可把它复制到每个 Shard 上本地 Join。
  • Distributed 聚合优化

    • 对于大规模聚合查询,建议先在本地执行聚合(aggregate_overflow_mode='throw'),再在客户端进行最终合并。
    • 使用 settings max_threads = X, max_memory_usage = Y 控制查询资源消耗。

8.2 数据倾斜与分片键设计

  • 数据倾斜

    • 如果分片键导出的数据在某个 Shard 过多而其他 Shard 较少,导致 Shard1 负载过重,Shards2/3 空闲。
    • 解决方案:

      • 重新设计分片键,例如使用复合键或哈希函数与随机数结合;
      • 动态调整分片策略,使用一致性哈希等更均衡的方案;
      • 扩容 Shard 节点,将热点数据分摊到更多 Shard。

8.3 磁盘、内存、网络调优

  • 磁盘性能

    • 推荐使用 SSD 或 NVMe,至少提供 10,000+ IOPS;
    • ClickHouse 在 Merge 任务、高并发写入时对磁盘 I/O 敏感。可使用 RAID0 多盘并行提升吞吐。
  • 内存配置

    • 设置合理的 max_memory_usage
    • 调整 [max_threads] 来控制并行度,避免 OOM;
    • 若有大量 Map/Join 操作,可考虑开启 [join_use_nulls_for_low_cardinality_keys] 以减少内存占用。
  • 网络带宽与延迟

    • 分布式查询与复制都依赖网络:

      • 使用至少 10Gb/s 以降低跨 Shard 数据传输延迟;
      • 配置 max_distributed_connectionsreceive_timeoutsend_timeout 等参数优化通信。

9. 总结

本文从 ClickHouse 分布式架构原理入手,详细讲解了如何在生产环境下:

  1. 部署 ZooKeeper 高可用集群,并配置 ClickHouse 节点连接;
  2. 设计分布式集群拓扑,实现 Shard 与 Replica 的高可用与负载均衡;
  3. 在各节点创建 ReplicatedMergeTree 本地表,通过 ZooKeeper 管理副本复制;
  4. 使用 Distributed 引擎创建逻辑表,自动实现跨 Shard 路由与分布式聚合;
  5. 演示数据写入与查询流程,并提供批量 Insert、Distributed 聚合等常见操作示例;
  6. 提供多种数据迁移方案,包括单机→分布式迁移、MySQL→ClickHouse 同步、Kafka 实时同步等全流程;
  7. 总结运维与监控要点,探讨 Replica 恢复、Shard 扩容、性能调优等实战经验;
  8. 针对常见问题给出优化建议,如数据倾斜、跨 Shard JOIN 降低网络开销、硬件选型等。

通过本文内容,你可以:

  • 搭建一个稳定的 ClickHouse 分布式集群,实现数据的高可用与水平扩展;
  • 利用 ReplicatedMergeTree 与 Distributed 引擎,灵活构建分布式表结构;
  • 结合 Kafka、Materialized View、clickhouse-copier 等工具,实现多源异构数据迁移与实时同步;
  • 在运维过程中通过系统表与监控手段快速排查问题,保证集群高效运行;
  • 通过合理的分片键与硬件优化,避免数据倾斜与性能瓶颈。
2024-09-09

ClickHouse是一个用于在线分析(OLAP)的列式数据库管理系统(DBMS)。它的主要特点是极高的数据处理速度,可以在几十秒内完成对千万条记录的查询。

ClickHouse的主要特性包括:

  • 列式存储:数据按列存储,适合于某些类型的查询操作。
  • 数据压缩:支持多种数据压缩算法,以减少存储空间。
  • 向量化执行引擎:使用SIMD指令优化代码执行。
  • 索引:适度的索引减少了查询时的seek和IO操作。
  • 多核并行处理:利用多核处理器进行并行处理。
  • SQL支持:支持标准的SQL查询。

安装和使用ClickHouse:

  1. 下载并安装:访问ClickHouse官网下载页面,选择合适的版本并安装。
  2. 启动服务:在安装目录下执行clickhouse-server启动服务。
  3. 连接数据库:使用clickhouse-client连接数据库。

基本SQL操作示例:




-- 创建数据库
CREATE DATABASE IF NOT EXISTS example_db;
 
-- 创建表
CREATE TABLE example_db.example_table (
  EventDate Date,
  EventTime DateTime,
  UserID Int32,
  EventName String,
  EventData String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (EventDate, EventTime, intHash32(UserID))
 
-- 插入数据
INSERT INTO example_db.example_table (EventDate, EventTime, UserID, EventName, EventData) VALUES ('2023-01-01', '2023-01-01 00:00:00', 42, 'event_name', 'event_data');
 
-- 查询数据
SELECT EventDate, count(UserID) FROM example_db.example_table GROUP BY EventDate;

以上是ClickHouse的基本介绍和使用示例。

2024-09-09

为了将PostgreSQL数据同步到ClickHouse,你可以使用外部数据同步工具,例如:

  1. Debezium: 一个分布式平台,用于捕获数据库变更。
  2. Kafka: 一个分布式流平台。
  3. pg\_chameleon: 一个用于PostgreSQL和ClickHouse之间数据同步的工具。

以下是使用pg\_chameleon的一个基本示例:

  1. 安装pg\_chameleon:



# 安装依赖
sudo apt-get install -y postgresql-12-replication
 
# 下载pg_chameleon
git clone https://github.com/2ndquadrant-it/pg_chameleon.git /usr/src/pg_chameleon
 
# 编译安装
cd /usr/src/pg_chameleon
make USE_PGXS=1
make USE_PGXS=1 install
  1. 配置PostgreSQL和ClickHouse:

确保PostgreSQL有复制权限和对应的数据库已经创建好。

  1. 配置pg\_chameleon:

在PostgreSQL中创建一个角色和对应的复制槽,然后在pg\_chameleon中配置连接到PostgreSQL和ClickHouse。

  1. 启动pg\_chameleon:



pg_chameleon -D /path/to/config/directory

请注意,这只是一个基本示例,实际部署时可能需要考虑更多因素,如数据同步的实时性、一致性和安全性等。同时,你需要根据你的系统环境和需求调整安装和配置步骤。

2024-09-06

在ClickHouse中,数据可以通过多种方式导出,包括使用命令行工具、SQL查询以及编程语言接口。以下是一个使用Python clickhouse-driver库从ClickHouse数据库导出数据的示例:

首先,确保你已经安装了clickhouse-driver库:




pip install clickhouse-driver

然后,使用Python代码导出数据:




from clickhouse_driver import Client
 
# 创建ClickHouse客户端连接
client = Client('localhost')
 
# 执行SQL查询并获取数据
result = client.execute('SELECT * FROM your_table')
 
# 将数据导出为CSV文件
with open('output.csv', 'w') as file:
    for row in result:
        file.write(','.join(map(str, row)) + '\n')

在这个例子中,your_table是你希望导出数据的表名,代码将该表的所有数据导出到名为output.csv的文件中。你可以根据需要修改查询和导出的文件路径。

2024-09-06

在ClickHouse中,你可以使用system.query_log表来查看正在执行的SQL语句的信息。这个表记录了所有查询的日志,包括查询开始执行的时间、查询执行的时长、查询的状态等。

以下是一个简单的SQL查询,用于获取当前正在执行的所有SQL语句的概要信息:




SELECT
    query_start_time,
    query,
    read_rows,
    total_rows_approx,
    memory_usage,
    state
FROM system.query_log
WHERE is_initial_query AND state = 'Running'

这个查询会返回以下信息:

  • query_start_time: 查询开始的时间。
  • query: 查询的SQL语句。
  • read_rows: 查询执行过程中读取的行数。
  • total_rows_approx: 查询执行过程中估算的总行数。
  • memory_usage: 查询执行过程中使用的内存量。
  • state: 查询的当前状态(例如,'Running'表示查询正在执行)。

请注意,system.query_log表中的数据是有一定时效性的,数据保留的时间由ClickHouse配置文件中的query_log_max_age配置项决定。如果你需要保留更长时间的查询日志,可以考虑增加这个配置项的值。

此外,根据你的需求,你可能还需要根据其他列(如useraddress等)来筛选或者排序查询结果。

2024-09-06

在实际的生产环境中,我们常常需要将ClickHouse的数据导入到MySQL数据库中。这里提供一种方法,使用ClickHouse的INSERT INTO语句和MySQL的LOAD DATA INFILE语句进行导入。

首先,我们需要在ClickHouse中创建一个导出数据的临时表,假设我们要导出的表名为click_table,并且该表已经存在。




-- 创建临时表
CREATE TEMPORARY TABLE IF NOT EXISTS click_table_tmp AS click_table ENGINE = Log;

然后,我们可以使用INSERT INTO语句将数据导入到临时表中:




-- 清空临时表
DELETE FROM click_table_tmp;
 
-- 从原表插入数据到临时表
INSERT INTO click_table_tmp SELECT * FROM click_table;

接下来,我们需要将临时表中的数据导出到一个CSV文件中,该文件将被MySQL用来导入数据。




-- 导出数据到CSV文件
INSERT INTO TABLE click_table_tmp FORMAT CSVWithNamesAndTypes;

在MySQL中,我们需要创建一个与ClickHouse表结构相同的表,然后使用LOAD DATA INFILE语句将CSV文件导入到MySQL表中。




-- 在MySQL中创建表
CREATE TABLE IF NOT EXISTS mysql_table (
    -- 这里列出所有ClickHouse表中的字段和对应的MySQL数据类型
    column1 INT,
    column2 VARCHAR(255),
    ...
);
 
-- 导入数据
LOAD DATA INFILE '/path/to/click_table.csv'
INTO TABLE mysql_table
FIELDS TERMINATED BY ',' -- 根据CSV文件实际情况调整
ENCLOSED BY '"' -- 如果CSV文件字段被引号包围的话
LINES TERMINATED BY '\n' -- 行结束符,根据操作系统调整
IGNORE 1 LINES; -- 如果CSV文件有标题头,则忽略第一行

请注意,这个过程需要你有足够的权限来执行这些操作,并且在执行这些操作之前需要备份你的数据。此外,CSV文件的路径和格式需要根据你的服务器环境进行相应的调整。

2024-09-06



-- 查询所有分区的大小
SELECT 
    database, 
    table, 
    partition, 
    sum(bytes_on_disk) AS partition_size 
FROM system.parts 
GROUP BY database, table, partition 
ORDER BY partition_size DESC;
 
-- 查询每个表的大小
SELECT 
    database, 
    table, 
    sum(bytes_on_disk) AS table_size 
FROM system.parts 
GROUP BY database, table 
ORDER BY table_size DESC;
 
-- 查询整个集群的大小
SELECT 
    database, 
    sum(bytes_on_disk) AS database_size 
FROM system.parts 
GROUP BY database 
ORDER BY database_size DESC;
 
-- 查询所有节点的CPU和内存使用情况
SELECT 
    hostname, 
    cpu_usage, 
    memory_usage_percent 
FROM system.metrics 
WHERE (event_date >= today() - 1) AND (event_date <= today());
 
-- 查询最慢的查询
SELECT 
    query, 
    round(elapsed, 2) AS duration, 
    type 
FROM system.query_log 
WHERE (type = 'Query' AND event_date >= today() - 1 AND event_date <= today()) 
ORDER BY duration DESC 
LIMIT 10;
 
-- 查询最频繁的查询
SELECT 
    query, 
    count() AS num_executions 
FROM system.query_log 
WHERE (type = 'Query' AND event_date >= today() - 1 AND event_date <= today()) 
GROUP BY query 
ORDER BY num_executions DESC 
LIMIT 10;

这些SQL语句可以帮助你监控ClickHouse的性能,包括分区大小、表大小、集群大小,以及节点的CPU和内存使用情况。同时,它们还提供了最慢查询和最频繁查询的相关信息。这些信息对于调优ClickHouse集群性能至关重要。

2024-09-06

Spring Boot 整合 MyBatis-Plus 和 ClickHouse 的方式主要是配置数据源和定义相应的 Mapper。以下是一个简单的示例:

  1. pom.xml 中添加依赖:



<!-- MyBatis-Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>最新版本</version>
</dependency>
<!-- ClickHouse -->
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>最新版本</version>
</dependency>
  1. 配置 application.propertiesapplication.yml 文件:



# MyBatis-Plus 配置
spring.datasource.url=jdbc:mysql://localhost:3306/yourdb
spring.datasource.username=yourusername
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 
# ClickHouse 配置
clickhouse.db.url=jdbc:clickhouse://localhost:8123
clickhouse.db.user=default
clickhouse.db.password=
  1. 创建 ClickHouse 的配置类:



import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.clickhouse.jdbc.ClickHouseDataSource;
 
@Configuration
public class ClickHouseConfig {
 
    @Value("${clickhouse.db.url}")
    private String clickHouseUrl;
 
    @Value("${clickhouse.db.user}")
    private String clickHouseUser;
 
    @Value("${clickhouse.db.password}")
    private String clickHousePassword;
 
    @Bean(name = "clickHouseDataSource")
    public ClickHouseDataSource clickHouseDataSource() {
        ClickHouseDataSource dataSource = new ClickHouseDataSource();
        dataSource.setUrl(clickHouseUrl);
        dataSource.setUser(clickHouseUser);
        dataSource.setPassword(clickHousePassword);
        return dataSource;
    }
}
  1. 创建 MyBatis-Plus 的 Mapper 接口和对应的 XML 文件:



import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
 
@Mapper
@Repository
public interface YourEntityMapper extends BaseMapper<YourEntity> {
    // 自定义查询方法
}
  1. 创建实体类和对应的服务层:



import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
@Service
public class YourService {
 
    @Autowired
    private YourEntityMapper yourEntityMapper;
 
    // 使用 MyBatis-Plus 提供的方法或自定义方法
}
  1. application.java 中启用 MyBatis-Plus 和 ClickHouse 配置:
2024-09-05

在Spring Boot项目中集成ClickHouse数据库,并使用MyBatis Plus操作ClickHouse,可以通过以下步骤实现:

  1. pom.xml中添加ClickHouse的依赖:



<dependency>
    <groupId>com.clickhouse.jdbc</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>版本号</version>
</dependency>
  1. 配置application.properties或application.yml文件,添加ClickHouse的数据源配置:



# ClickHouse 数据源配置
spring.datasource.clickhouse.driver-class-name=com.clickhouse.jdbc.ClickHouseDriver
spring.datasource.clickhouse.url=jdbc:clickhouse://localhost:8123/数据库名
spring.datasource.clickhouse.username=用户名
spring.datasource.clickhouse.password=密码
  1. 创建配置类,配置MyBatis Plus的数据源:



@Configuration
public class ClickHouseConfig {
 
    @Bean(name = "clickHouseDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
    public DataSource clickHouseDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "clickHouseSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("clickHouseDataSource") DataSource clickHouseDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(clickHouseDataSource);
        return bean.getObject();
    }
 
    @Bean(name = "clickHouseSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("clickHouseSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  1. 使用MyBatis Plus提供的Mapper操作ClickHouse数据库:



public interface YourEntityMapper extends BaseMapper<YourEntity> {
    // 自定义的数据库操作方法
}
  1. 在Service层注入Mapper,进行数据库操作:



@Service
public class YourService {
 
    @Autowired
    private YourEntityMapper yourEntityMapper;
 
    public List<YourEntity> getYourEntityList() {
        return yourEntityMapper.selectList(null);
    }
}

以上步骤可以实现Spring Boot项目中集成ClickHouse数据库,并通过MyBatis Plus操作ClickHouse。如果需要集成多数据源,可以通过Spring的@Primary注解指定主数据源,并为每个数据源创建独立的配置类和SqlSessionFactoryBean实例。