第一章 OLAP场景的关键特征

  1. 绝大多数是读请求
  2. 数据一相当大的批次(>1000行)更新,而不是单行更新;或者没有更新。
  3. 已添加的数据不能修改
  4. 对于读取,从数据库中提取相当多的行,但只提起列的一小部分
  5. 宽表,即每个表包含大量的列
  6. 查询相对较少(通常每台服务器每秒查询数百次或更少)
  7. 对于简单查询,允许延迟大约50毫秒
  8. 列中的数据相对较小,数字和短字符串
  9. 处理单个查询是需要高吞吐量
  10. 事务不是必须的
  11. 对数据一致性要求低
  12. 每个查询有一个大表,除了它之外,其他的都很小
  13. 查询结果明显小于元数据。

    第二章 ClickHouse 架构

    2.1 Clickhouse 概述

    ClickHouse是MPP架构的列式数据库。

    2.1.1 完备的DBMS功能

    DDL(数据定义语言)、DML(数据操作语言)、权限控制、数据备份与恢复、分布式管理。

    2.1.2列式存储于数据压缩

    ck是一款使用列式存储的数据库,数据按列进行组织,属于同一列数据会被保存在一起,列与列之间由不同的文件分别储存(主要是指MergeTree表引擎),数据默认采用LZ4算法压缩。列式存储错了降低IO和储存的压力之外,还未向量化做好铺垫。

    2.1.3向量化执行引擎

    硬件层面的优化是最直接、最高效的提升性能提升的途径。向量化执行就是这种方式的典型代表,是寄存器硬件层面的特性,为上层应用程序的性能带来指数级的提升。
    实现向量化执行,需要利用CPU的SIMD指令,即用单条指令操作多条数据,通过数据并行以提高性能的一种实现方式(还有指令级并行和线程并行),原理是在CPU寄存器层面实现数据的并行操作。

    2.1.4 关系型模型与SQL查询

    提供标准化协议查询接口,使得现有的第三方分析可视化系统可以轻松与它集成对接。

    2.1.5 多样化的表引擎

    Clickhouse将储存部分进行抽象,把储存引擎作为一层独立接口。

    2.1.6 多线程与分布式

    SIMD不适合带有较多判断的场景,因此ck使用了多线程技术实现提速,和向量化执行互补。

    2.1.7 多主架构

    可以避免单点故障问题。

    2.1.8 在线查询

    2.1.9 数据分片与分布式查询

    数据分片是将数据进行横向切分,解决存储和查询瓶颈的有效手段。ck支持分片,而分片则依赖集群。每个集群由1到多个分片组成,而每个分片对应了ck的一个服务节点。分片的数据上限取决于节点数量(一个分片只能对应于1个服务节点)。
    ck并没有高度自动化分片功能,ck提供了本地表(Local Table)与分布式表(Distributed Table)的概念。一张本地表等同于一份数据分片。分布式表不存储任何数据,是本地表的访问代理,类似分库中间件,借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。

    2.2 clickhouse架构设计

图片.png

2.2.1 Column 与Field

column和Field是clickhouse的数据的最基础映射单元。ck按列存储,内存中的一列数据由一个Column对象表示。Colum对象分为接口和实现两个部分,在ICloum接口对象中,定义了对数据进行各种关系运算的方法。在大多数情况下,ck都会以整列的方式操作数据,但如果需要操作行里面的单个具体数值,则需要flied对象。Flield对象使用了聚合设计模式,在field对象内部聚合了Null、UInt64、String、Array等13中数据类型以及相应的处理逻辑。

2.2.2 DataType

数据的序列化与反序列化有DataType负责。IDataType接口定义了许多正反序列化的方法,他们成对出现。IDataType也使用了泛化的设计模式,具体实现方法的实现逻辑由对应的数据类型的实例承载,eg:DataTypeArray,DataTypeString等。
DataType负责序列化等相关工作,但不直接负责数据的读取,而是转由从Columan和Field对象获取。

2.2.3 Block 与 Block 流

CK内部的数据操作是面向Block对象进行的,并且采用了流的形式。虽然Column和filed组成了最基本的映射单元,但对于到实际操作,还缺少了一些必要的信息,eg:数据类型和列的名称。所以有了Blick对象,Blick可以看做数据的子集,该对象的本质是由数据对象、数据类型和列名称组成的三元组,记Column、DataType以及列的名称字符串。

2.2.4 Table

在数据库底层设计中并没有所谓的table对象,它直接使用IStorage接口指代数据表。IStorage接口定义了DDL、read、write方法,他们负责数据的定义与查询写入。在数据查询时,IStorage负责根据AST查询语句的知识要求,返回指定列原始数据。后续对数据的进一步加工、计算和过滤,则会统一交由Interpreter解释器对象处理。

2.2.5 Parser 与Interpreter

Parser与Interpreter是非常重要的两组接口。Parser分析其负责创建AST对象;而Interpreter解释器负责解释AST,并进一步创建查询的数据管道。与IStorage一起,串联起整个数据查询的过程。
Parser 分析其可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句经过Parser实现类解析。
interpreter解析器的作用就像Service服务层一样,祈祷串联整个查询过程作用,它会根据解析器的类型,聚合它所需要的资源。首选会解析AST对象;然后执行“业务逻辑”(分支、设置参数、调用接口);最终返回IBlock对象,以线程的形式建立一个查询执行管道。

2.2.6 Functions 与Aggregate Functions

CK主要提供两类函数——普通函数和聚合函数。普通函数由IFunction接口定义,拥有数十种函数实现,例如FunctionFormatDateTime、FunctionSubstring等。聚合函数由IAggregateFunction接口定义,相比无状态的普通函数,聚合函数是有状态的,以Count聚合为例,其AggregateFunctionCount的状态使用UInt64记录。聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。

2.2.7 Cluster 与 Replication

clickhouse的集群由分片(Shard)组成,而每个分片又通过副本(Replica)组成。

  1. ClickHouse的一个节点只能拥有一个分片,也就是说实现1分片、1副本,至少需要步数2个服务节点。
  2. 分片只是一个逻辑概念,其物理承载还是由副本承担的。

    第三章 数据定义

    3.1 定义数据表(DDL操作以及定义数据的方法)

    3.1.1 数据库

    1. create database if not exists db_name [ENGINE = engine]
  • Ordinary:默认引擎,在大多数下使用默认引擎,在此数据库下使用任意类型的表引擎
  • Dictionary:字典引擎,此数据库会自动为所有数据字典创建它们的数据表。
  • Memory:内存引擎,拥有存放临时数据,此类数据库下的表智慧停留在内存中,不涉及任何磁盘操作,当服务重启后会自动清除。
  • Lazy:日志引擎,此类数据库下只能使用Log系列表引擎。
  • Mysql:Mysql引擎,此类数据库下面的表会自动拉取Mysql中的数据,并为它们建立mysql表引擎的数据表。

默认数据库的实质是物理磁盘上的一个文件目录,在执行语句执行之后,clickhouse便会在安装路径下创 db_name的文件目录。与此同时,在metadata路径下回一同创建用于恢复数据库的 .sql文件。

3.1.2 数据表

clickhouse 提供了3种最基本的建表方法:

  1. -- 第一种
  2. create table [if not exisis] [db_name.]table_name(
  3. name1 [type] [default|materialized]alias expr],
  4. name1 [type] [default|materialized]alias expr],
  5. ...
  6. ) engine = engine
  7. -- 复制其他表的结构
  8. create table [if not exisis] [db_name1.]table_name as [db_name2.]table_name2 [ENGINE=engine]
  9. -- 通过select字句的形式创建
  10. create table [if not exisis] [db_name1.]table_name ENGINE=engine AS select ...

第四章 ClickHouse 表引擎

ClickHouse - 图2

4.1 MergeTree 引擎

ClickHouse 中最强大的是MergeTree(合并树)引擎以及该系列(MergeTree)中的其他引擎。
MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以已数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合并。相比在插入时不断修改(重写)已储存的数据,这种测录会搞笑很多。
*特点:

  1. 储存的数据按主键排序:这样可以创建一个小型的稀疏索引
  2. 如果指定了分区建的话,可以使用分区:在相同数据集合相同结果集的情况下CK中某些带分区的会比普通的快。查询的时候指定分区建是ClickHouse会自动截取分区数据。这样也可以有效的怎家查询性能。
  3. 支持数据副本:ReplicatedMergeTree 系列的表提供了数据副本功能
  4. 支持数据采样

    **

  1. ENFINE=MergeTree() 指定使用那个引擎
  2. [PARTITION BY exper] 分区键,只能按照日期分区
  3. [ORDER BY exper] 排序键,如果不指定排序键,排序键就是主键,不可指定与主键相同的列
  4. [PRIMAY KEY exper] 主键,如果不指定主键,主键就是排序键,不可指定与排序键的列
  5. [SAMPLE BY expr] 用于抽样的表达式,主键必须包含这个表达式
  6. [SETTNGS name=XXX] 一些其他影响性能的参数
  7. index_granular 索引粒度。即索引中相邻[标记]间的数据行数,默认是8192
  8. use_minimalistic_part_header_in_zookeeper 数据片段在zookeepr中的存储方法
  9. min_merge_bytes_to_use_drect_io 使用I/O来操作磁盘合并操作时要求合并操作时要求的最小数据量

实例:

  1. create table me_table
  2. (
  3. `data` Date,
  4. `id` UInt8,
  5. `name` String
  6. )
  7. ENGINE = MergeTree()
  8. PARTITION BY date
  9. order by (id,name)
  10. SETTINGS index_granularity = 8192

4.1.1 ReplacingMergeTree

使用场景:在数据过多场景对数据进行去重
ReplacingMergeTree 引擎时MergeTree引擎的延伸,比MergeTree多了个Replacing的描述,会在合并过程中删除主键重复的记录,确保主键唯一性。合并会在未知的时间在后台进行,所以无法预先做出计划。有些数据可能仍未被处理。
ReplacingMergeTree适用于在后台清除重复的数据以节省空间,但是它不保证没有重复数据的出现,在一定程度上可以弥补clickhouse不能对数据做更新的操作。

  1. Enginge = ReplacingMergetree([ver]) ver表示的列只能是UInt*,DateTime
  2. 合并的时候会选择ver中最大的版本或最后一条保留,类似HBASE的裂变。
  1. create table rme_table
  2. (
  3. `date` Date,
  4. `id` UInt8,
  5. `name` String,
  6. `point` UInt8
  7. )
  8. ENGINE = ReplacingMergeTree(point)
  9. PARTITION BY date
  10. order by (id,name)

4.1.2 SummingMergeTree

使用场景:对某个字段长期的汇总查询场景
SummingMergeTree 做出的延伸的是合并,是可加列的累加,不可加列选择第一次输入的值。当合并SummingMergeTree表数据片段时,ClickHouse会把所有具有相同主键的行为合并为一行,该行包含了呗合并的行中具有数值类型的列的汇总值。如果主键的组合方式使得单个键值对于大量的行,则可以显著的减少储存空间便加快数据查询的速度,对于不可加的列,会取一个最先出现的值。

  1. ENGINE = SummingMergeTree([colums])
  2. Create Table sme_table
  3. (
  4. `date` Date,
  5. `name` String,
  6. `sum` UInt16,
  7. `not_sum` UInt16
  8. )
  9. ENGINE = SummingMergeTree(sum)
  10. PARITION BY date
  11. ORDER BY (date,name)

注意:MergeTree 的合并并不能保证绝对合并完成,有可能没有合并,有可能去重时没有去除干净,累加时没有累加干净。如果用于汇总的所有的列中的值为0,则该行会被删除,如果汇总的列为主键则不会被汇总。

4.1.3 AggregatingMergeTree

使用场景:可以使用AggregatingMergeTree表来做增量数据统计聚合,包括物化视图的数据聚合。
该引擎继承自MergeTree,并改变了数据片段的合并逻辑。ClickHouse会将相同的主键的所有行(在一个数据片段内)替换为单个储存一系列聚合函数状态的行。
可以使用AggregatingMergeTree 表来做增量数据统计聚合,包括物化视图的数据聚合。
引擎使用AggregateFunction类型来处理所有列。
如果要按照一组规则来合并减少行数,则使用AggregatingMergeTree是合适的。
对于AgggregatingMergeTree不能直接使用insert来查询写入数据。

  1. CREATE TABLE amt_basic_tab(
  2. date Date,
  3. D1 String,
  4. D2 String,
  5. D3 String,
  6. M1 UInt16)
  7. ENGINE MergeTree()
  8. PARTITION BY date ORDER BY (D1,D2,D3)
  9. -- 物化视图
  10. create materialized view amt_tab_view
  11. ENGINE = AggregatingMergeTree()
  12. PARTITION BY date ORDER BY (D2,D3) as
  13. select date,D2, D3, uniqState(D1) as uv from amt_basic_tab group by date,D2,D3;

4.4.4 CollapsingMergeTree

CollapsingMergeTree 会异步的删除(折叠)这些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成对的行。没有成对的行会被保留。该引擎可以显著的降低存储量并提高 SELECT 查询效率。
CollapsingMergeTree引擎有个状态列sign,这个值1为”状态”行,-1为”取消”行,对于数据只关心状态列为状态的数据,不关心状态列为取消的数据

  1. CREATE TABLE cmt_tab(
  2. sign Int8,
  3. date Date,
  4. name String,
  5. point String
  6. )
  7. ENGINE=CollapsingMergeTree(sign)
  8. PARTITION BY date
  9. ORDER BY (name)
  10. SAMPLE BY name

4.1.1 数据存储

表由按主键排序的数据片段(DATA PART)组成。
当数据被插入到表中时,会创建多个数据片段并按照主键的字典序排序。片段中数据首先按照主键排序,具有相同的主键的部分按Date排序。
不同分区的数据会被分成不同的片段,Clickhouse在后台合并数据片段以便高效存储,不同分区的数据片段不会进行合并。合并机制并不保证具有相同的数据具有相同主键的行都合并在同一数据片段中。数据片段可以WideCompact格式储存。在Wide格式下,每一列会在文件系统中存储为单独文件在Compact格式下所有列都存在一个文件中。Compact格式可以提高插入量少插入频率频繁时的性能。
数据存储格式有min_bytes_for_partmin_rows_for_wide_part表引擎参数控制。如果数据片段中的字节数或行数小于相应的设置值,数据片段会以Compact格式存储,否则以Wide格式存储。每个数据片段被逻辑的分割成颗粒(granules)。颗粒是CK中进行数据进行数据查询时的最小不可分割数据集。ck不会对行或者值进行拆分,所以每个颗粒总是包含整数个数。每个数据的第一行通过该行的主键值进行标记。颗粒的大小通过表引擎参数index_granularityindex_granularity_bytes 控制。颗粒的行在[1, index_granularity] 范围中,这取决于行的大小。如果单行的大小超过了index_granularity_bytes 设置的值,那么一个颗粒的大小会超index_granularity_bytes。在这种情况下,颗粒的大小等于该行的大小。