Table of Contents generated with DocToc
ClickHouse 的名字由来: ClickHouse 最初的设计目标是为了服务于自家公司的一款名叫 Metrica 流量分析工具。 Metrica 在采集数据的过程中,一次页面点击(Click),就会产生一个事件(Event),就是基于页面的点击事件流(Stream),然后面向数据仓库进行 OLAP 分析。 所以 ClickHouse 的全称是 Click Stream、Data WareHouse,简称 ClickHouse
功能:
- DDL(Data Definition Language数据定义语言):可以动态地创建、修改或者删除数据库、表和视图,而无需重启服务
- DML(Data Manipulation Language数据操作语言):可以动态地查询、插入、修改或删除数据
- DCL(Data Control Language 数据控制语句):可以按照用户粒度设置数据库或者表的操作权限,保障数据的安全性
- 数据备份与恢复:提供了数据备份导出与导入恢复机制,满足生产环境的要求
- 分布式管理:提供集群模式,能够自动管理多个数据库节
缺点:
- 不支持事务
- 不擅长根据主键按行粒度进行查询(虽然支持),所以不应该把 ClickHouse 当做键值对数据库使用
- 不擅长按行删除数据(虽然支持)
- 电信行业用于存储数据和统计数据使用;
- 新浪微博用于用户行为数据记录和分析工作;
- 用于广告网络和RTB,电子商务的用户行为分析;
- 日志分析;
- 检测和遥感信息的挖掘;
- 商业智能;
- 网络游戏以及物联网的数据处理和价值数据分析;
- 最大的应用来自于Yandex的统计分析服务Yandex.Metri ca
列式存储与数据压缩,对于一款高性能数据库来说是必不可少的特性。一个非常流行的观点认为: 如果你想让查询变得更快,最简单且有效的方法就是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以实现上面两点.
首先列式存储,或者说按列存储,相比按行存储,前者可以有效减少查询时需要扫描的数据量,我们可以举个栗子说明一下。 假设一张数据表 A,里面有 50 个字段 A1 ~ A50,如果我们需要查询前 5 个字段的数据的话,那么可以使用如下 SQL 实现:
SELECT A1, A2, A3, A4, A5 from A;
数据库每次都会逐行扫描、并获取每行数据的全部字段,这里就是 50 个,然后再从中返回前 5 个字段。因此不难发现,尽管只需要前 5 个字段,但由于数据是按行进行组织的,实际上还是扫描了所有的字段。 但如果数据是按列进行存储,则不会出现这样的问题,由于数据按列进行组织,数据库可以直接选择 A1 ~ A5 这 5 列的数据并返回,从而避免多余的数据扫描.
如果是按行存储的话,那么假设我们要计算 age 这一列的平均值,就需要一行一行扫描,所以最终会至少扫描 11 个值( 3 + 3 + 3 + 2 )才能找到 age 这一列所存储的 4 个值。 这意味着我们要花费更多的时间等待 IO 完成,而且读完之后还要扔掉很多(因为我们只需要部分字段)。 但如果是按列存储的话,我们只需要获取 age 这一列的连续快,即可得到我们想要的 4 个值,所以这种操作速度更快、效率更高
假设有个字符串 abcdefghi_bcdefghi,现在对它进行压缩,如下所示:
压缩前:abcdefghi_bcdefghi
压缩后:abcdefghi_(9,8)
压缩的本质就是按照一定步长对数据进行匹配扫描,当发现重复部分的时候就会编码转换。 例如上面的 (9, 8),表示从下划线开始向前移动 9 个字节,会匹配到 8 个字节长度的重复项,即 bcdefghi。
尽管真实的压缩算法要比这个复杂许多,但压缩的本质就是如此。数据中的重复项越多,则压缩率越高;压缩率越高,则数据体量越小; 而数据体量越小,在网络中传输的速度则越快,并且对网络带宽和磁盘 IO 的压力也就越小。可怎样的数据最可能具备重复的特性呢? 答案是属于同一个列字段的数据,因为它们具有相同的数据类型和现实语义,重复项的可能性自然就更高。
ClickHouse 就是一款使用列式存储的数据库,数据按列进行组织,属于同一列的数据会被保存在一起,并进行压缩, 而列与列之间的数据也会由不同的文件分别保存(这里主要是指 MergeTree 引擎), 数据默认使用 LZ4 算法压缩,在 Yandex 公司的 Metrica 生产环境中,数据整体的压缩比可以达到 8 比 1(未压缩前 17 PB,压缩后 8 PB)。 另外列式存储除了降低 IO 和存储的压力之外,还为向量化执行做好了铺垫。
为了实现向量化执行,需要利用 CPU 的 SIMD 指令。SIMD 的全称是:Single Instruction Multiple Data,即用单条指令操作多条数据。
现代计算机系统概念中,它是通过数据并行以提高性能的一种实现方式(其它的还有指令级并行和线程级并行),它的原理是在 CPU 寄存器层面实现数据的并行计算。
Clickhouse拥有分布式能力,自然支持数据分片,数据分片是将数据进行横向切分,这是一种在面对海量数据的场景下,解决存储和查询瓶颈的有效手段。 ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。 ClickHouse提供了本地表 ( Local Table ) 与分布式表 ( Distributed Table ) 的概念。 一张本地表等同于一份数据的分片shard。而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
$ cd /tmp
$ mkdir ch_data ch_logs
$ docker run --rm \
-v $(realpath ./ch_data):/var/lib/clickhouse/ \
-v $(realpath ./ch_logs):/var/log/clickhouse-server/ -p 18123:8123 -p19000:9000 \
-e CLICKHOUSE_PASSWORD=admin -e CLICKHOUSE_USER=admin -e CLICKHOUSE_DB=my_database \
--name my-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server:25.1.4
$ docker exec -it my-clickhouse-server clickhouse-client
端口说明: https://clickhouse.com/docs/zh/guides/sre/network-ports
- 2181: ZooKeeper 默认服务端口
- 8123: HTTP 默认端口
- 9000:原生协议端口(也称为 ClickHouse TCP 协议)。 被 ClickHouse 应用程序和进程使用,如 clickhouse-server, clickhouse-client, 和原生 ClickHouse 工具。用于分布式查询的服务器间通信
1)Parser与Interpreter
Parser和Interpreter是非常重要的两组接口:Parser分析器是将sql语句已递归的方式形成AST语法树的形式,并且不同类型的sql都会调用不同的parse实现类。 而Interpreter解释器则负责解释AST,并进一步创建查询的执行管道。Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用, 它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行"业务逻辑" ( 例如分支判断、设置参数、调用接口等 ); 最终返回IBlock对象,以线程的形式建立起一个查询执行管道。
2)表引擎
表引擎是ClickHouse的一个显著特性,上文也有提到,clickhouse有很多种表引擎。不同的表引擎由不同的子类实现。 表引擎是使用IStorage接口的,该接口定义了DDL ( 如ALTER、RENAME、OPTIMIZE和DROP等 ) 、read和write方法,它们分别负责数据的定义、查询与写入。
3)DataType
数据的序列化和反序列化工作由DataType负责。根据不同的数据类型,IDataType接口会有不同的实现类。 DataType虽然会对数据进行正反序列化,但是它不会直接和内存或者磁盘做交互,而是转交给Column和Filed处理。
4)Column与Field
Column和Field是ClickHouse数据最基础的映射单元。作为一款百分之百的列式存储数据库,ClickHouse按列存储数据,内存中的一列数据由一个Column对象表示。 Column对象分为接口和实现两个部分,在IColumn接口对象中,定义了对数据进行各种关系运算的方法,例如插入数据的insertRangeFrom和insertFrom方法、用于分页的cut,以及用于过滤的filter方法等。 而这些方法的具体实现对象则根据数据类型的不同,由相应的对象实现,例如ColumnString、ColumnArray和ColumnTuple等。 在大多数场合,ClickHouse都会以整列的方式操作数据,但凡事也有例外。如果需要操作单个具体的数值 ( 也就是单列中的一行数据 ),则需要使用Field对象,Field对象代表一个单值。 与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式。在Field对象内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。
5)Block
ClickHouse内部的数据操作是面向Block对象进行的,并且采用了流的形式。虽然Column和Filed组成了数据的基本映射单元,但对应到实际操作,它们还缺少了一些必要的信息,比如数据的类型及列的名称。 于是ClickHouse设计了Block对象,Block对象可以看作数据表的子集。Block对象的本质是由数据对象、数据类型和列名称组成的三元组,即Column、DataType及列名称字符串。 Column提供了数据的读取能力,而DataType知道如何正反序列化,所以Block在这些对象的基础之上实现了进一步的抽象和封装,从而简化了整个使用的过程,仅通过Block对象就能完成一系列的数据操作。 在具体的实现过程中,Block并没有直接聚合Column和DataType对象,而是通过ColumnWith TypeAndName对象进行间接引
https://github.com/bitnami/charts/tree/main/bitnami/clickhouse
(⎈|kubeasz-test:clickhouse)➜ cat values.yaml
global:
security:
allowInsecureImages: true
image:
registry: swr.cn-north-4.myhuaweicloud.com
repository: ddn-k8s/docker.io/bitnami/clickhouse
tag: 24.8.4
zookeeper:
enabled: true
image:
registry: swr.cn-north-4.myhuaweicloud.com
repository: ddn-k8s/docker.io/bitnami/zookeeper
tag: 3.9.2-debian-12-r11
podSecurityContext:
enabled: false
containerSecurityContext:
enabled: false
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 2024Mi
shards: 2
replicaCount: 2
(⎈|kubeasz-test:clickhouse)➜ helm install -f values.yaml my-clickhouse oci://registry-1.docker.io/bitnamicharts/clickhouse
(⎈|kubeasz-test:clickhouse)➜ clickhouse helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-clickhouse clickhouse 2 2025-03-31 09:49:31.768305 +0800 CST deployed clickhouse-8.0.8 25.3.2
ZooKeeper 是第一个知名的开源协调系统之一。它是用 Java 实现的,具有相当简单且强大的数据模型。 ZooKeeper 的协调算法 ZooKeeper Atomic Broadcast (ZAB) 不提供读取的线性可化保证,因为每个 ZooKeeper 节点都是本地处理读取请求。 与 ZooKeeper 不同,ClickHouse Keeper 是用 C++ 编写的,并使用 RAFT 算法的实现。该算法允许读取和写入的线性可化,并在不同语言中有若干开源实现。
查看覆盖配置
(⎈|kubeasz-test:clickhouse)➜ ~ kubectl get cm -n clickhouse my-clickhouse -o yaml | yq -r '.data["00_default_overrides.xml"]'
<clickhouse>
<!-- Macros -->
<macros>
<shard from_env="CLICKHOUSE_SHARD_ID"></shard>
<replica from_env="CLICKHOUSE_REPLICA_ID"></replica>
<layer>my-clickhouse</layer>
</macros>
<!-- Log Level -->
<logger>
<level>information</level>
</logger>
<!-- Cluster configuration - Any update of the shards and replicas requires helm upgrade -->
<remote_servers>
<my-clickhouse>
<shard>
<replica>
<host>my-clickhouse-shard0-0.my-clickhouse-headless.clickhouse.svc.cluster.local</host>
<port>9000</port>
<user from_env="CLICKHOUSE_ADMIN_USER"></user>
<password from_env="CLICKHOUSE_ADMIN_PASSWORD"></password>
</replica>
<replica>
<host>my-clickhouse-shard0-1.my-clickhouse-headless.clickhouse.svc.cluster.local</host>
<port>9000</port>
<user from_env="CLICKHOUSE_ADMIN_USER"></user>
<password from_env="CLICKHOUSE_ADMIN_PASSWORD"></password>
</replica>
</shard>
<shard>
<replica>
<host>my-clickhouse-shard1-0.my-clickhouse-headless.clickhouse.svc.cluster.local</host>
<port>9000</port>
<user from_env="CLICKHOUSE_ADMIN_USER"></user>
<password from_env="CLICKHOUSE_ADMIN_PASSWORD"></password>
</replica>
<replica>
<host>my-clickhouse-shard1-1.my-clickhouse-headless.clickhouse.svc.cluster.local</host>
<port>9000</port>
<user from_env="CLICKHOUSE_ADMIN_USER"></user>
<password from_env="CLICKHOUSE_ADMIN_PASSWORD"></password>
</replica>
</shard>
</my-clickhouse>
</remote_servers>
<!-- Zookeeper configuration -->
<zookeeper>
<node>
<host from_env="KEEPER_NODE_0"></host>
<port>2181</port>
</node>
<node>
<host from_env="KEEPER_NODE_1"></host>
<port>2181</port>
</node>
<node>
<host from_env="KEEPER_NODE_2"></host>
<port>2181</port>
</node>
</zookeeper>
<listen_host>0.0.0.0</listen_host>
<listen_host>::</listen_host>
<listen_try>1</listen_try>
</clickhouse>
参数说明
- 集群名字: my-clickhouse
- 宏配置: 宏 shard 和 replica 减少了分布式 DDL 的复杂性。配置的值会自动替代您的 DDL 查询,从而简化您的 DDL。
- shard: 集群的分片(即 ClickHouse 节点),集群会有多个 shard,每个 shard 上都存有全部数据的一部分
- replica: 每个 shard 的副本
- zookeeper: node 表示一个 ZooKeeper 节点,可以设置多个
实际生成配置
# 支持多配置文件管理,主配置文件为 config.xml,默认位于 /etc/clickhouse-server 目录下,其余的配置文件均需包含在 /etc/clickhouse-server/config.d 目录下
$ ls /etc/clickhouse-server
conf.d config.xml keeper_config.xml users.d users.xml
$ ls /etc/clickhouse-server/conf.d/
00_default_overrides.xml
其他常用配置
<!--最大连接数配置-->
<max_connections>4096</max_connections>
<!-- 并发查询数配置-->
<max_concurrent_queries>200</max_concurrent_queries>
以上集群配置完之后,想要用到Clickhouse的集群能力,还需要使用Replicated MergeTree+Distributed引擎,该引擎是"本地表 + 分布式表"的方式,因此可以实现多分片多副本
Clickhouse先在每个 Shard 每个节点上创建本地表(即 Shard 的副本),本地表只在对应节点内可见;
然后再创建分布式表[Distributed],映射到前面创建的本地表。
用户在访问分布式表时,ClickHouse 会自动根据集群架构信息,把请求转发给对应的本地表.
CREATE DATABASE db1 ON CLUSTER default;
-- 使用 MergeTree 表引擎在集群上创建一个表
CREATE TABLE db1.table1 ON CLUSTER default
(
`id` UInt64,
`column1` String
)
ENGINE = MergeTree
ORDER BY id;
INSERT INTO db1.table1 (id, column1) VALUES (1, 'abc');
INSERT INTO db1.table1 (id, column1) VALUES (2, 'def');
-- 创建一个分布式表以查询两个节点的两个分片
CREATE TABLE db1.table1_dist ON CLUSTER default
(
`id` UInt64,
`column1` String
)
ENGINE = Distributed('default', 'db1', 'table1', rand())
SELECT * FROM db1.table1_dist;
1)插入:单机100-150M/s的插入速度;
2)查询:单字段groupby没有索引,1亿数据查询需要2.324s。有索引下,查询时间为0.101秒。可以看到Clickhouse的查询速度是及其快的,市面上常见的数据库基本都达不到这种性能;
3)其他:并发,官网默认配置为100。由于是大数据分析数据库主要适用于olap场景,对并发支持略差多个大数据查询可能会直接将cpu等资源占满,故并发实际达不到100