clickhouse集群搭建实战
OLAP(Online Analytical Processing):联机分析处理,是数据仓库的核心部分,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。例如动态报表系统。
特点:
- 对数据实时性要求不是很高
- 数据量大,因为 OLAP支持的是动态查询,所以用户也需要通过将很多数据的统计后才能得到想要知道的信息,例如时间序列分析等,所以处理的数据量很大
- 重点在于支持决策分析,所以查询一般是动态的,即允许用户随时提出查询的要求
- 行式数据库和列式数据库的区别与优缺点
|
特性 |
行式数据库 |
列式数据库 |
|
数据存储方式 |
按行存储数据(每行是一个数据单元) |
按列存储数据(每列是一个数据单元) |
|
查询性能 |
适合小范围,频繁的读写操作 |
适合大规模数据分析、查询特定列的数据 |
|
事物处理 |
高效支持事物,适合频繁的增删改查 |
不适合频繁更新或插入数据 |
|
数据压缩 |
较差 |
较好(相同数据类型的数据压缩效果更佳) |
|
适用场景 |
OLTP(联机事物处理) |
OLAP(联机分析处理) |
|
IO性能 |
在全表查询时可能较差 |
对列查询优化,减少I/O操作 |
|
常见数据库 |
Mysql、Pg、Sql Server |
Apache Hbase、CK |
总结:列式数据库适用于OLAP场景,数据分析领域,不适合频繁的数据读写。
Clickhouse 服务启动之后一般有如下端口比较常用:
|
8123 |
http default port,web查询和数据操作,适用于API调用 |
|
8443 |
http ssl/tls default port |
|
9000 |
本地协议端口,由ck应用程序使用,例如 clickhouse-client、clickhouse-server,用于分布式查询的服务器间通信 |
|
9004 |
Mysql emulation port |
|
9005 |
Pg emulation port |
|
9009 |
用于低级数据访问的服务器间通信端口,用于数据交换、复制和服务器间通信 |
|
// 拉取镜像 docker pull bitnami/clickhouse:23.3.2 // 启动 clickhosue 测试环境 docker run -itd --name ck-test -p 127.0.0.1:18123:8123 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" bitnami/clickhouse:23.3.2 // 进入到clickhouse 容器 docker exec -it ck-test /bin/bash // 使用 clickhouse-client 连接 clickhosue server ./clickhouse-client --port 9000 --password alben |
|
OPTIMIZE table {TABLE_NAME} final |
用于触发表的手动合并操作,final 表示强制进行表的最终合并,即无论是否符合合并条件,都进行合并,常用于清理碎片或者整理表的状态 |
|
CREATE TABLE my_table ( event_date Date, user_id Int32, event_type String ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_date) -- 按月份分区 ORDER BY (event_date, user_id); |
- 分片:将数据分布到不同的节点(服务器)上,每个节点只能存储数据的一部分,分片通常用于分布式集群中,通过将数据拆分到多个节点上,实现水平扩展。
- 允许将数据分布到多个服务器上,避免单个节点的存储瓶颈
- 通过将查询负载分配到多个节点上,分片可以减少单个节点的压力,提高查询吞吐量
- 扩展性:分片是实现水平扩展的关键,随着数据量的增加,可以通过添加更多的节点来增加处理能力
|
// my_table_local 是在每个节点上存储数据的本地表 // my_table_distributed 是分布式表,它将数据查询请求转发到集群中的不同节点 // my_cluster 集群名称、default 数据库名称、my_table_local 是在每个节点上的本地表名,rand() 是分片的选择方法 // 分布式表本身并不存储数据,而是将查询请求转发到对应的节点上 CREATE TABLE my_table_local ( event_date Date, user_id Int32, event_type String ) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_date) ORDER BY (event_date, user_id); CREATE TABLE my_table_distributed ( event_date Date, user_id Int32, event_type String ) ENGINE = Distributed('my_cluster', 'default', 'my_table_local', rand()); |
- Clickhosue 集群有两种,旧版本的依赖于 zookeeper,最新版本可以使用 clickhosue keeper
|
// 拉取镜像 docker pull bitnami/zookeeper:3.7.1 // 启动容器并将 zookeeper 默认配置拷贝出来 // 在 zookeeper 的数据存储目录下创建 myid 文件,并分别向里面写入 1,2,3 // zookeeper 配置文件内容如下所示:
// 依次启动 zookeeper 的三个节点 docker run -itd -e "ALLOW_ANONYMOUS_LOGIN=yes" --name zk_03 --network=host -v /Users/hongguangyu/Desktop/clickhouse/node03/zookeeper/data:/bitnami/zookeeper/data -v /Users/hongguangyu/Desktop/clickhouse/node03/zookeeper/conf:/opt/bitnami/zookeeper/conf bitnami/zookeeper:3.7.1 // 启动完成之后,查看zookeeper 各个节点的状态如下所示
|
-
-
-
- 搭建 clickhouse 集群
-
-
搭建2分片2副本的集群,总共具有4个ck节点。
- 拉取clickhouse镜像
|
docker pull bitnami/clickhouse:23.3.2 |
- 将clickhouse 配置文件备份出来并移动到外面

修改 config.xml 文件,将末尾的以下配置删掉,因为目前clickhouse镜像服务启动都是使用主机模式,需要手动配置 tcp_port、http_port、interserver_http_port,如果未删除掉,启动的时候会有端口异常报错。

- 在 conf.d 文件夹下新建 metrics.xml 文件,并写入以下内容,需要注意,tcp_port、http_port、interserver_http_port、macros需要根据情况进行配置。
|
<clickhouse> <listen_host>127.0.0.1</listen_host> <tcp_port>19000</tcp_port> <http_port>18123</http_port> <interserver_http_port>19009</interserver_http_port> <remote_servers> <ck_cluster> <!-- 数据分片1 --> <shard> <internal_replication>true</internal_replication> <replica> <user>default</user> <password>alben</password> <host>127.0.0.1</host> <port>9000</port> </replica> <replica> <user>default</user> <password>alben</password> <host>127.0.0.1</host> <port>19000</port> </replica> </shard> <shard> <internal_replication>true</internal_replication> <replica> <user>default</user> <password>alben</password> <host>127.0.0.1</host> <port>29000</port> </replica> <replica> <user>default</user> <password>alben</password> <host>127.0.0.1</host> <port>39000</port> </replica> </shard> </ck_cluster> </remote_servers> <!-- zookeeper 集群信息 --> <zookeeper> <node index="1"> <host>127.0.0.1</host> <port>12181</port> </node> <node index="2"> <host>127.0.0.1</host> <port>22181</port> </node> <node index="3"> <host>127.0.0.1</host> <port>32181</port> </node> </zookeeper>
<networks> <ip>::</ip> </networks> <clickhouse_compression> <case> <min_part_size>10000000000</min_part_size> <min_part_size_ratio>0.01</min_part_size_ratio> <method>lz4</method> </case> </clickhouse_compression> <macros> <shard>1</shard> <replica>2</replica> </macros> </clickhouse> |
- 使用以下命令行分别启动4个clickhouse节点
|
docker run -itd --name ck_01 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" --network=host -v /Users/hongguangyu/Desktop/clickhouse/node01/clickhouse/conf:/opt/bitnami/clickhouse/etc/ bitnami/clickhouse:23.3.2 |
- 分别进入4个clickhouse 节点并执行以下命令
|
docker exec -it ck_01 /bin/bash cd /opt/bitnami/clickhouse/bin/ ./clickhouse-client --password alben --port 39000 |
|
CREATE TABLE default.test_local (id UInt32, name String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test_local', '{replica}') ORDER BY id; CREATE TABLE default.test_distributed AS default.test_local ENGINE = Distributed('my_cluster', default, test_local, rand()); |
- 分别在1、3 节点执行以下表插入命令,可以看到当查询 test_distributed 表时,可以查询到全量的数据,当查询 test_local 时,只能查询到本地的数据,且根据 clickhouse 的remote_servers 配置,2 节点 test_local 的数据和 1 节点test_local 的数据一致,3 节点 test_local 的数据和 4 节点 test_local 的数据一致
|
INSERT INTO default.test_local VALUES (5, 'Alice'), (6, 'Bob'); |

-
- 使用 clickhouse keeper 搭建clickhouse集群
相关配置以及搭建教程参考如下官方文档:
https://clickhouse.com/docs/en/architecture/replication#clickhouse-keeper-01-configuration
- 需要在2.4 操作的基础之上,准备 clickhouse keeper 的配置,其中标红的配置,在3台clickhouse节点上有区别,其他的配置3台节点上保持一致:

|
<clickhouse> <logger> <level>trace</level> <log>/opt/log/clickhouse-keeper/clickhouse-keeper.log</log> <errorlog>/opt/log/clickhouse-keeper/clickhouse-keeper.err.log</errorlog> <size>1000M</size> <count>3</count> </logger> <listen_host>127.0.0.1</listen_host> <keeper_server> <tcp_port>9181</tcp_port> <server_id>1</server_id> <log_storage_path>/opt/lib/clickhouse/coordination/log</log_storage_path> <snapshot_storage_path>/opt/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>trace</raft_logs_level> </coordination_settings> <raft_configuration> <server> <id>1</id> <hostname>127.0.0.1</hostname> <port>9234</port> </server> <server> <id>2</id> <hostname>127.0.0.1</hostname> <port>19234</port> </server> <server> <id>3</id> <hostname>127.0.0.1</hostname> <port>29234</port> </server> </raft_configuration> </keeper_server> </clickhouse> |
- Clickhouse Server 配置中的 zk 配置也需要更改
|
<zookeeper> <!-- where are the ZK nodes --> <node> <host>127.0.0.1</host> <port>9181</port> </node> <node> <host>127.0.0.1</host> <port>19181</port> </node> <node> <host>127.0.0.1</host> <port>29181</port> </node> </zookeeper> |
- 分别启动以下三台ck容器
|
docker run -itd --name ck_01 -e "CLICKHOUSE_ADMIN_PASSWORD=alben" --network=host -v /Users/hongguangyu/Desktop/clickhouse/keepernode01/clickhouse/conf:/opt/bitnami/clickhouse/etc/ bitnami/clickhouse:23.3.2 |
- 在容器中,执行以下命令启动 clickhouse-keeper
|
cd /opt/bitnami/clickhouse/bin/ ./clickhouse-keeper -C /opt/bitnami/clickhouse/etc/keeper/keeper.xml --daemon |
- CK 集群验证,在ck_01 节点上执行以下命令,可以发现ck_02 节点上也创建了 db1 数据库,table1表,并也在表中插入了以下数据:
|
CREATE DATABASE db2 ON CLUSTER ck_cluster CREATE TABLE db2.table1 ON CLUSTER ck_cluster ( `id` UInt64, `column1` String ) ENGINE = ReplicatedMergeTree ORDER BY id INSERT INTO db2.table1 (id, column1) VALUES (1, 'abc'); |
- 当停止多于两台节点的 clickhouse-keeper 后,再执行insert 命令报错如下:

- select * from system.clusters,可以发现集群状态如下所示:

MergeTree 引擎是 Clickhouse 最常用的表引擎,特别适用于大规模的数据存储和分析。可以通过以下方式使用,是MergeTree家族的基础表引擎:
|
CREATE TABLE my_table ( id UInt64, timestamp DateTime, value Float64 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(timestamp) ORDER BY (id, timestamp) PRIMARY KEY id TTL timestamp + INTERVAL 1 YEAR DELETE; |
以上语句执行完毕之后,可以在 /bitnami/clickhouse/data/metadata/ 目录下发现一个test文件夹,内容如下:

数据存储:
- 列式存储:数据按列存储,每一列单独存储在一个文件中,有利于压缩和查询性能
- 分区:数据可以根据指定的列(如日期)进行分区,每个分区可以独立管理和查询
- 排序:数据在写入时按指定的排序键进行排序,这有助于提高性能,特别是范围查询和聚合
数据写入:
- 批量写入:数据通常以批量的方式写入,每个批次的数据会生成一个新的数据块
- 合并:数据块会定期进行合并,以减少数据文件的数量,提高查询性能,合并过程中会删除重复的数据,进行数据压缩
数据读取:
- 索引:clickhouse 使用稀疏索引来加速查询,稀疏索引记录了每个数据块的最小值和最大值,查询时可以跳过不相关的数据块
- 预读:clickhouse 会预读数据块,以提高读取性能
数据保留:
- TTL:可以设置数据保留策略,数据超过指定的时间后会被自动删除
创建 MergeTree 时有如下参数:
|
ENGINE |
引擎名称和参数 |
|
ORDER BY |
排序键 |
|
PARTITION BY |
分区键,大多数情况下,不需要分区键,如果确实需要分区,通常不需要比按月更细粒度的分区,按月分区,使用 toYYYYMM(data_column) |
|
PRIMARY KEY |
主键 |
|
TTL |
数据过期时间 |
MergeTree虽然有主键,但是不能对相同主键的数据进行去重。Clickhouse提供了ReplacingMergeTree 引擎用于去重,能够在合并分区时删除重复的数据,删除时保留最新的数据。
测试方法:
|
// 创建 MergeTree 引擎的表 CREATE TABLE my_table ( `id` UInt64, `timestamp` DateTime, `value` Float64 ) ENGINE = MergeTree PARTITION BY toYYYYMM(timestamp) PRIMARY KEY id ORDER BY (id, timestamp) TTL timestamp + toIntervalYear(1 // 插入两条主键一致的数据 insert into my_table(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2); insert into my_table(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2); // 手动触发数据合并操作 OPTIMIZE table my_table final; |
|
// 创建 ReplacingMergeTree 引擎的表 CREATE TABLE my_table1 ( `id` UInt64, `timestamp` DateTime, `value` Float64 ) ENGINE = ReplacingMergeTree PARTITION BY toYYYYMM(timestamp) PRIMARY KEY id ORDER BY (id, timestamp) TTL timestamp + toIntervalYear(1) // 插入两条主键一致的数据 insert into my_table1(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2); insert into my_table1(id, timestamp, value) values(1, '2024-10-10 11:11:11', 1.2); // 手动触发数据合并操作 OPTIMIZE table my_table1 final;
|
主要用于支持数据的高可用性和容错性,特别是在集群环境中。允许表在多个节点之间进行数据的复制,这样即使一个节点发生故障,其他节点上仍然有数据的副本,从而保证系统的高可用性。
- 每个 ReplicatedMergeTree 表都有一个或多个副本,副本会自动同步数据,当一个副本不可用时,ck 会从其他副本读取数据,保证查询不会中断。
- ReplicatedMergeTree 引擎本身并不直接通过表的定义来设置副本的数量,副本的数量是在集群部署时通过配置和集群架构来管理的
|
CREATE TABLE my_table ( event_date Date, user_id Int32, event_type String ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/my_table', '{replica}') PARTITION BY toYYYYMM(event_date) ORDER BY (event_date, user_id); |
- 在创建 ReplicatedMergeTree 时有两种方式:
- 第一种:在任意一个副本节点上执行以下命令,会在所有的副本上创建table1
|
CREATE TABLE db2.table1 ON CLUSTER ck_cluster ( `id` UInt64, `column1` String ) ENGINE = ReplicatedMergeTree ORDER BY id |
-
- 第二种:在所有的副本节点上都执行以下命令
|
CREATE TABLE default.test_local (id UInt32, name String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/test_local', '{replica}') ORDER BY id; |
需要注意,以上两种方式创建的复制表有区别,第一种貌似并没有和insert_quorum配置强关联(3个节点的情况下,最少需要两个节点正常才能写入数据,2个节点的情况下,必须两个节点都正常才能写入数据,即使insert_quorum设置为1),第二个需要满足成功写入副本的数量大于 insert_quorum配置,即设置insert_quorum 为1时,3副本集群,一个副本正常也能正常写入。
ReplicatedMergeTree 和 Distributed 表引擎相互结合可以组成集群级别的高可用性。
|
CREATE TABLE my_table_local ( event_date Date, user_id Int32, event_type String ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/my_table', '{replica}') PARTITION BY toYYYYMM(event_date) ORDER BY (event_date, user_id); CREATE TABLE my_table_distributed ( event_date Date, user_id Int32, event_type String ) ENGINE = Distributed('my_cluster', 'default', 'my_table_local', rand()); |
Clickhouse 中只有使用了 ReplicatedMergeTree 复制表系列引擎,才能应用副本的能力。

ReplicatedMergeTree 需要依靠zk 的事件监听机制以实现各个副本之间的协同,所以在每张ReplicatedMergeTree 表的创建过程中,它会以 zk_path 为根路径,在 zookeeper 中为这张表创建一组监听节点。按照作用的不同,监听节点可以大致分为如下几类:
- 元数据:
- /metadata:保存元数据信息,包括主键、分区键、采样表达式
- /columns:保存列字段信息,包括列名称和数据类型
- /replicas:保存副本名称,对应设置参数中的 replica_name
- 判断标识:
- /leader_election:用于主副本的选举工作,主副本会主导 MERGE 和 MUTATION 操作(Alter Delete、Alter Update)。这些任务在主副本完成之后再借助zookeeper将消息事件分发至其他副本
- /blocks:记录 blocks 数据块的hash信息摘要,以及对应的partition_id。通过hash 摘要能够判断blocks数据块是否重复
- /quorum:记录 quorum 的数量,当至少有 quorum 数量的副本写入成功后,整个写操作才算成功
- 操作日志:
- /log:常规操作日志(insert、merge、drop partition)它是整个工作机制中最为重要的一环,保存了副本需要执行的任务指令,log 使用了 zk 的持久顺序节点,每条指令的名称以 log- 为前缀递增,每一个副本实例都会监听 /log 节点,当有新的指令加入时,它们会把指令加入副本各自的任务队列,并执行任务
- /replicas/{replica_name}:每个副本各自节点下的一组监听节点,用于指导副本在本地执行具体的任务指令:
- /queue:任务队列节点,用于执行具体的操作任务,当副本从/log 或者 /mutations 节点监听到操作指令时,会讲执行任务添加到该节点下,并基于队列执行
- /log_pointer:log 日志指针节点,记录了最后一次执行的log日志下标信息
- /mutation_pointer:mutation 日志指针节点,记录了最后一次执行的mutations日志名称,例如mutation_pointer

如果配置了 insert_quorum 参数,并且 insert_quorum >= 2 ,则 linux121 会进一步监控已完成写入操作的副本个数,只有当写入副本个数大于或等于 insert_quorum 时,整个写入操作才算成功。否则会报错:

需要注意 insert_quorum 配置在 users.xml 里面:

参考文档:
更多推荐






所有评论(0)