Clickhouse: 从探索到应用
前言
2019年到现在,忙忙碌碌,终于有了一些空闲,开始梳理做的事情,也开始思索职业生涯,记录下一些探索和尝试。前作 使用docker搭建zookeeper集群 和 使用docker搭建clickhouse 实际为本文探索时的记录,如您需要快捷搭建对应的服务,请移步查看。
背景
截止到本文发表的时间,Cloudera CDH 依然是公司技术架构中最繁重的组件,无论是从部署的角度,亦或维护的复杂度上来说。由于在后期维护项目时,占用了过多的人力成本,经过多方探讨,公司下定决心寻找一个在项目中最节省成本的替代品。近两年 Clickhouse 的应用越来越广,呼声也越来越高,于是初步选定使用 Clickhouse 作为替代品,而由于在公司工作的大部分时间忙于在各个产品之间救火,所以对各个产品的业务都有所了解,于是受命开始探索 Clickhouse 在公司落地的可行性。
准备
盲干不是一个合格的探索者,需要要结合公司整体业务情况,确认好研究方向,以及期望输出的调研结果。由于业务属于敏感信息,此处略过,仅做技术交流。
本文的应用场景基本符合 Clickhouse OLAP场景的关键特征 ,如您也对 Clickhouse感兴趣,请结合业务要求和 Clickhouse 数据库的优缺点,来决定是否应该在此问题上做一些尝试。
搭建 zookeeper 集群
搭建 clickhosue 集群
技术验证
此部分内容有较多通过阅读 Clickhouse源码 得到结论,如有谬误,还请指正
深入挖掘 ReplicatedMergeTree
引擎
一句话总结:不适合少量多次写入数据库,需要结合业务来选择合适的批量插入条数和频率。
Clickouse 在执行 INSERT
语句时,会先将数据写入临时文件,同时有任务会自动将临时文件转储到数据目录。请注意每次执行 INSERT
语句,会生成 字段数量 * 2 + 3 个临时文件,每个字段在临时目录中对应一个 字段.bin
和一个 字段.mrk2
文件,同时还有 checksums.txt
、 columns.txt
和 count.txt
三个文件。因此请批量写入数据,否则磁盘寿命将大大降低,同时如果生成的临时文件过多,在转储时,会占用大量的系统资源。因此,建议在数据写入的时效性,和性能之间做个取舍。
字段名称.bin
: 数据文件
字段.mrk2
: 索引文件
checksums.txt
: 数据完整性验证
columns.txt
: 表字段信息
count.txt
: 插入的数据条数
批量写入
单个临时文件的大小默认被限制为 10M
,如果单次插入的数据量比较大,将会被拆分为多个临时文件,因此需要根据实际数据情况选择合适的插入数量。
数据同步
采用 ReplicatedMergeTree
引擎的表,每个replica
节点都会去检测其他节点的数据状态,如果发生数据缺失,会尝试从其他节点恢复,并且每隔 3至30秒
将自身的状态同步至 zookeeper
,否则将开始 数据恢复
,直至数据状态和其他节点一致。
临时文件
由于 Clickhouse 没有事务,所以在临时文件转储时,使用 共享锁
(通过 zokeeper 进行状态交换),临时文件转储任务的优先级低于数据写入任务。每个 replica
节点都会去检测其他节点的数据状态,当状态一致时,才会开始转储临时文件,所以如果单次插入的数据量较小,会积压非常多的临时文件,因此也需要根据数据写入的频率,预留足够的磁盘空间。
写入测试
创建表
-- 创建数据库
CREATE DATABASE IF NOT EXISTS test;
-- 创建表
CREATE TABLE IF NOT EXISTS test.test_table (
ts UInt64 , day Date ,
field_1 Float64 , field_2 Float64 , field_3 Float64 ,
field_4 Float64 , field_5 Float64 , field_6 Float64 ,
field_7 Float64 , field_8 Float64 , field_9 Float64 ,
field_10 Float64 , field_11 String , field_12 String ,
field_13 String , field_14 String , field_15 String ,
field_16 String , field_17 String , field_18 String ,
field_19 String , field_20 String
) ENGINE = ReplicatedMergeTree('/clickhouse/cluster/tables/{shard}/test_table', '{replica}')
PARTITION BY day
ORDER BY (ts, day)
SETTINGS index_granularity = 8192;
插入数据
准备一个 100W
行数据的 test_data.csv
,字段和此前创建的表一致。
导入csv
可以利用 clickhouse-client 导入 csv 数据文件。
请注意 单次 SQL 执行默认有 3600s 超时限制,对应
/etc/clickhouse-server/config.xml
中max_session_timeout
的配置
# test_data.csv 包含表头
# 如文件过大,可以使用 --max_insert_block_size=100000 参数来限制单次插入的大小
clickhouse-client --user=root --password=123456 --query "INSERT INTO test.test_table FORMAT CSVWithNames" < test_data.csv
在 system.query_log
表中记录每次 SQL 执行的记录(此功能默认开启,不建议关闭)
select databases, tables, written_rows, query_duration_ms
from query_log
where query_kind ilike 'INSERT'
order by event_time desc
limit 10;
查询结果如下,从结果中可以看到,插入 100W
条数据,合计耗时 299782ms
彩蛋:插入性能会随磁盘性能和机器配置的提高而大幅提高
┌─databases─┬─tables──────────────┬─written_rows─┬─query_duration_ms─┐
│ ['test'] │ ['test.test_table'] │ 1000000 │ 299782 │
└───────────┴─────────────────────┴──────────────┴───────────────────┘
clickhouse-benchmark
可以使用 clickhouse-benchmark 测试 clickhouse
数据库的性能
请注意:在 SQL 语句中无筛选条件时,Clickhouse 中
count(*)
或count(固定值)
不会扫描数据文件,而是返回对应数据表目录下count.txt
中的值
# 在 SQL 语句中无筛选条件时,返回对应数据表目录下 count.txt 中的值,如下
echo "select count(*) from test.test_table" | clickhouse-benchmark -i 10
# 调整后的 SQL 语句
echo "select count(*) from test.test_table where day = '2021-03-29' " | clickhouse-benchmark -i 10
示例输出如下
Loaded 1 queries.
Queries executed: 10.
localhost:9000, queries 10, QPS: 224.143, RPS: 224.143, MiB/s: 0.877, result RPS: 224.143, result MiB/s: 0.002.
0.000% 0.004 sec.
10.000% 0.004 sec.
20.000% 0.004 sec.
30.000% 0.004 sec.
40.000% 0.004 sec.
50.000% 0.005 sec.
60.000% 0.005 sec.
70.000% 0.005 sec.
80.000% 0.005 sec.
90.000% 0.005 sec.
95.000% 0.006 sec.
99.000% 0.006 sec.
99.900% 0.006 sec.
99.990% 0.006 sec.
负载均衡
可以使用 nginx
实现负载均衡,在 nginx.conf
中添加如下配置,更多关于 upstream
的说明请参考 Module ngx_http_upstream_module
个人认为在 Clickhouse 的应用场景中,使用 轮询模式 来实现负载均衡是一个比较好的选择,可以避免单节点负载较高的场景出现
# 请自行添加 hosts 配置
stream {
upstream clickhouse-9000 {
# 可以根据服务器配置设置不同的权重
server ch-server1:9000 weight=1 max_fails=3 fail_timeout=30s;
server ch-server2:9000 weight=1 max_fails=3 fail_timeout=30s;
server ch-server3:9000 weight=1 max_fails=3 fail_timeout=30s;
server ch-server4:9000 weight=1 max_fails=3 fail_timeout=30s;
}
server{
listen 9000;
server_name localhost;
proxy_pass clickhouse-9000;
}
upstream clickhouse-8123 {
# 可以根据服务器配置设置不同的权重
server ch-server1:8123 weight=1 max_fails=3 fail_timeout=30s;
server ch-server2:8123 weight=1 max_fails=3 fail_timeout=30s;
server ch-server3:8123 weight=1 max_fails=3 fail_timeout=30s;
server ch-server4:8123 weight=1 max_fails=3 fail_timeout=30s;
}
server{
listen 8123;
server_name localhost;
proxy_pass clickhouse-8123;
}
}
总结
彩蛋:使用4台 2Core 8G 云服务器,写入 100W 条数据仅需 3 秒,请多做尝试。
综上,Clickhouse 是一个性能优异的适用于 OLAP场景
的数据库,但其由于无事务机制,在集群模式下采用共享锁来实现数据同步,从而限制了其在集群模式下的应用场景不适合实时写入,在写入时生成的临时文件也是一个不可忽略的细节。