ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。
OLAP名为联机分析,又可以称为多维分析,是由关系型数据库之父埃德加·科德(EdgarFrank Codd)于1993年提出的概念。顾名思义,它指的是通过多种不同的维度审视数据,进行深层次分析。
维度可以看作观察数据的一种视角,例如人类能看到的世界是三维的,它包含长、宽、高三个维度。直接一点理解,维度就好比是一张数据表的字段,而多维分析则是基于这些字段进行聚合查询。
如上图,多维分析包含以下几种操作:
OLTP(on-line transaction processing)翻译为联机事务处理, OLAP(On-Line Analytical Processing)翻译为联机分析处理。
下面用一张图来简要对比。
在传统的行式数据库系统中,处于同一行中的数据总是被物理的存储在一起,存储方式如下图
在列式数据库系统中,来自不同列的值被单独存储,来自同一列的数据被存储在一起,数据按如下的顺序存储:
不同的数据存储方式适用不同的业务场景,而对于OLAP来说,列式存储是最适合的选择。
为什么列式数据库更适合于OLAP场景呢?下面这两张图就可以给你答案
下面分别从两个I/O和CPU两个角度来分析为什么他们有如此之大的差别
如果你想让查询变得更快,最简单且有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以帮助我们实现上述两点。
列式存储和数据压缩通常是伴生的。数据按列存储。而具体到每个列字段,数据也是独立存储的,每个列字段都拥有一个与之对应的.bin数据文件,相同类型的数据放在同一个文件中,对压缩更加友好。数据默认使用LZ4算法压缩,在Yandex.Metrica的生产环境中,数据总体的压缩比可以达到8:1(未压缩前17PB,压缩后2PB)。
ClickHouse拥有完备的管理功能,所以它称得上是一个DBMS(Database Management System,数据库管理系统),而不仅是一个数据库。作为一个DBMS,它具备了一些基本功能,
如下所示。
相比HBase和Redis这类NoSQL数据库,ClickHouse使用关系模型描述数据并提供了传统数据库的概念(数据库、表、视图和函数等)。与此同时,ClickHouse完全使用SQL作为查询语言(支持GROUP BY、ORDER BY、JOIN、IN等大部分标准SQL),这使得它平易近人,容易理解和学习。
向量化执行,可以简单地看作从硬件的角度上消除程序中循环的优化。
为了实现向量化执行,需要利用CPU的SIMD指令。SIMD的全称是Single Instruction MultipleData,即用单条指令操作多条数据。现代计算机系统概念中,它是通过数据并行以提高性能的一种实现方式,它的原理是在CPU寄存器层面实现数据的并行操作。例如有8个32位整形数据都需要进行移位运行,则由一条对32位整形数据进行移位的指令重复执行8次完成。SIMD引入了一组大容量的寄存器,一个寄存器包含8 * 32位,可以将这8个数据按次序同时放到一个寄存器。同时,CPU新增了处理这种8 * 32位寄存器的指令,可以在一个指令周期内完成8个数据的位移运算。(本质就是将每次处理的数据从一条变为一批)
与MySQL类似,ClickHouse也将存储部分进行了抽象,把存储引擎作为一层独立的接口。ClickHouse共拥有合并树、内存、文件、接口和其他6大类20多种表引擎。其中每一种表引擎都有着各自的特点,用户可以根据实际业务场景的要求,选择合适的表引擎使用。
ClickHouse则采用Multi-Master多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果。这种多主的架构有许多优势,例如对等的角色使系统架构变得更加简单,不用再区分主控节点、数据节点和计算节点,集群中的所有节点功能相同。所以它天然规避了单点故障的问题,非常适合用于多数据中心、异地多活的场景。
在各服务器之间,通过网络传输数据的成本是高昂的,所以相比移动数据,更为聪明的做法是预先将数据分布到各台服务器,将数据的计算查询直接下推到数据所在的服务器。ClickHouse在数据存取方面,既支持分区(纵向扩展,利用多线程原理),也支持分片(横向扩展,利用分布式原理),可以说是将多线程和分布式的技术应用到了极致。
数据分片是将数据进行横向切分,这是一种在面对海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。ClickHouse支持分片,而分片则依赖集群。每个集群由1到多个分片组成,而每个分片则对应了ClickHouse的1个服务节点。分片的数量上限取决于节点数量(1个分片只能对应1个服务节点)。
ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse提供了** 本地表(Local Table)** 与 **分布式表(Distributed Table)**的概念。一张本地表等同于一份数据的分片,而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
首先亮出官方的测试报告
Clickhouse性能对比报告
所有用于对比的数据库都使用了相同配置的服务器,在单个节点的情况下,对一张拥有133个字段的数据表分别在1000万、1亿和10亿三种数据体量下执行基准测试,基准测试的范围涵盖43项SQL查询。
市面上有很多与Clickhouse采用了同样技术(如列式存储、向量化引擎等)的数据库,但为什么ClickHouse的性能能够将其他数据库远远甩在身后呢?这主要依赖于下面几个方面
目前ClickHouse公开的资料相对匮乏,比如在架构设计层面就很难找到完整的资料,甚至连一张整体的架构图都没有,根据官网提供的信息,我们能够得出一个大概的架构,如下图
Parser:Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析。
Interpreter:Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行“业务逻辑”(例如分支判断、设置参数、调用接口等);最终返回IBlock
对象,以线程的形式建立起一个查询执行管道。
Tables:Tables由 IStorage
接口表示。该接口的不同实现对应不同的表引擎。比如 StorageMergeTree
、StorageMemory
等。这些类的实例就是表。
Block与Block Streams:ClickHouse内部的数据操作是面向Block对象进行的,并且采用了流的形式。
ColumnWithTypeAndName
对象进行间接引用。IBlockInputStream
具有 read
方法,其能够在数据可用时获取下一个块。IBlockOutputStream
具有 write
方法,其能够将块写到某处。Functions:ClickHouse主要提供两类函数——普通函数和聚合函数。
IFunction
接口定义,其是没有状态的,函数效果作用于每行数据之上。当然,在函数具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用于一整列数据。IAggregateFunction
接口定义,相比无状态的普通函数,聚合函数是有状态的,并且聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。DataType:数据的序列化和反序列化工作由DataType负责。根据不同的数据类型,IDataType
接口会有不同的实现类。DataType虽然会对数据进行正反序列化,但是它不会直接和内存或者磁盘做交互,而是转交给Column和Filed处理。
Column与Field:Column和Field是ClickHouse数据最基础的映射单元。
IColumn
接口对象中,定义了对数据进行各种关系运算的方法,例如插入数据的insertRangeFrom
和insertFrom
方法、用于分页的cut
,以及用于过滤的filter
方法等。而这些方法的具体实现对象则根据数据类型的不同,由相应的对象实现。