Bootstrap

ClickHouse笔记——ClickHouse架构

一、OLAP常见架构分类
1.Relational OLAP(关系型OLAP),它直接使用关系模型构建,数据模型中最常使用的是星型模型和雪花模型,这是最为直接的实现方法,因为OLAP概念最开始提出来的时候,就是建立在关系型数据库之上的
2.Multidimensional OLAP(多维型OLAP),它是为了解决ROLAP性能问题,核心思想就是借助预先聚合结果,以空间换时间的形式最终提升查询性能,先确定需要分析的维度字段,然后通过预处理的形式,对各个维度进行组合并事先聚合,最后将结果保存起来
3.Hybrid OLAP(混合架构OLAP),这种思路可以理解为ROLAP和MOLAP两者的集成,简单了解一下即可
二、ClickHouse架构
ClickHouse是一款MPP架构的列式存储数据库,它使用了多主对等网络结构,但它同时也是基于关系模型ROLAP方案。
1.CilckHouse的核心特征
(1) 完备的DBMS功能,CilckHouse拥有完备的管理功能,作为一个DBMS,它具备DDL,DML,权限控制,数据备份与恢复(提供了数据备份导出与导入恢复机制,满足生产环境的需要),分布式管理(提供集群模式,能够自动管理多个数据库节点)
(2) 列式存储与数据压缩,列式存储和数据压缩,对于一个高性能数据库来说是必不可少的特性,想让查询效率变快,最简单且有效的方法就是减少数据扫描范围和数据传输的大小,一般来说列式存储是数据压缩的前提。
行式存储与列式存储相比,后者可以减少查询时所需扫描的数据量,比如一张表有A1~A50的字段,我需要查询A1~A5,行式存储会扫描出所有的字段,即使我只需要前5个字段,但数据是按行进行组织的,实际上还是扫描了所有字段;而列式存储,可以直接获取数据的前5个字段,避免了多余数据扫描的问题。
数据压缩的实质就是,当数据发生重复时,就会对重复的数据进行编码转换,重复的数据越多,则压缩率越高,压缩率越高,则数据的体积就越小,在网络中的传输越快,对网络带宽和磁盘IO的压力就越小。
ClickHouse就是按列进行组织,属于同一列的数据会被保存到一起,列于列之间也会有不同的文件分别保存(MergeTree表引擎),数据默认使用LZ4算法压缩,在Yandex.Metrica的生产环境中,数据总体的压缩比可以达到8 : 1。
(3) 向量化执行引擎,向量化执行寄存器在硬件层面的特性,为上层应用程序性能带来了指数级的提升。可以把它看作一项消除程序中循环的优化,如一台机器要运行10个任务,需要把一个一个把任务执行完,但是如果把机器扩充到10台,则运行10个任务只需要一次就够了。
为了实现向量化执行,需要利用CPU的SIMD指令,SIMD全称是Single Instruction Multiple Data,即用单条指令操作多条数据,它是通过数据并行以提高性能的一种实现方式,原理是在CPU寄存器层面实现数据的并行操作。在计算机系统的体系结构中,存储系统是一种层次结构,存储媒介距离CPU越近,则访问数据的速度越快。
CPU寄存器
从寄存器中访问数据的速度,是从内存中访问的数据速度的300倍,是从磁盘中访问数据速度的3000万倍,所以利用CPU向量化执行的特性,对于程序的性能提升意义非凡。(ClickHouse可以把CPU占满)
(4) 关系模型与SQL查询,ClickHouse使用关系模型并提供了传统数据库的概念,如数据库,表,视图,函数等,而且ClickHouse完全使用SQL作为查询语言(大小写敏感,语意不一样,而且使用join非常慢)
ClickHouse使用了关系模型,所以将构建在传统关系型数据库或数据仓库之上的系统迁移到ClickHouse的成本会非常低。
(5) 多样化的表引擎,ClickHouse将存储部分进行了抽象,把存储引擎作为一层独立的接口,它拥有合并树,内存,文件,接口和其他6大类20多种表引擎。
将表引擎独立设计的优点是通过特地表引擎支撑特定的场景,非常灵活,可以根据不同的场景使用不同的引擎降低成本。
(6) 多线程与分布式,ClickHouse大量使用了多线程技术以实现提速,以此和向量化执行形成互补。ClickHouse在数据存取方面,既支持分区(纵向扩展,利用多线程原理),也支持分片(横向扩展,利用分布式原理),可以说将多线程和分布式技术用到了极致。
(7) 多主架构,ClickHouse采用了Multi-Master多主架构,集群中的每个节点角色对等,客户端访问任何一个节点都能得到相同的效果,这种架构可以不用再区分主控节点,集群中所有节点功能相同,天然规避了单节点故障,使系统架构变得简单(多适合数据中心,异地多活等场景)
(8) 在线查询,即在复杂查询的场景下,也能做到极快响应,且无须对数据进行任何预处理加工。
(9) 数据分片与分布式查询,数据分片是对数据进行横向切分,是在海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。
ClickHouse提供了本地表与分布式表的概念,一张本地表等于一份数据的分片,分布式表本身不存储任何数据,它是本地表的访问代理,借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
2.ClickHouse的架构设计
(1) Column与Field,Column和Field是CilckHouse数据最基础的映射单元,内存中的一列数据由一个Column对象表示,Cloumn对象分为接口和实现两个部分,在IColumn接口对象中,定义了对数据进行各种关系运算的方法(insertRangeFrom,insertFrom插入数据,cut分页,filter过滤),大多数情况下,CilckHouse都会以整列的方式操作数据,如果需要操作单个具体的值,需要使用Field对象,Field对象代表一个单值,Field对象使用了聚合的设计模式(在Field对象内部聚合了Null,UInt64,String,Array等13种数据类型及处理逻辑)
(2) DataType,数据的序列化与反序列化工作由DataType负责,IDataType接口定义了许多正反序列化的方法,它们成对出现(serializeBinary和deserializeBinary等),使用了泛化的设计模式,具体方法的实现由对应数据类型实现和承载。
(3) Block与Block流,ClickHouse的内部数据操作是面向Block对象进行的,并且采用了流的形式,Block对象可以看作数据表的子集,Block对象的本质是由数据对象,数据类型和列名称组成的三元组(Column,DataType和列名称字符串),Colum提供了数据的读取能力,DataType知道了数据如何正反序列化,所以Block在这些对象的基础上实现了进一步的抽象和封装,简化了整个使用过程,仅通过Block对象就能完成一系列的数据操作(具体实现并没有直接聚合Column和DataType对象,而是通过ColumnWithTypeAndName对象进行间接引用)。
Block流有两组顶层接口,IBlockInputStream负责数据的读取和关系运算,IBlockOutputDtream负责将数据输出到下一环节,Block流也使用了泛化的设计模式,对数据的各种操作最终都会转换成其中一种流的实现。
IBlockInputStream的实现类大概可以分为三类,第一类是用于处理数据定义的DDL操作,如DDLQueryStatusInputStream等;第二类是用于处理关系运算的相关操作,如LimitBlockInputStream等;第三类则是与表引擎呼应,每一种表引擎都有对应的BlockInputStream实现,如MergeTreeBaseSelectBlockInputStream(MeregeTree表引擎)。
(4) Table,在数据表的底层设计中没有Table对象,它直接使用了IStorage接口指代数据表,不同的表引擎由不同的子类实现(IStorageSystemOneBlock系统表,StorageMergeTree合并树引擎,StorageTinyLog日志表引擎),在数据查询的时候,IStorage负责根据AST查询语句的指示要求,返回指定列的原始数据,后续对数据的进一步加工,计算,过滤,则会统一交由Interpreter解释器对象处理。
(5) Parser与Interpreter,Parser与Interpreter是非常重要的两组接口,Parser分析器负责创建AST对象,Interpreter解释器则负责解释AST,并进一步创建查询的执行管道,它们与IStorage一起串联成了整个数据查询的过程,Parser分析器可以将一条SQL语句以递归下降的方法解析成语法树的形式,不同的SQL会由不同的Parser实现类解析;Interpreter解释器会根据解释器的类型,聚合它所需的资源,首先会解析成AST对象,然后执行业务逻辑,最终返回IBlock对象,以线程的形式建立起一个查询执行管道。
(6) Functions与Aggregate Functions,ClickHouse主要提供两类函数,普通函数和聚合函数,普通函数由IFunction接口定义,拥有数十种函数实现,例如FunctionFormatDateTime,FunctionSubstring等,普通函数是没有状态的,函数效果作用于每行数据之上,函数在执行的过程中,并不会一行一行的运算,而是采用向量化的方式直接作用于一整列数据。
聚合函数由IAggregateFunction接口定义,聚合函数是有状态的(以增量的方式实现),聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输。
(7) Cluster与Replication,ClickHouse的集群由分片(Shard)组成,而每个分片又通过副本(Replica)组成。ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片,1副本,则至少需要部署2个服务节点,分片只是一个逻辑概念,其物理承载还是由副本承担的。
<ch_cluste>
  <shard>
    <replica>
      <host>10.37.129.6</host>
      <port>9000</port>
    </replica>
    <replica>
      <host>10.37.129.7</host>
      <port>9000</port>
    </replica>
  </shard>
</ch_cluste>
以上代码,在物理存储层面则统一使用副本代表分片和副本,所以真正表示1分片,1副本语义的配置,应该改为1个分片和2个副本。
3.ClickHouse为什么这么快
(1) 着眼硬件,先想后做。ClickHouse会在内存中进行GROUP  BY,并且使用HashTable装载数据,他们非常在意CPU  L3级别的缓存,因为一次L3的缓存失效会带来70~100ns的延迟,这意味着在单核CPU上,会浪费4000万次/秒的运算,而在一个32线程的CPU上,则可能会浪费5亿次/秒的运算,正是因为这些细节,ClickHouse在基准查询的时候能做到1.75亿次/秒的数据扫描性能。
(2) 算法在前,抽象在后。ClickHouse选择的算法:对于常量,使用了Volnitsky算法;对于非常量,使用了CPU的向量化执行SIMD,暴力优化;正则匹配使用re2和hyperscan算法。性能是算法选择的首要考量指标。
(3) 勇于尝鲜,不行就换。ClickHouse会使用最合适,最快的算法,如果市面上出现了号称性能最强大的新算法,ClickHouse团队会立即将其纳入并进行验证。如果效果不错,就保留使用;如果性能不尽人意,就将其抛弃。
(4) 特定场景,特殊优化。针对于同一场景的不同状况,选择使用不同的实现方式,尽可能将性能最大化,如去重函数uniqCombined函数,会根据数据量的不同选择不同的算法,当数据量小的时候,会使用Array保存,当数据量中等的时候,使用HashSet保存,当数据量很大的时候,则使用HyperLogLog算法。
SIMD被广泛地应用于文本转换,数据过滤,数据解压和JSON转换等场景,它利用了寄存器进行暴力优化。
(5) 持续测试,持续改进。ClickHouse拥有一个能够持续验证,持续改进的机制,由于Yandex的天然优势,ClickHouse经常会使用真实的数据进行测试,很好地保证了测试场景的真实性。

;