ClickHouse架构原理-初探

ClickHouse架构原理-初探_第1张图片

文章目录

  • CK设计思路剖析
  • Kylin的弊端
  • ClickHouse
  • CK表引擎
  • ClickHouse MergeTree 引擎工作机制详解

CK设计思路剖析

大数据领域中如果要进行高效率查询核心思想:
设计一种架构,能够快速把待搜寻的数据范围降低到原来的1/n,然后再结合索引或者热点数据放在内存等思路,就能实现高效率的查询了。

数据存储系统的典型操作:

  • 需求1. 根据key找value
  • 需求2. 分组计算

如下:

1、select name from student where id = 1; 
2、select avg(age) from student group by department;

如果:第一种需求多

如果数据量小,并且数据是结构化的,使用 MySQL 去存储即可
如果数据量大,不管是不是结构化的,可以转成 key-value的存储,使用HBase,Cassandra等来解决

如果:第二种需求多

如果数据量小,并且数据是结构化的,使用 MySQL 去存储即可
如果数据量大,不管是不是结构化的,设计一个专门用来做分析的存储系统解决分析的低效率问题

那么一个专门用来做 OLAP 分析的存储引擎该如何设计呢?如何在海量数据中,针对大量数据进行查询分析呢?

需要考虑到以下一些处理方式:

01、列式存储 + 字段类型统一
02、列裁剪
03、数据排序
04、数据分区分片 + 分布式查询
05、预聚合
06、利用CPU特性:向量化引擎,系统必须支持
07、位图索引+布隆索引+主键索引等
08、支持近似计算
09、多样化的存储引擎满足不同场景的特定需要
10、多样化算法选择:Volnitsky高效字符串搜索算法 和 HyperLogLog去重概率算法

总结一下:
单条记录的增删改等操作,通过数据的横向划分,做到数据操作的快速定位,在海量数据查询分析中,一般就是针对某些列做分析,既然并不是全部列,那么把数据做纵向切分把表中的数据按照列来单独存储,那么在做分析的时候,同样可以快速把待查询分析的数据总量降低到原来表的1/n,同样提高效率。

Kylin的弊端

这样的设计背景,其实Kylin考虑的差不多,但也有其使用的弊端:

1、预聚合只支持固定的分析场景,无法满足自定义分析场景(且不太灵活,无法跟上目前快速迭代的数据处理需求),所以预聚合只能作为一种可选方案
2、维度组合爆炸会导致数据膨胀,这样会造成不必要的计算和存储开销。无必要的维度组合的计算就属于浪费资源
3、大概率数据都是增量生成,预聚合不能进行数据更新。所以会产生大量的重算。

ClickHouse

ClickHouse 是一个用于联机分析 (OLAP) 的列式数据库管理系统 (DBMS)。来自于 2011 年在纳斯达克上市的俄罗斯本土搜索引擎企业 Yandex 公司,诞生之初就是为了服务 Yandex 公司自家的 Web 流量分析产品 Yandex.Metrica,后来经过演变,逐渐形成为现在的 ClickHouse,全称是:Click Stream,Data WareHouse
ClickHouse 官网:https://clickhouse.tech/

具有ROLAP、在线实时查询、完整的DBMS功能支持、列式存储、不需要任何数据预处理、支持批量更新、拥有非常完善的SQL支持和函数、支持高可用、不依赖Hadoop复杂生态、开箱即用等许多特点。

在1亿数据集体量的情况下,ClickHouse的平均响应速度是Vertica的2.63倍、InfiniDB的17倍、MonetDB的27倍、Hive的126倍、MySQL的429倍以及Greenplum的10倍(PS: 这仅是官方特定场景下的测试结论)。详细的测试结果可以查阅:https://clickhouse.tech/benchmark/dbms/

ClickHouse非常适用于商业智能领域(也就是我们所说的BI领域),除此之外,它也能够被广泛应用于广告流量、Web、App流量、电信、金融、电子商务、信息安全、网络游戏、物联网等众多其他领域。

ClickHouse 缺点:

1、不支持事务
2、不擅长细粒度或者key-value类型数据的查询需求
3、不擅长大批量按行修改
4、不建议实时流失写入,还是得转化为批量写入
5、运维的成本比较大
6、后期扩展比较麻烦
7、热点的数据,需要我们单独去做缓存,后面还得离线+实时的指标合并起来,也是比较麻烦

CK表引擎

表引擎在 ClickHouse 中的作用十分关键,直接决定了数据如何存储和读取、是否支持并发读写、是否支持 index、支持的 query 种类、是否支持主备复制等。
具体可看官网:https://clickhouse.tech/docs/zh/engines/table-engines/

ClickHouse 的表引擎提供了四个系列(Log、MergeTree、Integration、Special)大约 28 种表引擎。

各有各的用途,比如有 Log 系列用来做小表数据分析,MergeTree 系列用来做大数据量分析,而Integration 系列则多用于外表数据集成。Log、Special、Integration 系列的表引擎相对来说,应用场景有限,功能简单,应用特殊用途,MergeTree 系列表引擎又和两种特殊表引擎(Replicated,
Distributed)正交形成多种具备不同功能的 MergeTree 表引擎。

这是 ClickHouse 的表引擎系列家谱:
ClickHouse架构原理-初探_第2张图片

MergeTree 作为家族中最基础的表引擎,提供了主键索引、数据分区、数据副本和数据采样等基本能力,而家族中其他的表引擎则在 MergeTree 的基础之上各有所长。

ClickHouse 也支持在创建库的时候,指定库引擎,目前支持5种,分别是:
Ordinary,Dictionary, Memory, Lazy, MySQL,其实 Ordinary 是默认库引擎,在此类型库引擎下,可以使用任意类型的表引擎。

ClickHouse MergeTree 引擎工作机制详解

MergeTree 系列是官方主推的存储引擎,支持几乎所有 ClickHouse 核心功能,该系列中,常用的表引擎有:MergeTree、ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree、SummingMergeTree、AggregatingMergeTree 等。学习好MergeTree 表引擎的工作机制,是应用好 ClickHouse 的最基本基础。

关于表引擎类型:

第一:MergeTree 表引擎主要用于海量数据分析,支持数据分区、存储有序、主键索引、稀疏索引、数据TTL等。MergeTree 支持所有ClickHouse SQL 语法,但是有些功能与 MySQL 并不一致,比如在MergeTree 中主键并不用于去重。

第二: 为了解决 MergeTree 相同主键无法去重的问题,ClickHouse 提供了 ReplacingMergeTree 引擎,用来做去重。ReplacingMergeTree 确保数据最终被去重,但是无法保证查询过程中主键不重复。因为相同主键的数据可能被 shard 到不同的节点,但是 compaction 只能在一个节点中进行,而且
optimize 的时机也不确定。

第三: CollapsingMergeTree 引擎要求在建表语句中指定一个标记列 Sign(插入的时候指定为1,删除的时候指定为-1),后台 Compaction 时会将主键相同、Sign 相反的行进行折叠,也即删除。来消除
ReplacingMergeTree 的限制。

第四: 为了解决 CollapsingMergeTree 乱序写入情况下无法正常折叠问题,VersionedCollapsingMergeTree 表引擎在建表语句中新增了一列 Version,用于在乱序情况下记录状态行与取消行的对应关系。主键相同,且 Version 相同、Sign 相反的行,在 Compaction 时会被删除。

第五: ClickHouse 通过 SummingMergeTree 来支持对主键列进行预先聚合。在后台 Compaction时,会将主键相同的多行进行 sum 求和,然后使用一行数据取而代之,从而大幅度降低存储空间占用,提升聚合计算性能。

第六: AggregatingMergeTree 也是预先聚合引擎的一种,用于提升聚合计算的性能。与SummingMergeTree 的区别在于:SummingMergeTree 对非主键列进行 sum 聚合,而AggregatingMergeTree 则可以指定各种聚合函数。

MergeTree 的建表语法:

CREATE TABLE [IF NOT EXISTS] [db_name.]table_name (
	name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
	name2 [type] [DEFAUErEMAMLERLALLIZED|ALIAS expr],
	省略...
) ENGINE = MergeTree()
	[PARTITION BY expr]
	[ORDER BY expr]
	[PRIMARY KEY expr]
	[SAMPLE BY expr]
	[SETTINGS name=value, 省略...]

注意 settings 中的重要参数:

1、index_granularity 默认是 8192
2、index_granularity_bytes 默认 10M,需要通过 enable_mixed_granularity_parts 来开启

关于表分区目录结构:MergeTree 表的分区目录物理结构:

ClickHouse架构原理-初探_第3张图片

关于这些文件的解释:

1、分区目录:20190710_20190711_1_5_1,一个分区可能会有多个不同的目录,该目录下存储该分区的数
据及其他各种形式的数据。后台会执行合并,把相同分区的多个目录合并到一个分区。
2、checksums.txt:校验文件。使用二进制格式存储。它保存了余下各类文件(primary. idx、count.txt等)的size 大小及 size 的哈希值,用于快速校验文件的完整性和正确性。
3、columns.txt:列信息文件,使用明文格式存储。用于保存此数据分区下的列字段信息
4、count.txt:计数文件,使用明文格式存储。用于记录当前数据分区目录下数据的总行数
5、primary.idx:一级索引文件
6、xxx.bin:数据文件,使用压缩格式存储,默认为LZ4压缩格式,用于存储某一列的数据
7、xxx.mrk2:列字段标记文件,如果使用了自适应大小的索引间隔,则标记文件以.mrk2命名,否则以.mrk命名。它建立primary.idx稀疏索引与xxx.bin数据文件之间的映射关系,先通过主键索引找到数据的偏移量,然后去xxx.bin数据文件中找到真实数据
8、... 还有二级索引 和 分区键相关信息文件等等

关于表分区命名规则:分区的命名规则:PartitionID_MinBlockNum_MaxBlockNum_Level
ClickHouse架构原理-初探_第4张图片

该 blocknum 在该表内全局累加,每次创建一个新的分区目录的时候,就会累加1。Level 是分区被合并过的次数计数,合并一次则加1。

ClickHouse架构原理-初探_第5张图片
ClickHouse架构原理-初探_第6张图片

关于一级索引: MergeTree 的主键使用 PRIMARY KEY 定义,待主键定义之后,MergeTree 会依据index_granularity 间隔(默认8192行),为数据表生成一级索引并保存至 primary.idx 文件内。一级索引是稀疏索引,意思就是说:每一段数据生成一条索引记录,而不是每一条数据都生成索引,如果是每一条数据都生成索引,则是稠密索引。稀疏索引的好处,就是少量的索引标记,就能记录大量的数据区间位置信息,比如 不到 24414 条标记信息,就能为 2E 条数据提供索引(算法:200000000 /8192)。在 ClickHouse 中,一级索引常驻内存。总的来说:一级索引和标记文件一一对齐,两个索引标记之间的数据,就是一个数据区间,在数据文件中,这个数据区间的所有数据,生成一个压缩数据块。

关于二级索引: 又称之为跳数索引。目的和一级索引一样,是为了减少待搜寻的数据的范围。跳数索引的默认是关闭的,需要通过 SET allow_experimental_data_skipping_indices = 1 来开启,索引生成粒度由 granularity 控制,如果生成了二级索引,则会在分区目录下生成额外的:
skp_idx_[Column].idx 与 skp_idx_[Column].mrk 文件。跳数索引的生成规则:按照特定规则每隔granularity 个 index_granularity 条数据,就会生成一条跳数索引。比如 minmax 跳数索引,生成的是:granularity 个 index_granularity 条数据内的最大值最小值生成一条索引,如果将来需要针对构建二级索引的这个字段求最大值最小值,则可以帮助提高效率。跳数索引一共支持四种类型:minmax、set、ngrambf_v1 和 tokenbf_v1,一张数据表支持同时声明多个跳数索引。比如:

CREATE TABLE skip_test(
	ID String,
	URL String,
	Code String,
	EventTime Date,
	INDEX a ID TYPE minmax GRANULARITY 5,
	INDEX b (length(ID) * 8) TYPE set(2) GRANULARITY 5,
	INDEX c (ID, Code) TYPE ngrambf_v1(3, 256, 2, O) GRANULARITY 5,
	INDEX d ID TYPE tokenbf_v1(256, 2, 0) GRANULARITY 5
) ENGINE= MergeTree()
order by id;

关于数据压缩: ClickHouse 的数据存储文件 column.bin 中存储是一列的数据,由于一列是相同类型的数据,所以方便高效压缩。在进行压缩的时候,请注意:一个压缩数据块由头信息和压缩数据两部分组
成,头信息固定使用 9 位字节表示,具体由 1 个 UInt8(1字节)整型和 2 个 UInt32(4字节)整型组成,分别代表使用的压缩算法类型、压缩后的数据大小和压缩前的数据大小。每个压缩数据块的体积,按照其压缩前的数据字节大小,都被严格控制在64KB~1MB,其上下限分别由
min_compress_block_size(默认65536)与 max_compress_block_size(默认1048576)参数指定。

具体压缩规则:
原理的说法:每8192条记录,其实就是一条一级索引 一个索引区间 压缩成一个数据块。自适应压缩

1、单个批次数据 size < 64KB:如果单个批次数据小于 64KB,则继续获取下一批数据,直至累积到size>= 64KB时,生成下一个压缩数据块。如果平均每条记录小于8byte,多个数据批次压缩成一个数据块

2、单个批次数据 64KB<= size <=1MB:如果单个批次数据大小恰好在 64KB 与 1MB 之间,则直接生成下一个压缩数据块。

3、单个批次数据 size > 1MB:如果单个批次数据直接超过 1MB,则首先按照 1MB大小截断并生成下一个压缩数据块。剩余数据继续依照上述规则执行。此时,会出现一个批次数据生成多个压缩数据块的情况。如果平均每条记录的大小超过 128byte,则会把当前这一个批次的数据压缩成多个数据块

总结: 在一个 xxx.bin 字段存储文件中,并不是一个压缩块对应到一条一级索引!

总结: 一个 [Column].bin 其实是由一个个的压缩数据块组成的。每个压缩块的大小在:64kb - 1M 之间。

关于数据标记: 数据标记文件也与 .bin 文件一一对应。即每一个列字段 [Column].bin 文件都有一个与之对应的 [Column].mrk2 数据标记文件,用于记录数据在 .bin 文件中的偏移量信息。一行标记数据使用一个元组表示,元组内包含两个整型数值的偏移量信息。它们分别表示在此段数据区间内,在对应的.bin 压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量。每一行标记数据都表示了一个片段的数据(默认8192行)在 .bin 压缩文件中的读取位置信息。标记数据与一级索引数据不同,它并不能常驻内存,而是使用LRU(最近最少使用)缓存策略加快其取用速度。

总结数据读取流程: 先根据一级索引,找到标记文件中的对应数据块信息(压缩块在 .bin 文件中的起始偏移量和,和 未压缩之前该条数据的是偏移量)然后从 .bin 文件中,把压缩块加载到内存,解压缩之后,执行读取。

你可能感兴趣的:(#,Clickhouse,架构,数据库,sql)