ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

clickhouse概述

2022-03-21 00:03:14  阅读:310  来源: 互联网

标签:分区 granularity 索引 概述 分片 数据 MergeTree clickhouse


前言

随着数据科技的进步,数据分析师早已不再满足于传统的T+1式报表或需要提前设置好维度与指标的OLAP查询。数据分析师更希望使用可以支持任意指标、任意维度并秒级给出反馈的大数据Ad-hoc查询系统。这对大数据技术来说是一项非常大的挑战,传统的大数据查询引擎根本无法做到这一点。由俄罗斯的Yandex公司开源的ClickHouse脱颖而出。在第一届易观OLAP大赛中,在用户行为分析转化漏斗场景里,ClickHouse比Spark快了近10倍。clickhouse之所以快,取决于二个方面

  1. 使用向量引擎计算,对内存中的列式数据,一个batch调用一次SIMD指令(而非每一行调用一次),不仅减少了函数调用次数、降低了cache miss,而且可以充分发挥SIMD指令的并行能力,大幅缩短了计算耗时。向量执行引擎,通常能够带来数倍的性能提升

SIMD全称Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。以同步方式,在同一时间内执行同一条指令。

  1. ClickHouse拥有非常庞大的表引擎体系,截至本书完成时,其共拥有合并树、外部存储、内存、文件、接口和其他6大类20多种表引擎。可以选用各种分析情况

核心引擎MergeTree

在这众多的表引擎中,又属合并树(MergeTree)表引擎及其家族系列(*MergeTree)最为强大,在生产环境的绝大部分场景中,都会使用此系列的表引擎。MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改。为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点,也正是合并树名称的由来。

MergeTree创建方式

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] 
(
 name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], 
 name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2], 
 ... 
) ENGINE = MergeTree()
 ORDER BY expr 
 [PARTITION BY expr] 
 [PRIMARY KEY expr] 
 [SAMPLE BY expr] 
 [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...] 
 [SETTINGS name=value, ... 

这里说明一下MergeTree引擎的主要参数:

  • 必填选项
    1. ENGINE:引擎名字,MergeTree引擎无参数。
    2. ORDER BY:排序键,可以由一列或多列组成,决定了数据以何种方式进行排序,例如ORDER BY(CounterID, EventDate)。 如果没有显示指定PRIMARY KEY,那么将使用ORDER BY作为PRIMARY KEY。通常只指定ORDER BY即可。
  • 选填选项
    1. PARTITION BY:分区键,指明表中的数据以何种规则进行分区。分区是在一个表中通过指定的规则划分而成的逻辑数据集。分区可以按任意标准进行,如按月、按日或按事件类型。为了减少需要操作的数据,每个分区都是分开存储的。
    2. PRIMARY KEY:主键,设置后会按照主键生成一级索引(primary.idx),数据会依据索引的设置进行排序,从而加速查询性能。默认情况下,PRIMARY KEY与ORDER BY设置相同,所以通常情况下直接使用ORDER BY设置来替代主键设置。
    3. SAMPLE BY:数据采样设置,如果显示配置了该选项,那么主键配置中也应该包括此配置。例如 ORDER BY CounterID / EventDate / intHash32(UserID)、SAMPLE BY intHash32(UserID)。
    4. TTL:数据存活时间,可以为某一字段列或者一整张表设置TTL,设置中必须包含Date或DateTime字段类型。如果设置在列上,那么会删除字段中过期的数据。如果设置的是表级的TTL,那么会删除表中过期的数据。如果设置了两种类型,那么按先到期的为准。例如,TTL createtime + INTERVAL 1 DAY,即一天后过期。使用场景包括定期删除数据,或者定期将数据进行归档。
    5. index_granularity:索引间隔粒度。MergeTree索引为稀疏索引,每index_granularity个数据产生一条索引。index_granularity默认设置为8092。
    6. enable_mixed_granularity_parts:是否启动index_granularity_bytes来控制索引粒度大小。
    7. index_granularity_bytes:索引粒度,以字节为单位,默认10Mb
    8. merge_max_block_size:数据块合并最大记录个数,默认8192。
    9. merge_with_ttl_timeout:合并频率最小时间间隔,默认1天。

MergeTree的存储结构

MergeTree表引擎中的数据是拥有物理存储的,数据会按照分区目录的形式保存到磁盘之上,其完整的存储结构如图所示:
在这里插入图片描述
从图中可以看出,一张数据表的完整物理结构分为3个层级,依次是数据表目录、分区目录及各分区下具体的数据文件。接下来就逐一介绍它们的作用。

partition

分区目录,余下各类数据文件(primary.idx、[Column].mrk、[Column]. bin等)都是以分区目录的形式被组织存放的,属于相同分区的数据,最终会被合并到同一个分区目录,而不同分区的数据,永远不会被合并在一起。

CREATE TABLE test.test 
( 
 id        UInt64, 
 type      UInt8, 
 create_time DateTime 
) ENGINE = MergeTree() 
 PARTITION BY toYYYYMMDD(create_time) 
 ORDER BY (id) 
 SETTINGS index_granularity = 4;

insert into test.test(id, type, create_time) VALUES (1, 1, toDateTime('2021-03-01 00:00:00'));

会出现目录名为20210301_8_8_0。
在这里插入图片描述

PartitionID:分区id,例如20210301。
MinBlockNum:最小分区块编号,自增类型,从1开始向上递增。每产生一个新的目录分区就向上递增一个数字。
MaxBlockNum:最大分区块编号,新创建的分区MinBlockNum等于MaxBlockNum的编号。
Level:合并的层级,被合并的次数。合并次数越多,层级值越大。

此分区的分区id为20210301,当前分区的MinBlockNum和MinBlockNum均为8,而level为0,表示此分区没有合并过。

数据分区ID生成规则

数据分区规则由分区ID决定,分区ID由PARTITION BY分区键决定。根据分区键字段类型,ID生成规则可分为:

  1. 未定义分区键:没有定义PARTITION BY,默认生成一个目录名为all的数据分区,所有数据均存放在all目录下。
  2. 整型分区键:分区键为整型,那么直接用该整型值的字符串形式做为分区ID。
  3. 日期类分区键:分区键为日期类型,或者可以转化成日期类型。
  4. 其他类型分区键:String、Float类型等,通过128位的Hash算法取其Hash值作为分区ID。
分区目录的合并过程
  1. MergeTree的分区目录和传统意义上其他数据库有所不同。首先,MergeTree的分区目录并不是在数据表被创建之后就存在的,而是在数据写入过程中被创建的。也就是说如果一张数据表没有任何数据,那么也不会有任何分区目录存在。
  2. 伴随着每一批数据的写入(一次INSERT语句),MergeTree都会生成一批新的分区目录。即便不同批次写入的数据属于相同分区,也会生成不同的分区目录。也就是说,对于同一个分区而言,也会存在多个分区目录的情况
  3. 在之后的某个时刻(写入后的10~15分钟,也可以手动执行optimize查询语句), ClickHouse会通过后台任务再将属于相同分区的多个目录合并成一个新的目录。已经存在的旧分区目录并不会立即被删除,而是在之后的某个时刻通过后台任务被删除(默认8分钟)。

合并目录名称的变化过程如图所示:
在这里插入图片描述

数据分区文件组织结构

目录中的文件主要包括bin文件、mrk文件、primary.idx文件以及其他相关文件

  • bin文件:数据文件,存储的是某一列的数据。数据表中的每一列都对应一个与其字段名相同的bin文件,例如id.bin存储的是表test中id列的数据。
  • mrk文件:标记文件,每一列都对应一个与其字段名相同的标记文件,标记文件在idx索引文件和bin数据文件之间起到了桥梁作用。以mrk2结尾的文件,表示该表启用了自适应索引间隔。
  • primary.idx文件:主键索引文件,用于加快查询效率。
  • count.txt:数据分区中数据总记录数。上述20210301_8_8_0的数据分区中,该文件中的记录总数为1。
  • columns.txt:表中所有列数的信息,包括字段名和字段类型。
  • partion.dat:用于保存分区表达式的值。上述20210301_8_8_0的数据分区中该文件中的值为20210301。
  • minmax_create_time.idx:分区键的最大最小值。
  • checksums.txt:校验文件,用于校验各个文件的正确性。存放各个文件的size以及hash值。
数据文件
  • 数据文件是按数据会事先依照ORDER BY的声明排序
  • 在MergeTree中数据是按列存储的
  • 数据是以压缩数据块的形式被组织并写入.bin文件中的

假设根据一索引查找一行数据id在id.bin的移偏量p,如何确定此行其它列c1的数据?

  1. 可以根据columns获取id列的大小,用现在p去计算出当前行数n。
  2. 可以根据columns获取c1列的大小,再结果行数n,算出当前行对应c1.bin的偏移量,即可获取数据
一级索引

MergeTree的主键使用PRIMARY KEY定义,待主键定义之后,MergeTree会依据index_granularity间隔(默认8192行),为数据表生成一级索引并保存至primary.idx文件内,索引数据按照PRIMARY KEY排序。主键不指定默认为ORDER BY。

ps: ORDER BY = PRIMARY KEY + 其它字段。 当你有些字段仅用于排序,不想用于搜索。来分别设置不同此,以节省空间

如果使用多个主键,例如ORDER BY (CounterID, EventDate),则每间隔8192行可以同时取CounterID与EventDate两列的值作为索引值,具体如图所示。
在这里插入图片描述

数据标记

如果把MergeTree比作一本书,primary.idx一级索引好比这本书的一级章节目录,.bin文件中的数据好比这本书中的文字,那么数据标记(.mrk)会为一级章节目录和具体的文字之间建立关联。
在这里插入图片描述
即各个数据文件xxx.bin,都会根据index_granularity(默认8192行)去生成一个mark,分别记录在xxx.mrk里面。这样子在primary中如果确定了mark,就可以分别在xxx.mrk获取不同列在xxx.bin的偏移量

二级索引

除了一级索引之外,MergeTree同样支持二级索引。二级索引又称跳数索引,由数据的聚合信息构建而成。根据索引类型的不同,其聚合信息的内容也不同。跳数索引的目的与一级索引一样,也是帮助查询时减少数据扫描的范围

如果在建表语句中声明了跳数索引,则会额外生成相应的索引与标记文件(skp_idx_[Column].idx与skp_idx_[Column].mrk). 并理解index_granularity和index_granularity两参数,其规则大致是如下

  • 首先,按照index_granularity粒度间隔将数据划分成n段,总共有[0 ,n-1]个区间(n = total_rows / index_granularity,向上取整)。
  • 接着,根据索引定义时声明的表达式,从0区间开始,依次按index_granularity粒度从数据中获取聚合信息,每次向前移动1步(n+1),聚合信息逐步累加。最后,当移动granularity次区间时,则汇总并生成一行跳数索引数据。

以minmax索引为例,它的聚合信息是在一个index_granularity区间内数据的最小和最大极值。以下图为例,假设index_granularity=8192且granularity=3,则数据会按照index_granularity划分为n等份,MergeTree从第0段分区开始,依次获取聚合信息。当获取到第3个分区时(granularity=3),则汇总并会生成第一行minmax索引(前3段minmax极值汇总后取值为[1 , 9]),如图所示。
在这里插入图片描述

查询过程

数据查询的本质,可以看作一个不断减小数据范围的过程。在最理想的情况下,MergeTree首先可以依次借助分区索引、一级索引和二级索引,将数据扫描范围缩至最小。然后再借助数据标记,将需要解压与计算的数据范围缩至最小。
在这里插入图片描述
如果一条查询语句没有指定任何WHERE条件,或是指定了WHERE条件,但条件没有匹配到任何索引(分区索引、一级索引和二级索引),那么MergeTree就不能预先减小数据范围。在后续进行数据查询时,它会扫描所有分区目录,以及目录内索引段的最大区间。虽然不能减少数据范围,但是MergeTree仍然能够借助数据标记,以多线程的形式同时读取多个压缩数据块,以提升性能

写入过程

下图所示是一张MergeTree表在写入数据时,它的分区目录、索引、标记和压缩数据的生成过程。

  1. 第一步是生成分区目录,伴随着每一批数据的写入,都会生成一个新的分区目录。
  2. 在后续的某一时刻,属于相同分区的目录会依照规则合并到一起;
  3. 接着,按照index_granularity索引粒度,会分别生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件)、每一个列字段的.mrk数据标记和.bin压缩数据文件。
    在这里插入图片描述
    从分区目录201403_1_34_3能够得知,该分区数据共分34批写入,期间发生过3次合并。在数据写入的过程中,依据index_granularity的粒度,从要合并的老区间按批次读取数据(可借助索引),然后依次为每个区间的数据生成索引、标记和压缩数据块。其中,索引和标记区间是对齐的,而标记与压缩块则根据区间数据大小的不同,会生成多对一、一对一和一对多三种关系。

MergeTree 家族

MergeTree(合并树)系列表引擎是ClickHouse核心的存储引擎。其及其扩展提供各种功能
在这里插入图片描述

  • ReplacingMergeTree:在后台数据合并期间,对具有相同排序键的数据进行去重操作。
  • SummingMergeTree:当合并数据时,会把具有相同主键的记录合并为一条记录。根据聚合字段设置,该字段的值为聚合后的汇总值,非聚合字段使用第一条记录的值,聚合字段类型必须为数值类型
  • AggregatingMergeTree:在同一数据分区下,可以将具有相同主键的数据进行聚合。
  • CollapsingMergeTree:在同一数据分区下,对具有相同主键的数据进行折叠合并。
  • VersionedCollapsingMergeTree:基于CollapsingMergeTree引擎,增添了数据版本信息字段配置选项。在数据依据ORDER BY设置对数据进行排序的基础上,如果数据的版本信息列不在排序字段中,那么版本信息会被隐式的作为ORDER BY的最后一列从而影响数据排序。
  • GraphiteMergeTree:用来存储时序数据库Graphites的数据。

架构

ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能;它提供了本地表与分布式表的概念;一张本地表等同于一个数据分片。而分布式表是张逻辑表,本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。当然,也可以在应用层实现数据分发。

ps: ClickHouse的集群是表维度的。

副本

ClickHouse的数据副本一般通过ReplicatedMergeTree复制表系列引擎实现,副本之间借助ZooKeeper实现数据的一致性。

CREATE TABLE repl_test123
(
    `id` String,
    `price` Float64,
    `create_time` DateTime
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/repl_test123','{replica}')
PARTITION BY toYYYYMM(create_time)
ORDER BY id
  • repl_test123 表示要复制的表名
  • {shard} 表示要复制哪分片上的表
  • {replica}表示当前备份表的编号

分片

分片是指将数据拆分,将其分散在不同的实例上的过程。每个分片只负责总数据的一部分,通过一个名为Distributed的引擎进行操作。

CREATE TABLE test_clsuter_all on cluster test_cluster_0_repl
(
    `id` String,
    `price` Float64,
    `create_time` DateTime
)
ENGINE = Distributed('test_cluster_0_repl','default','test_clsuter_a', rand())
  • Distributed(‘集群名’,‘数据库名’,‘表名’, ‘分片键’)
  • test_clsuter_all对外表名

Distributed引擎表,其自身不存储数据,而是作为数据分片的代理,自动路由数据到集群中的各个节点,其中<分片键>参数要求返回整型类型的取值,即按照分片键的规则将数据分布到各个节点,如:

  • userid:按照用户id余数拆分
  • rand():按照随机数拆分
  • intHash64(userid):按照用户id散列值划分

扩容&缩容

  1. 副本缩容:副本节点作为一个独立的节点,直接下线缩容即可。
  2. 分片缩容: 把要下分片的数据写入下,不下分片中
  3. 副本扩容:副本扩容比较简单,只需要在新实例上创建表,修改表结构中的{replica_name},{zk_path}不需要改变。数据会自动通过ZooKeeper来协调获取主信息,从主上下载数据到本地。
  4. 分片扩容:增加分片节点,集群的分片数将增加,这样会让新的分片表和老的分片表的分片数量不一致。因为扩容需要用到新分片,所以需要先新增一个集群,让扩容的分片表可以使用到新的分片节点,然后将旧集群中的数据迁移至新集群,最后删除旧集群的数据与集群配置信息。

常用方案

以四节点实现多分片和双副本为例:
在这里插入图片描述
在每个节点创建一个数据表,作为一个数据分片,使用ReplicatedMergeTree表引擎实现数据副本,而分布表作为数据写入和查询的入口。这是最常见的集群实现方式。如果资源紧缺,也可采用
在这里插入图片描述

物化视图

数据库中的 视图(View) 指的是通过一张或多张表查询出来的 逻辑表 ,本身只是一段 SQL 的封装并 不存储数据。
而 物化视图(Materialized View) 与普通视图不同的地方在于它是一个查询结果的数据库对象(持久化存储),非常趋近于表;

ClickHouse中的物化视图可以挂接在任意引擎的基础表上,而且会自动更新数据,它可以借助 MergeTree 家族引擎(SummingMergeTree、Aggregatingmergetree等),得到一个实时的预聚合,满足快速查询;但是对 更新 与 删除 操作支持并不好,更像是个插入触发器

CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT ...

场景

假设有一个日志表 login_user_log 来记录每次登录的用户信息,现在需要按用户所属地为维度来统计每天的登录次数。正常的聚合SQL如下:city为用户所属地,login_date为登录时间

select city, login_date, count(1) login_cnt
from login_user_log
group by city, login_date

改成物化视图

CREATE TABLE login_user_log_base
(
    city String,
        login_date Date,
    login_cnt UInt32
)
ENGINE = SummingMergeTree()
ORDER BY (city, login_date)

CREATE MATERIALIZED VIEW if not exists login_user_log_mv 
TO login_user_log_base 
AS 
SELECT city, login_date, count(1) login_cnt
from login_user_log
group by city, login_date

使用 TO 关键字关联 物化视图 与 基础表,需要自己初始化历史数据。
在这里插入图片描述

主要参考

官方文档
clickhouse
ClickHouse入门实践–MergeTree原理解析
常见ClickHouse集群部署架构

标签:分区,granularity,索引,概述,分片,数据,MergeTree,clickhouse
来源: https://blog.csdn.net/y3over/article/details/123555323

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有