MENU

ClickHouse架构概述

March 13, 2023 • Read: 1026 • 编码,ClickHouse

随着业务的增长,ClickHouse 已经为 Yandex.Metrica 存储了超过二十万亿行数据,90% 的自定义查询都能在 1 秒内响应结果,集群规模也超过了 400 台服务器。

ClickHouse 的核心特性

ClickHouse 是一款 MPP 架构的列式数据库,但 MPP 和列式存储并不是什么新鲜玩意,为什么只有 ClickHouse 的性能如此出众?通过前面的介绍,我们知道ClickHouse 发展至今的演进过程。一共经历了四个阶段,每一个阶段的迭代都是取其精华去其糟粕。可以说 ClickHouse 吸收了个各种技术的精髓,下面了解一下 ClickHouse 数据库的一些和新特性,正是这些特性使得 ClickHouse 如此有优秀。

完整的 DBMS 功能

ClickHouse 具备完整的管理功能,所以称得上 DBMS (Datbase Management System,数据库管理系统),不仅仅是一个数据库,作为一个 DBMS,还具备以下功能(仅为代表性功能,并非全部功能):

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

列式存储和数据压缩

列式存储和数据压缩,对于一款高性能数据库来说一个必不可少的特性。如果你想让查询变得更快,最简单有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储可以帮助我们实现上述两点。列式存储和数据压缩通常是相伴而生的,一般来说列式存储是数据压缩的前提。

列式存储相比行式存储,可以减少查询时所需扫描的数据量。例如一个用户表有 50 个字段,查询前 5 个字段,因为是行式存储,所以逐行扫描时实际会获取所有的字段,然后返回前五个。这样就意味着需要扫描磁盘更多空间,然后不返回这些数据。如果是按列获取数据,就不会存在这样的问题,直接扫描并返回前五列数据即可,避免了多余的数据扫描。

按照列式存储相比按行式存储另一个优势就是对数据压缩的友好性。ClickHouse 的数据按列进行组织,属于同一列的数据会被保存在一起,而压缩的本质就是如此,同一列的数据类型和语义相同,数据具有更高比例的重复性,对应的压缩比也会也大。数据默认采用LZ4 压缩算法,在 Yandex.Metrica 的生产环境中,数据总体的压缩比可以达到 8:1 (由 17PB 压缩至 2PB)。列式存储除了降低 IO 和存储的压力之外,还为向量化执行做好了铺垫。

HBase,BigTable 这些系统也能实现列式存储,但无法有效处理分析查询需求。在这些系统中,每秒可以实现数十万的数据吞吐量,但不会达到数亿行的级别。

向量化执行引擎

向量化执行是 CPU 寄存器硬件层面的特性,为上层应用带来指数级的提升。

向量化执行可以看作是一项消除程序中循环结果的优化。为了实现向量化执行,需要利用 CPU 的 SIMD 指令(全称是 ingle Instruction Multiple Data 即 单条指令操作多条数据)。它是通过数据并行以提高性能的一种实现方式,原理是在 CPU 寄存器层面实现并行操作。

具体实现原理参考:Clickhouse SIMD 使用初探 - 简书

关系模型和 SQL 查询

相比 Redis 和 HBase 这类 NoSQL 数据库,ClickHouse 使用关系模型描述数据并提供了传统数据库的概念(数据库、表、视图和函数等)。与此同时,ClickHouse 完全支持使用 SQL 作为查询语言,兼容大多数标准 SQL 。也正因为 ClickHouse 提供标准协议的 SQL 查询接口,是的现有的第三方系统可以轻松与它对接。在 SQL 解析方面,ClickHouse 是大小写敏感的。

关系模型相比文档或键值对等其他模型,拥有更好的描述能力,也能够更加清晰的表述实体之间的关系。更重要的是,在 OLAP 领域,已有的大量数据建模工作都是基于关系模型展开的(星型模型、雪花模型或宽表模型)。ClickHouse 使用了关系模型,所以构建在传统关系型数据库或数据仓库之上的系统迁移到 ClickHouse 的成本会变得更低。

多样化的表引擎

因为 Yandex.Metrica 的最初架构是基于 MySQL 实现的,所以在 ClickHouse 的设计中,能够看到 MySQL 的影子,表引擎的设计就是其中之一。与 MySQL 类似, ClickHouse 也将存储部分进行了抽象,把存储引擎作为一层独立的接口。ClickHouse 拥有合并树、内存、文件、接口和其他 6 大类 20 多种表引擎,每个引擎都有各自的特点,用户需要根据实际的业务场景选择适合的表引擎。

在 IT 技术和发展的长河中,并不会存在能够适应任何场景的通用系统,所以将表引擎独立设计的好处显而易见,通过特定的表引擎支撑特定的场景,可以直接使用简单的引擎降低成本,而复杂的场景也有合适的选择。

多线程和分布式

如果说向量化执行是通过数据级并行的方式提升了性能,那么多线程执行就是通过线程级并行的方式实现了性能提升。相比基于底层硬件实现的向量化执行 SIMD,线程级并行通常由更高层次软件层面控制。现在市面上的服务器早已普及多处理器架构,具有良好的多核心多线程处理能力。由于 SIMD 不适用于较多的分支判断场景,所以 ClickHouse 也大量使用了多线程技术进行提速,以和向量化执行形成互补。

如果一台服务器性能吃紧,那么就要利用的多台服务器的资源协同处理。为了实现这一目标,首先要在数据层面实现数据的分布式。因为在分布式领域,有一条金科玉律:“计算移动比数据移动更划算”。在各服务器之间,使用网络传输数据的成本的高昂的,所以相比移动数据,更聪明的做法是预先将数据分布到各台服务器上,将数据的计算查询直接推送到数据所在的服务器。ClickHouse 在数据存取方面,既支持分区(纵向扩展,利用多线程原理),也支持分片(横向扩展,利用分布式原理)。

多主架构

HDFS、Spark、HBase 和 Elasticsearch 这类分布式系统,都采用了 Master - Slave 主从架构,让一个节点作为 Leader 掌控全局。而 ClickHouse 采用 Multi - Master 的多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能达到相同的结果。这种多主架构有许多优势,例如对等的角色使系统架构变的更加简单,不再区分主控节点、数据节点和计算节点,集群中所有节点的功能相同。正因如此,ClickHouse 天然规避了单点故障的问题,非常适用于多数据中心、异地多活等场景。

在线查询

ClickHouse 经常被拿来和其他的分析型数据库做对比,例如 Vertica、SparkSQL、Hive 和 Elasticsearch 等, ClickHouse 与这些数据库确实有相似之处,例如都可以支持海量数据的查询场景,都拥有分布式架构、列式存储、数据分配、计算下推等特性。这也侧面说明了 ClickHouse 在设计上确实吸取了各路的奇技淫巧。在具备这些功能的同时,ClickHouse 也拥有明显的优势。例如,Vertica 这类商业数据库价高昂;SparkSQL 与 Hive 这类系统无法保障 90% 的查询都在一秒内返回,在大数据量的情况下的复杂查询甚至需要分钟级的响应时间;而 ES 这类搜索引擎在处理过亿级别数据的聚合查询时显得捉襟见肘。

其他的开源系统太慢,商用的系统太贵,而 ClickHouse 在成本和性能之间做了完美平衡。同时 ClickHouse 当之无愧的阐释了 "在线" 二字的含义,即便是在复杂的查询场景下,也能做到极快的响应,而无需对数据进行预处理加工。

数据分片与分布式查询

数据分片是将数据横向切分,这是一种面对海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。ClickHouse 支持分片,而分片依赖集群。每个集群由一个到多个分片组成,而每个分片对应了 ClickHouse 的一个服务节点。分片数量的上限取决于节点数量(一个分片只能对应一个服务节点)。

ClickHouse 并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse 提供了本地表(Local Table)和分布式表 (Distributed Table) 的概念。一张本地表扽沟通与一份数据的分片,而分布式表本身不存储任何数据,他只是本地表的代理访问,类似于其他分库中间件。借助分表能够代理访问多个数据分片,从而实现分布式查询。

这种设计类似于数据库的分库分表,十分灵活。比如系统上线初期,数据量不高,只需要使用单台服务器(单个数据分片)即可满足需求,等到业务增长,数据量增大时,再通过新增分布式表的方式分流数据,并通过分布式表实现分布式查询。

ClickHouse 为什么如此之快

虽然前面解释了 ClickHouse 是列式存储并且用了向量化引擎所以快,但依旧不能解释所有的疑问。因为市面上由很多数据库同样使用着这些技术,但依然没有 ClickHouse 这么快,所以我们从另一个角度探讨一下 ClickHouse 的秘诀到底是什么。

首先我们要抛出一个疑问:在设计软件架构时,做设计的原则应该是自上而下的去设计,还是自下而上的去设计呢?在传统的观念中,自然是自上而下的去设计,通常我们都被教导过要做好顶层设计。而 ClickHouse 的设计则是采用自下而上的方式。

着手硬件,先想后做

首先从硬件方面下手,需要思考如下的问题:

  • 我们使用的硬件水平如何?包含 cpu、硬盘、内存、网络等。
  • 在这样的硬件上,我们需要达到什么样的性能,包括吞吐、延迟等。
  • 我们准备使用怎样的数据结构,包含 String、HashTable、Vector 等。
  • 选择这些数据结构,在我们的硬件上会如何工作?

基于将硬件功效最大化的目的,ClickHouse 会在内存中进行 GROUP BY,并使用 HashTable 来装在数据。同时,技术团队非常在一 CPU L3 级别的缓存,因为一次 L3 级别的缓存失效将会带来 70 ~ 100ns 的延迟。意味着在单核 CPU 上会浪费 4000万次/秒的运算;在 32 线程的 CPU 上,可能会浪费 5 亿次/秒的运算。别小看这些运算细节,一点一滴的累加起来,带来的数据是非常可观的。正因为 ClickHouse 在设计之初就考虑到了这些细节,所以才能再基准查询中做到 1.75 亿次/秒的数据扫描性能。

算法在前,抽象在后

ClickHouse 在底层的实现中,经常会面对一些重复的场景,例如字符串子串查询、数组排序、使用 HashTable 等。如何才能实现性能的最大化?算法的选择才是重中之重。例如在字符串搜索方面,针对不同的场景,ClickHouse 选择了这些算法:对于常量,使用 VoInitsky 算法;对于非常量,使用 CPU 的向量化执行 SIMD,暴力优化;正则匹配使用 re2 和 hyperscan 算法。性能是算法选择的重要考量指标。

勇于尝鲜,不行就换

除了字符串之外,其他的场景也类似,ClickHouse 会使用最合适、最快的算法。如果市面上出现了性能更强大的新算法,ClickHouse 团队会立即对其进行验证。如果效果不错,就会纳入 ClickHouse 中,并替换掉现有的算法。

特定场景,特殊优化

针对同一个场景的不同状况,使用不同的实现方式,尽可能的将性能最大化。签名介绍的字符串查询,针对不同的场景选择不同的算法就是这一思路的体现。还例如去重计数 uniqCombined 函数,会根据数据量的不同选择不同的算法:当数据量较小时,会使用 Array 保存;当数据量中等时,会使用 HashSet;当数据量很大时,则会使用 HyperLogLog 算法。

对于数据结构比较清晰的场景,会通过代码生成技术展开循环,以减少循环次数。接着就是利用了向量化执行。SIMD 被更广泛的应用于文本转换、数据过滤、数据解压和 JSON 转换等场景。相较于单纯的使用CPU,利用寄存器暴力优化也算是一种降维打击了。

持续测试,持续改进

如果只是单纯在上述细节上下功夫,也不足以构建出强大的 ClickHouse,还需要拥有一个能够持续验证和改进的机制。这也是 Yandex 的天然优势,ClickHouse 经常会使用真实的数据进行测试,这一点很好的保证了测试数据的真实性,与此同时,ClickHouse 的发版节奏也是非常快的,差不多每个月都有个新版本。

小结

本篇文章分析了 ClickHouse 的核心特性,通过核心特性的部分展示,ClickHouse 如此强悍的缘由已初见端倪,列式存储、向量化引擎和表引擎都是它的杀手锏。

Last Modified: July 4, 2023