ClickHouse-概述

概述

ClickHouse 是一款 MPP 架构的列式存储数据库,拥有完备的管理功能,所以他称得上是一个 DBMS 数据库管理系统,而不仅仅是一个数据库。

如果你想学习的话,那就一起来吧。

完备的 DBMS 功能

作为 DBMS 具备以下基本功能:

  • DDL(数据定义语言):可以动态地创建、修改和删除数据库、表和视图,而无需重启服务。
  • DML(数据操作语言):可以动态查询、插入、修改和删除数据。
  • 权限控制:可以按照用户粒度设置数据库或者表的操作权限,保障数据的安全性。
  • 数据备份与恢复:提供了数据备份导出与导入机制,满足生产环境的需求。
  • 分布式管理:提供集群模式,能够自动管理多个数据库节点。

列式存储与数据压缩

列式存储和数据压缩对于一款高性能数据库来说是必不可少的特性。
按列存储相比按行存储的另一个优势就是对数据压缩的友好性。并且由于压缩的本质是按照一定步长对数据进行匹配扫描,当发现重复部分的时候就进行编码转换。而在同一列的数据中,他们会拥有相同的数据类型和现实语义,重复项的可能性自然更高。

向量化执行引擎

能用钱解决的问题,千万别花时间。这句话是虽然是一句玩笑话,但是在实际中硬件层面的优化是最直接、最高效的提升性能途径之一。当然向量化执行就是这种方式的代表,寄存器硬件层面的特性,为上层应用程序的性能带来了指数级的提升。
为了实现向量化执行,需要利用 CPUSIMD 命令。SIMD 的全称是 Single Instruction Multiple Data,即用单条指令操作多条数据。在现代计算机概念中,通过数据并行以提高性能的一种实现方式,原理即在 CPU 寄存器层面实现数据的并行操作。
ClickHouse 目前利用 SSE4.2 指令集实现向量化执行。

关系模型和 SQL 查询

ClickHouse 完全使用 SQL 作为查询语言(支持 GROUP BY, ORDER BY, JOIN, IN 等大部分标准 SQL),这会使得它平易近人、容易理解和学习。
SQL 解析方面 ClickHouse 是大小写敏感的,这意味着 SELECT aSELECT A 所代表的语义是不同的。
ClickHouse 使用了关系模型,所以将构建在传统关系型数据库或者数据仓库上的系统迁移到 ClickHouse 的成本会降低很多,可以直接沿用之前的成果。

多样化的表引擎

ClickHouse 共拥有合并树、内存、文件、接口和其他六大类二十多种表引擎,其中每一种表引擎都有各自的特点,用户可以根据世界业务场景的需求选择合适的表引擎使用。
将表引擎单独设计的好处是显而易见的,通过特定的表引擎支撑特定的场景,十分灵活,对于简单的场景,可直接使用简单的引擎降低成本,而复杂的场景也有合适的引擎。

多线程与分布式

前面提到的向量化执行通过数据并行的方式提高性能,那么多线程处理就是通过线程级并行方式实现了性能的提升。

多主结构

ClickHouse 采用了 Multi-Master 多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果,这种多主的结构有许多优势,例如对等的角色使系统架构变得更加简单,不用再区分主控节点、数据节点和计算节点,集群中的所有节点功能相同。所以他天然规避了单点故障问题,非常适合多数据中心、异地多活的场景。

在线查询

ClickHouse 完美平衡成本和性能,采用了 LSM 树结构,使得数据的插入量可以很大。同时由于 ClickHouse 的内部优化,使得在复杂查询的场景下能够做到极快响应,且无须对数据进行任何预处理加工,即真 在线

数据分片与分布式查询

ClickHouse 支持分片,而分片依赖集群,每个集群有一个到多个分片组成,而每个分片则对应了 ClickHouse 的一个服务节点,分片的数量上限取决于节点数量(一个分片对应一个服务节点)。
ClickHouse 提供了本地表(Local Table)和分布式表(Distributed Table)的概念。一张本地表等同于一份数据的分片,而分布式表本身不存储任何数据,仅仅只是本地表的访问代理,其作用类似于分库中间件,借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
所以在使用单个节点的本地表(单个数据分片)即可满足业务需求的基础上,待业务增长后,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。

不足之处

上面说有这么多的优点,那么当然也是会有缺点的,毕竟人无完人,物无完物

  • 不支持事务。
  • 不擅长根据主键按行粒度进行查询(虽然支持)。
  • 不擅长按行删除数据(虽然支持)。

架构设计

click-1-1.jpg

ColumnField

ColumnFieldClickHouse 数据最基础的映射单元,作为一款百分之百的按列存储数据,内存中的一列数据库由一个 Column 对象表示。Column 对象分为接口和实现两个部分,在 ICloumn 接口对对象中,定义了对数据进行各种关系运算的方法。
在大多数场合下,ClickHouse 都会以整列的方式操作数据,但凡也有例外,如果需要操作单个具体的数值(也就是单列中的一行数据),则需要使用 Field 对象,Field 对象代表一个单值,与 Column 对象的泛化设计思路不同,Field 对象使用了聚合的设计模式,在 Field 对象内部聚合了 NullUInt64String等十三种数据结构类型及对应的处理逻辑。

DataType

数据的序列化和反序列化工作由 DataType 负责。IDataType 接口定义了许多正反序列的方法,他们成对出现,例如 serializeBinarydeserializeBinary 等,覆盖常见的二进制、文本、JSONXML 等多种格式类型。
DataType 虽然负责序列化相关工作,但它并不直接负责数据的读取,而实际转由从 ColumnField 对象中获取数据。在 DataType 的实现类中,聚合了相应数据类型的 Column 对象和 Field 对象。

BlockBlock

ClickHouse 内部的数据操作是面向 Block 对象进行的,并且采用了流的形式。虽然 ColumnField 组成了数据的基本映射单元,但对应到实际的操作中,他们还是缺少了一些必要的信息,比如数据类型和列名称。
Block 对象可以看作数据表的子集Block 对象的本质是由数据对象、数据类型和列名称组成的三元组,即 ColumnDataType 和列名称字符串。其中 Column 提供数据的读取能力,而 DataType 知道如何正反序列化,所以 Block 在这些对象的基础上进行进一步的抽象和封装,从而简化并完成一系列的数据操作。

在具体的实际中,Block 并没有直接聚合 ColumnDataType 对象,而是通过 ColumnWithTypeAndName 对象进行间接引用。

在有了 Block 对象这一层封装之后,在 Block 流的设计就是水到渠成的事情,流操作有两个顶层的接口,其中 IBlockInputStream 负责数据的读取和关系运算,IBlockOutputStream 负责将数据输出到下一环节。同样的 Block 也使用了泛化的设计,对数据的各种操作最终都会转化为之类其中一种流的实现,IBlockInputStream 接口定义读取数据的若干个 read 虚方法都会由具体的实现类来填充。当然 IBlockOutputStream 与之类似同样定义了若干个 write 虚方法,均由实现类来填充具体逻辑。

IBlockInputStream 接口共有六十多个实现类,大致可以分为三类:

  • 处理数据定义的 DDL 操作。
  • 处理关系运算的相关操作。
  • 与表引擎相呼应,每一种表引擎都有与之对应的 BlockInputStream 实现。

Table

在数据表的底层设计中并没有所谓的 Table 对象,直接使用 IStorage 接口指代数据表。其中 IStorage 接口定义了 DDLreadwrite 方法,这些方法分别负责数据的定义、查询与写入。在数据查询时 IStorage 负责根据 AST 查询语句的指示要求,返回数据列的原始数据。后续对数据的进一步加工、计算和过滤,则会统一交由 Interpreter 解释器对象处理。
Table 发起的一次操作通常都会经历如此的过程,接收 AST 查询语句,根据 AST 返回指定列的数据,之后再将数据交由 Interpreter 做进一步处理。

ParserInterpreter

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

FunctionsAggregate Functions

ClickHouse 主要提供了两类函数,普通函数和聚合函数。
其中普通函数由 IFunction 接口定义,拥有数十种函数实现,另外普通函数是没有状态的,函数效果作用于每行数据之上,当然在数据具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用与一整列数据。
聚合函数由 IAggregateFunction 接口定义,相比无状态的普通函数,聚合函数是由状态的,另外聚合函数的状态支持序列化和反序列化,能够在分布式节点之间进行传输,以实现增量计算。

ClusterReplication

ClickHouse 的集群由分片(Shard)组成,而每个分片又通过副本(Replica)组成,这种分层的概念,在一些流行的分布式系统中十分普遍。
ClickHouse 中有几个与众不同的特性:

  • ClickHouse 的一个节点只能有一个一个分片,也就是说若要实现一分片、一副本,则至少需要部署两个服务节点。
  • 分片只是一个逻辑概念,其物理承载还是由副本承担的。

引用

ClickHouse 架构概述


个人备注

此博客内容均为作者学习所做笔记,侵删!
若转作其他用途,请注明来源!