概述
ClickHouse
是一款 MPP
架构的列式存储数据库,拥有完备的管理功能,所以他称得上是一个 DBMS
数据库管理系统,而不仅仅是一个数据库。
如果你想学习的话,那就一起来吧。
完备的 DBMS
功能
作为 DBMS
具备以下基本功能:
DDL
(数据定义语言):可以动态地创建、修改和删除数据库、表和视图,而无需重启服务。DML
(数据操作语言):可以动态查询、插入、修改和删除数据。- 权限控制:可以按照用户粒度设置数据库或者表的操作权限,保障数据的安全性。
- 数据备份与恢复:提供了数据备份导出与导入机制,满足生产环境的需求。
- 分布式管理:提供集群模式,能够自动管理多个数据库节点。
列式存储与数据压缩
列式存储和数据压缩对于一款高性能数据库来说是必不可少的特性。
按列存储相比按行存储的另一个优势就是对数据压缩的友好性。并且由于压缩的本质是按照一定步长对数据进行匹配扫描,当发现重复部分的时候就进行编码转换。而在同一列的数据中,他们会拥有相同的数据类型和现实语义,重复项的可能性自然更高。
向量化执行引擎
能用钱解决的问题,千万别花时间。这句话是虽然是一句玩笑话,但是在实际中硬件层面的优化是最直接、最高效的提升性能途径之一。当然向量化执行就是这种方式的代表,寄存器硬件层面的特性,为上层应用程序的性能带来了指数级的提升。
为了实现向量化执行,需要利用 CPU
的 SIMD
命令。SIMD
的全称是 Single Instruction Multiple Data
,即用单条指令操作多条数据。在现代计算机概念中,通过数据并行以提高性能的一种实现方式,原理即在 CPU
寄存器层面实现数据的并行操作。ClickHouse
目前利用 SSE4.2
指令集实现向量化执行。
关系模型和 SQL
查询
ClickHouse
完全使用 SQL
作为查询语言(支持 GROUP BY, ORDER BY, JOIN, IN
等大部分标准 SQL
),这会使得它平易近人、容易理解和学习。
在 SQL
解析方面 ClickHouse
是大小写敏感的,这意味着 SELECT a
和 SELECT A
所代表的语义是不同的。ClickHouse
使用了关系模型,所以将构建在传统关系型数据库或者数据仓库上的系统迁移到 ClickHouse
的成本会降低很多,可以直接沿用之前的成果。
多样化的表引擎
ClickHouse
共拥有合并树、内存、文件、接口和其他六大类二十多种表引擎,其中每一种表引擎都有各自的特点,用户可以根据世界业务场景的需求选择合适的表引擎使用。
将表引擎单独设计的好处是显而易见的,通过特定的表引擎支撑特定的场景,十分灵活,对于简单的场景,可直接使用简单的引擎降低成本,而复杂的场景也有合适的引擎。
多线程与分布式
前面提到的向量化执行通过数据并行的方式提高性能,那么多线程处理就是通过线程级并行方式实现了性能的提升。
多主结构
ClickHouse
采用了 Multi-Master
多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果,这种多主的结构有许多优势,例如对等的角色使系统架构变得更加简单,不用再区分主控节点、数据节点和计算节点,集群中的所有节点功能相同。所以他天然规避了单点故障问题,非常适合多数据中心、异地多活的场景。
在线查询
ClickHouse
完美平衡成本和性能,采用了 LSM
树结构,使得数据的插入量可以很大。同时由于 ClickHouse
的内部优化,使得在复杂查询的场景下能够做到极快响应,且无须对数据进行任何预处理加工,即真 在线。
数据分片与分布式查询
ClickHouse
支持分片,而分片依赖集群,每个集群有一个到多个分片组成,而每个分片则对应了 ClickHouse
的一个服务节点,分片的数量上限取决于节点数量(一个分片对应一个服务节点)。ClickHouse
提供了本地表(Local Table
)和分布式表(Distributed Table
)的概念。一张本地表等同于一份数据的分片,而分布式表本身不存储任何数据,仅仅只是本地表的访问代理,其作用类似于分库中间件,借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
所以在使用单个节点的本地表(单个数据分片)即可满足业务需求的基础上,待业务增长后,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。
不足之处
上面说有这么多的优点,那么当然也是会有缺点的,毕竟人无完人,物无完物。
- 不支持事务。
- 不擅长根据主键按行粒度进行查询(虽然支持)。
- 不擅长按行删除数据(虽然支持)。
架构设计
Column
和 Field
Column
和 Field
是 ClickHouse
数据最基础的映射单元,作为一款百分之百的按列存储数据,内存中的一列数据库由一个 Column
对象表示。Column
对象分为接口和实现两个部分,在 ICloumn
接口对对象中,定义了对数据进行各种关系运算的方法。
在大多数场合下,ClickHouse
都会以整列的方式操作数据,但凡也有例外,如果需要操作单个具体的数值(也就是单列中的一行数据),则需要使用 Field
对象,Field
对象代表一个单值,与 Column
对象的泛化设计思路不同,Field
对象使用了聚合的设计模式,在 Field
对象内部聚合了 Null
、UInt64
、String
等十三种数据结构类型及对应的处理逻辑。
DataType
数据的序列化和反序列化工作由 DataType
负责。IDataType
接口定义了许多正反序列的方法,他们成对出现,例如 serializeBinary
和 deserializeBinary
等,覆盖常见的二进制、文本、JSON
、XML
等多种格式类型。DataType
虽然负责序列化相关工作,但它并不直接负责数据的读取,而实际转由从 Column
或 Field
对象中获取数据。在 DataType
的实现类中,聚合了相应数据类型的 Column
对象和 Field
对象。
Block
和 Block
流
ClickHouse
内部的数据操作是面向 Block
对象进行的,并且采用了流的形式。虽然 Column
和 Field
组成了数据的基本映射单元,但对应到实际的操作中,他们还是缺少了一些必要的信息,比如数据类型和列名称。Block
对象可以看作数据表的子集,Block
对象的本质是由数据对象、数据类型和列名称组成的三元组,即 Column
、 DataType
和列名称字符串。其中 Column
提供数据的读取能力,而 DataType
知道如何正反序列化,所以 Block
在这些对象的基础上进行进一步的抽象和封装,从而简化并完成一系列的数据操作。
在具体的实际中,Block
并没有直接聚合 Column
和 DataType
对象,而是通过 ColumnWithTypeAndName
对象进行间接引用。
在有了 Block
对象这一层封装之后,在 Block
流的设计就是水到渠成的事情,流操作有两个顶层的接口,其中 IBlockInputStream
负责数据的读取和关系运算,IBlockOutputStream
负责将数据输出到下一环节。同样的 Block
也使用了泛化的设计,对数据的各种操作最终都会转化为之类其中一种流的实现,IBlockInputStream
接口定义读取数据的若干个 read
虚方法都会由具体的实现类来填充。当然 IBlockOutputStream
与之类似同样定义了若干个 write
虚方法,均由实现类来填充具体逻辑。
IBlockInputStream
接口共有六十多个实现类,大致可以分为三类:
- 处理数据定义的
DDL
操作。 - 处理关系运算的相关操作。
- 与表引擎相呼应,每一种表引擎都有与之对应的
BlockInputStream
实现。
Table
在数据表的底层设计中并没有所谓的 Table
对象,直接使用 IStorage
接口指代数据表。其中 IStorage
接口定义了 DDL
、read
、write
方法,这些方法分别负责数据的定义、查询与写入。在数据查询时 IStorage
负责根据 AST
查询语句的指示要求,返回数据列的原始数据。后续对数据的进一步加工、计算和过滤,则会统一交由 Interpreter
解释器对象处理。
对 Table
发起的一次操作通常都会经历如此的过程,接收 AST
查询语句,根据 AST
返回指定列的数据,之后再将数据交由 Interpreter
做进一步处理。
Parser
和 Interpreter
Parser
和 Interpreter
是非常重要的两组接口,Parser
分析器负责创建 AST
对象,而 Interpreter
解释器则负责解释 AST
,并进一步创建查询的执行管道。他们与 IStorage
一起,串联起整个查询的过程。Parser
分析器可以将一条 SQL
语句以递归下降的方法分解成 AST
语法树的形式,不同的 SQL
会经由不同的 Parser
实现类解析。Interpreter
解释器的作用就像 service
服务层一样,起到串联整个查询过程的作用,他会根据解释器的类型,聚合它所需要的资源。首先他会解析 AST
对象,然后执行 业务逻辑(如分支判断、设置参数、调用接口等),最终返回 IBlock
对象,以线程的形式建立起一个查询执行管道。
Functions
和 Aggregate Functions
ClickHouse
主要提供了两类函数,普通函数和聚合函数。
其中普通函数由 IFunction
接口定义,拥有数十种函数实现,另外普通函数是没有状态的,函数效果作用于每行数据之上,当然在数据具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用与一整列数据。
聚合函数由 IAggregateFunction
接口定义,相比无状态的普通函数,聚合函数是由状态的,另外聚合函数的状态支持序列化和反序列化,能够在分布式节点之间进行传输,以实现增量计算。
Cluster
和 Replication
ClickHouse
的集群由分片(Shard
)组成,而每个分片又通过副本(Replica
)组成,这种分层的概念,在一些流行的分布式系统中十分普遍。
在 ClickHouse
中有几个与众不同的特性:
ClickHouse
的一个节点只能有一个一个分片,也就是说若要实现一分片、一副本,则至少需要部署两个服务节点。- 分片只是一个逻辑概念,其物理承载还是由副本承担的。
引用
个人备注
此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!