MPP (Massively Parallel Processing),即大规模并行处理,将任务并行的分散到多个服务器和节点上,在每个节点上计算完成后,将各自部分的结果汇总在一起得到最终的结果(与hadoop相似)。
ClickHouse是一款MPP架构的列式存储数据库,吸取了其他优秀技术的精髓,将每个细节做到极致,从而在性能上远远超过其他技术。
ClickHouse拥有DBMS完备的管理功能,但它不仅仅是一个数据库。作为DBMS,它具备以下基本功能。
行式存储和列式存储,数据在磁盘上的组织结构有着根本不同,数据分析计算时,行式存储需要遍历整表,列式存储只需要遍历单个列,所以列式库更适合做大宽表,用来做数据分析计算。
列式存储和数据压缩,是高性能数据库必不可少的重要特性。如果想让查询变得更快,最简单且最有效的方法就是减少数据扫描范围和数据传输时的大小。
HBase,BigTable,Cassandra,这些系统也可以单独存储单独列的值,但由于其他场景的优化,无法有效处理分析查询。在这些系统中,每秒钟可以获得大约十万行的吞吐量,但是每秒不会达到数亿行。
1.向量化执行引擎
“能升级硬件解决的问题,千万别优化程序”这句话虽然有点调侃的味道,但有时候优化程序带来的性能提升,还真不如直接升级硬件来的简单直接。硬件层面的优化确实是最直接、最高效的方式之一。
向量化执行,就是利用寄存器硬件层面的特性,为上层应用程序的性能带来了指数级的提升。
为了实现向量化执行,需要利用CPU的SIMD(Single Instruction Multiple Data,即用单条指令操作多条数据)命令,它是通过数据并行来提高性能,原理就是在CPU寄存器层面实现数据的并行操作。
(1)CPU
中央处理单元(Cntral Pocessing Uit)的缩写,也叫处理器,是计算机的运算核心和控制核心。人靠大脑思考,电脑靠CPU来运算、控制。让电脑的各个部件顺利工作,起到协调和控制作用。
从功能方面来看,CPU的内部由寄存器,控制器,运算器和时钟四部分构成,各部分之间由电流信号相互连通。
1)寄存器:可用来暂存指令,数据等处理对象,可以将其看做是内存的一种。根据种类的不同,一个CPU内部会有20~100个寄存器。
2)控制器:负责把内存上的指令,数据等读入寄存器,并根据指令的执行结果来控制整个计算机。
3)运算器:负责运算从内存读入寄存器的数据。
4)时钟:负责发出CPU开始计时的时钟信号。不过,也有些计算机的时钟位于CPU的外部。
(2)内存
内存:
(3)CPU缓存
CPU缓存的定义为CPU与内存之间的临时数据交换器,它的出现是为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快得多。
三级缓存(包括L1一级缓存、L2二级缓存、L3三级缓存)都是集成在CPU内的缓存,它们的作用都是作为CPU与主内存之间的高速数据缓冲区,L1最靠近CPU核心;L2其次;L3再次。运行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2较大、L3最大。CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。
(4)磁盘
硬盘:存储资料和软件等数据的设备,有容量大、断电数据不丢失的特点。
典型计算机的存储层次结构如下图所示,存储媒介距离CPU越近,则访问数据的速度越快。
可以看出寄存器访问数据的速度,是内存的300倍,是磁盘的30000 万倍。
秒的换算:s(秒)=1000ms(毫秒)=1000 000μs(微秒)=1000 000 000ns(纳秒)=1000 000 000 000ps(皮秒)
ClickHouse使用关系型模型描述数据,并提供了传统数据库的概念,如数据库、表、视图和函数等。ClickHouse完全可以使用SQL作为查询语言,用户基础大,容易上手。(一个强大的技术必须惠及大众,人人都会用)
Hive
Impala是Cloudera公司主导开发的新型查询系统,它提供SQL语义,能查询存储在Hadoop的HDFS和HBase中的PB级大数据。
Spark SQL
Druid 是一个分布式的数据分析平台,预聚合算是 Druid 的一个非常大的亮点,通过预聚合可以减少数据的存储以及避免查询时很多不必要的计算。
Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。
5.表引擎
与MySQL类似,ClickHouse将存储部分进行了抽象,把存储引擎作为一层独立的接口。ClickHouse目前拥有合并树、内存、文件、接口和其他6大类等20多种表引擎,用户可以根据实际应用场景,选择合适的表引擎使用。
一个通用的表引擎可以有更广泛的适用性,能适应更多的应用场景,但是正是这种通用性,造成它无法在所有应用场景内性能做到极致,也是一种平庸的表现。
举个导弹的例子:短程,中程,远程洲际导弹
将表引擎独立设计,可以通过特定的表引擎支撑特定的场景,用法十分灵活。对于简单的应用场景,可以直接使用简单的引擎降低成本,而复杂的应用场景也有合适的表引擎。
向量化执行是通过数据级并行的方式提升了性能,多线程处理是通过线程级并行的方式实现了性能的提升。
ClickHouse在数据存取方面,既支持分区(纵向扩展,利用多线程技术),也支持分片(横向扩展,利用分布式技术)。ClickHouse将多线程和分布式的技术应用到了极致。
Hadoop生态系统技术都采用了Master-Slave主从架构,由一个主节点统筹全局。而ClickHouse则采用Multi-Master多主架构,集群中每个节点角色对等,客户端访问任意一个节点都能得到相同的效果。
多主架构有很多优势:
ClickHouse规避了单点故障的问题,非常适合用于多数据中心、异地多活的场景。
ClickHouse支持实时查询,即便是在复杂查询的场景下,也能够做到极快响应,且无需对数据进行任何预处理加工。
与其他技术相比,ClickHouse优势十分明显:
Vertica(不开源)是一款基于列存储的MPP (massively parallel processing)架构的数据库。它可以支持存放多至PB(Petabyte)级别的结构化数据。Vertica是由关系数据库大师Michael Stonebraker(2014 年图灵奖获得者)所创建,于2011年被惠普收购并成为其核心大数据平台软件。
ClickHouse支持分片,也是采用分治思想,将数据进行横向切分,解决存储和查询的瓶颈。ClickHouse分片依赖集群,每个集群由1个到多个分片组成,而每个分片则对应ClickHouse的1台服务器节点,分片的数量取决于节点数量。(一个分片只能对应一台服务器节点)
ClickHouse的分片功能没有那么自动化,它提供了一个本地表(Local Table)和分布式表(Distribute Table)的概念。
这种设计类似数据库的分库与分表,使用非常灵活。
在业务系统上线的早期,数据量不大,此时数据无需分片,所以使用单节点的本地表即可满足业务需求;
随着业务增长,数据量增大,再通过新增分片的方式分流数据,通过分布式表实现分布式查询;
1.Column和Field
Column和Field是ClickHouse数据最基础的映射单元。ClickHouse按列存储数据,内存中的一列数据由一个Column对象表示。在大多数情况下,ClickHouse都会以整列的方式操作数据,但如果需要操作某列中单个具体的数值,就需要使用Field对象,Field对象代表一个单值。
Column对象采用泛化设计思路,对象分为接口和实现两个部分。接口定义了对数据进行各种关系运算的方法;而这些方法的具体实现,根据数据类型的不同,由相应的对象实现。
Field对象使用了聚合的设计模式,在Field对象内部聚合了Null、UInt64、String和Array等多种数据类型和对应的处理逻辑。
数据的序列化和反序列化工作由DataType负责,虽然DataType负责序列化相关工作,但DataType并不直接负责数据的读取,而是转由从Column或者Field对象获取数据。
虽然Column和Field组成了数据的基本映射单元,但在实际操作中还缺少一些必要信息,如数据的类型和列的名称,于是ClickHouse设计了Block对象,ClickHouse内部的数据操作是面向Block对象进行的,Block对象可以看作是数据表的子集。
Block对象的本质是由数据对象(Column)、数据类型(DataType)和列名称(列名称字符串)组成的三元组。Column提供了数据的读取能力,DataType提供了序列化和反序列化,Block在这些对象的基础上实现了进一步的抽象和封装,从而简化了整个使用的过程,仅通过Block对象就能完成一系列的数据操作。
ClickHouse主要提供两类函数:普通函数和聚合函数。
普通函数由IFunction接口定义,内部有很多函数的实现,如FunctionFormatDateTime,FunctionSubstring等。普通函数是没有状态的,函数效果作用于每行数据之上,在函数执行过程中,并不会逐行计算,而是采用向量化的方式直接作用与一整列数据。
聚合函数由IAggregateFunction接口定义,聚合函数是有状态的。聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,从而实现增量计算。
4.Storage
ClickHouse在数据表的底层设计中并没有所谓的Table对象,它直接使用IStorage接口指代数据表。
表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现,例如IStorageSystemOneBlock(系统表引擎),StorageMergeTree(合并树表引擎)和StorageTinyLog(日志表引擎)等。
IStorage接口定义了DDL(如alter、rename、drop等)、read和write方法,分别负责数据的定义、查询与写入。在数据查询时,IStorage负责根据AST(抽象语法树)查询语句的要求,返回指定列的原始数据。
Parser和Intercepter是非常重要的两组接口,Parser分析器负责创建AST对象;Intercepter解释器负责解释AST,并进一步创建查询的执行管道。它们与IStorage一起串联了整个数据查询过程。
服务器实现了多个不同的接口:
(1)一个用于任何外部客户端的 HTTP 接口。
(2)一个用于本机 ClickHouse 客户端以及在分布式查询执行中跨服务器通信的 TCP 接口。
(3)一个用于传输数据以进行拷贝的接口。
整个流程:
ClickHouse集群中,一张数据表可以有多个分片,而每个分片都拥有自己的副本。
在设计软件架构的时候,大部分技术组件都采用自上而下的设计思路,而ClickHouse则采用了自下而上的设计方式,ClickHouse就是希望能以最快的速度进行group by查询和过滤。
能用钱解决的问题,千万别花时间。能用硬件解决的问题,千万别优化程序。
首先从硬件功能层面着手设计ClickHouse,需要考虑硬件水平(CPU、内存、磁盘、网络等)和数据结构(String、HashTable、Vector等)。
为了让硬件的功效最大化,ClickHouse会在内存中进行group by操作,并且使用HashTable装载数据,同时非常主要CPU L3级别的缓存使用。
ClickHouse就是在每个环节、细节使用最优方式,逐步积累,才达到非常惊人的性能,ClickHouse在基本查询中能做到1.75亿次/秒的数据扫描性能。
在ClickHouse的底层实现中,经常面对一些应用场景,如字符串查询、数组排序等,选择合适的算法,才能实现性能的最大化。
在字符串搜索方面,ClickHouse针对不同的应用场景,选择不同的算法:
总结:性能是算法选择的首要考量的指标
除了字符串之外,其余的应用场景也类似,ClickHouse会使用最适合、最快的算法。一旦有性能强大的新的算法出现,ClickHouse会进行验证,择优保留使用。
针对同一个应用场景的不同情况,可以选择不同的实现方式,尽可能将性能最大化。例如去重计数函数uniqCombined函数,会根据数据量的不同选择不同的算法:
对于文本转换,数据过滤,数据压缩,Json转换,可以使用必杀器 向量化执行。
构建出如此强大的ClickHouse,还需要拥有一个能够持续验证、持续改进的机制。由于Yandex数据量比较大,ClickHouse经常会使用真实的数据进行测试,这一点很好地保证了测试场景的真实性。正是有如此可靠的持续集成环境,ClickHouse才能够持续测试,快速迭代,快速改进。
ClickHouse不是一项单一的技术,而是一种自底向上的,追求极致性能的设计思路,这就是ClickHouse如此之快的秘诀。