战斗民族开源神器!ClickHouse为什么能够征服各个大厂?
Posted 凌桓丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了战斗民族开源神器!ClickHouse为什么能够征服各个大厂?相关的知识,希望对你有一定的参考价值。
文章目录
ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。
OLAP
什么是OLAP?
OLAP名为联机分析,又可以称为多维分析,是由关系型数据库之父埃德加·科德(EdgarFrank Codd)于1993年提出的概念。顾名思义,它指的是通过多种不同的维度审视数据,进行深层次分析。
维度可以看作观察数据的一种视角,例如人类能看到的世界是三维的,它包含长、宽、高三个维度。直接一点理解,维度就好比是一张数据表的字段,而多维分析则是基于这些字段进行聚合查询。
如上图,多维分析包含以下几种操作:
- 下钻:从高层次向低层次明细数据穿透,例如从省下钻到市。
- 上卷:和下钻相反,从低层次向高层次汇聚,例如从市汇聚成省。
- 切片:观察立方体的一层,将一个或多个维度设为单个固定值,然后观察剩余的维度,例如将商品维度固定为足球。
- 切块:与切片类似,只是将单个固定值变成多个值。例如将商品维度固定成足球、篮球。
- 旋转:旋转立方体的一面,如果要将数据映射到一张二维表,那么就要进行旋转,这就等同于行列置换。
OLAP与OLTP
OLTP(on-line transaction processing)翻译为联机事务处理, OLAP(On-Line Analytical Processing)翻译为联机分析处理。
- 从字面上来看OLTP是做事务处理,OLAP是做分析处理。
- 从对数据库操作来看,OLTP主要是对数据的增删改,OLAP是对数据的查询。
- 因为OLTP所产生的业务数据分散在不同的业务系统中,而OLAP往往需要将不同的业务数据集中到一起进行统一综合的分析,这时候就需要根据业务分析需求做对应的数据清洗后存储在数据仓库中,然后由数据仓库来统一提供OLAP分析。
- OLTP是数据库的应用,OLAP是数据仓库的应用
下面用一张图来简要对比。
列式存储
列式存储与行式存储
在传统的行式数据库系统中,处于同一行中的数据总是被物理的存储在一起,存储方式如下图
在列式数据库系统中,来自不同列的值被单独存储,来自同一列的数据被存储在一起,数据按如下的顺序存储:
不同的数据存储方式适用不同的业务场景,而对于OLAP来说,列式存储是最适合的选择。
列式存储与OLAP
为什么列式数据库更适合于OLAP场景呢?下面这两张图就可以给你答案
- 行式数据库
- 列式数据库
下面分别从两个I/O和CPU两个角度来分析为什么他们有如此之大的差别
- I/O
- 针对分析类查询,通常只需要读取表的一小部分列。在列式数据库中你可以只读取你需要的数据。
- 由于数据总是打包成批量读取的,所以压缩是非常容易的。同时数据按列分别存储这也更容易压缩。这进一步降低了I/O的体积。
- 由于I/O的降低,这将帮助更多的数据被系统缓存,进一步降低了数据传输的成本。
- CPU
- 由于执行一个查询需要处理大量的行,因此在整个向量上执行所有操作将比在每一行上执行所有操作更加高效。同时这将有助于实现一个几乎没有调用成本的查询引擎。如果你不这样做,使用任何一个机械硬盘,查询引擎都不可避免的停止CPU进行等待。所以,在数据按列存储并且按列执行是很有意义的。
列式存储与数据压缩
如果你想让查询变得更快,最简单且有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以帮助我们实现上述两点。
列式存储和数据压缩通常是伴生的。数据按列存储。而具体到每个列字段,数据也是独立存储的,每个列字段都拥有一个与之对应的.bin数据文件,相同类型的数据放在同一个文件中,对压缩更加友好。数据默认使用LZ4算法压缩,在Yandex.Metrica的生产环境中,数据总体的压缩比可以达到8:1(未压缩前17PB,压缩后2PB)。
核心特点
完备的DBMS功能
ClickHouse拥有完备的管理功能,所以它称得上是一个DBMS(Database Management System,数据库管理系统),而不仅是一个数据库。作为一个DBMS,它具备了一些基本功能,
如下所示。
- DDL(数据定义语言):可以动态地创建、修改或删除数据库、表和视图,而无须重启服务。
- DML(数据操作语言):可以动态查询、插入、修改或删除数据。
- 权限控制:可以按照用户粒度设置数据库或者表的操作权限,保障数据的安全性。
- 数据备份与恢复:提供了数据备份导出与导入恢复机制,满足生产环境的要求。
- 分布式管理:提供集群模式,能够自动管理多个数据库节点。
关系模型与SQL查询
相比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)**的概念。一张本地表等同于一份数据的分片,而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
应用场景
擅长的场景
- 绝大多数是读请求
- 数据以相当大的批次(> 1000行)更新,而不是单行更新;或者根本没有更新。
- 已添加到数据库的数据不能修改。
- 对于读取,从数据库中提取相当多的行,但只提取列的一小部分。
- 宽表,即每个表包含着大量的列
- 查询相对较少(通常每台服务器每秒查询数百次或更少)
- 对于简单查询,允许延迟大约50毫秒
- 列中的数据相对较小:数字和短字符串(例如,每个URL 60个字节)
- 处理单个查询时需要高吞吐量(每台服务器每秒可达数十亿行)
- 事务不是必须的
- 对数据一致性要求低
- 每个查询有一个大表。除了他以外,其他的都很小。
- 查询结果明显小于源数据。换句话说,数据经过过滤或聚合,因此结果适合于单个服务器的RAM中
不擅长的场景
- OLTP事务性操作(不支持事务,不支持真正的更新/删除)
- 不擅长根据主键按行粒度进行查询(如select * from table where user_id in (xxx, xxx, xxx, …))
- 不擅长存储和查询 blob 或者大量文本类数据(按列存储)
- 不擅长执行有大量join的查询(Distributed引擎局限)
- 不支持高并发,官方建议QPS <= 100
Clickhouse为什么会这么快?
首先亮出官方的测试报告
所有用于对比的数据库都使用了相同配置的服务器,在单个节点的情况下,对一张拥有133个字段的数据表分别在1000万、1亿和10亿三种数据体量下执行基准测试,基准测试的范围涵盖43项SQL查询。
市面上有很多与Clickhouse采用了同样技术(如列式存储、向量化引擎等)的数据库,但为什么ClickHouse的性能能够将其他数据库远远甩在身后呢?这主要依赖于下面几个方面
- 着眼硬件,先想后做
- ClickHouse会在内存中进行GROUP BY,并且使用HashTable装载数据。
- ClickHouse非常在意CPU L3级别的缓存,因为一次L3的缓存失效会带来70~100ns的延迟。这意味着在单核CPU上,它会浪费4000万次/秒的运算;而在一个32线程的CPU上,则可能会浪费5亿次/秒的运算。
- 算法在前,抽象在后
- 对于常量,使用Volnitsky算法;
- 对于非常量,使用CPU的向量化执行SIMD(用于文本转换、数据过滤、数据解压和JSON转换等),暴力优化;
- 正则匹配使用re2和hyperscan算法。性能是算法选择的首要考量指标。
- 勇于尝鲜,不行就换
- 除了字符串之外,其余的场景也与它类似,ClickHouse会使用最合适、最快的算法。如果世面上出现了号称性能强大的新算法,ClickHouse团队会立即将其纳入并进行验证。如果效果不错,就保留使用;如果性能不尽人意,就将其抛弃。
- 特定场景,特殊优化
- 针对同一个场景的不同状况,选择使用不同的实现方式,尽可能将性能最大化。
- 例如去重计数uniqCombined函数,会根据数据量的不同选择不同的算法:当数据量较小的时候,会选择Array保存;当数据量中等的时候,会选择HashSet;而当数据量很大的时候,则使用HyperLogLog算法。
- 针对不同的场景,Clickhouse提供了MergeTree引擎家族,如MergeTree、ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree和VersionedCollapsingMergeTree等。
- 持续测试,持续改进
- 由于Yandex的天然优势,ClickHouse经常会使用真实的数据进行测试,这一点很好地保证了测试场景的真实性。
- ClickHouse差不多每个月都能发布一个版本,正因为拥有这样的发版频率,ClickHouse才能够快速迭代、快速改进。
架构
目前ClickHouse公开的资料相对匮乏,比如在架构设计层面就很难找到完整的资料,甚至连一张整体的架构图都没有,根据官网提供的信息,我们能够得出一个大概的架构,如下图
-
Parser:Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析。
-
Interpreter:Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行“业务逻辑”(例如分支判断、设置参数、调用接口等);最终返回
IBlock
对象,以线程的形式建立起一个查询执行管道。 -
Tables:Tables由
IStorage
接口表示。该接口的不同实现对应不同的表引擎。比如StorageMergeTree
、StorageMemory
等。这些类的实例就是表。- IStorage接口定义了DDL(如ALTER、RENAME、OPTIMIZE和DROP等)、read和write方法,它们分别负责数据的定义、查询与写入。在数据查询时,IStorage负责根据AST查询语句的指示要求,返回指定列的原始数据。
- 后续对数据的进一步加工、计算和过滤,则会统一交由Interpreter解释器对象处理。对Table发起的一次操作通常都会经历这样的过程,接收AST查询语句,根据AST返回指定列的数据,之后再将数据交由Interpreter做进一步处理。
-
Block与Block Streams:ClickHouse内部的数据操作是面向Block对象进行的,并且采用了流的形式。
- Block:虽然Column和Filed组成了数据的基本映射单元,但对应到实际操作,它们还缺少了一些必要的信息,比如数据的类型及列的名称。于是ClickHouse设计了Block对象,Block对象可以看作数据表的子集。Block对象的本质是由数据对象、数据类型和列名称组成的三元组,即Column、DataType及列名称字符串。Column提供了数据的读取能力,而DataType知道如何正反序列化,所以Block在这些对象的基础之上实现了进一步的抽象和封装,从而简化了整个使用的过程,仅通过Block对象就能完成一系列的数据操作。在具体的实现过程中,Block并没有直接聚合Column和DataType对象,而是通过
ColumnWithTypeAndName
对象进行间接引用。 - Block Streams:Block Streams用于处理数据。我们可以使用Block Streams从某个地方读取数据,执行数据转换,或将数据写到某个地方。
IBlockInputStream
具有read
方法,其能够在数据可用时获取下一个块。IBlockOutputStream
具有write
方法,其能够将块写到某处。
- Block:虽然Column和Filed组成了数据的基本映射单元,但对应到实际操作,它们还缺少了一些必要的信息,比如数据的类型及列的名称。于是ClickHouse设计了Block对象,Block对象可以看作数据表的子集。Block对象的本质是由数据对象、数据类型和列名称组成的三元组,即Column、DataType及列名称字符串。Column提供了数据的读取能力,而DataType知道如何正反序列化,所以Block在这些对象的基础之上实现了进一步的抽象和封装,从而简化了整个使用的过程,仅通过Block对象就能完成一系列的数据操作。在具体的实现过程中,Block并没有直接聚合Column和DataType对象,而是通过
-
Functions:ClickHouse主要提供两类函数——普通函数和聚合函数。
- Function:普通函数由
IFunction
接口定义,其是没有状态的,函数效果作用于每行数据之上。当然,在函数具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用于一整列数据。 - AggregateFunction:聚合函数由
IAggregateFunction
接口定义,相比无状态的普通函数,聚合函数是有状态的,并且聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。
- Function:普通函数由
-
DataType:数据的序列化和反序列化工作由DataType负责。根据不同的数据类型,
IDataType
接口会有不同的实现类。DataType虽然会对数据进行正反序列化,但是它不会直接和内存或者磁盘做交互,而是转交给Column和Filed处理。 -
Column与Field:Column和Field是ClickHouse数据最基础的映射单元。
- Column:内存中的一列数据由一个Column对象表示。Column对象分为接口和实现两个部分,在
IColumn
接口对象中,定义了对数据进行各种关系运算的方法,例如插入数据的insertRangeFrom
和insertFrom
方法、用于分页的cut
,以及用于过滤的filter
方法等。而这些方法的具体实现对象则根据数据类型的不同,由相应的对象实现。 - Field:在大多数场合,ClickHouse都会以整列的方式操作数据,但凡事也有例外。如果需要操作单个具体的数值(也就是单列中的一行数据),则需要使用Field对象,Field对象代表一个单值。与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式。在Field对象内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。
- Column:内存中的一列数据由一个Column对象表示。Column对象分为接口和实现两个部分,在
以上是关于战斗民族开源神器!ClickHouse为什么能够征服各个大厂?的主要内容,如果未能解决你的问题,请参考以下文章
俄罗斯民族是一个战斗民族wwwhj8828com19908836661