ClickHouse简介
2020年下半年在OLAP领域有一匹黑马以席卷之势进入大数据开发者的领域,它就是ClickHouse。在2019年小编也曾介绍过ClickHouse,大家可以参考这里进行入门:
那么我们有必要先从全局了解一下ClickHouse到底是个什么样的数据库?
ClickHouse是一个开源的,面向列的分析数据库,由Yandex为OLAP和大数据用例创建。ClickHouse对实时查询处理的支持使其适用于需要亚秒级分析结果的应用程序。ClickHouse的查询语言是SQL的一种方言,它支持强大的声明性查询功能,同时为最终用户提供熟悉度和较小的学习曲线。
ClickHouse全称是Click Stream,Data Warehouse,简称ClickHouse就是基于页面的点击事件流,面向数据仓库进行OLAP分析。ClickHouse是一款开源的数据分析数据库,由战斗民族俄罗斯Yandex公司研发的,Yandex是做搜索引擎的,就类似与Google,百度等。
我们都知道搜索引擎的营收主要来源与流量和广告业务,所以搜索引擎公司会着重分析用户网路流量,像Google有Anlytics,百度有百度统计,那么Yandex就对应于Yandex.Metrica。ClickHouse就式在Yandex.Metrica下产生的技术。
面向列的数据库将记录存储在按列而不是行分组的块中。通过不加载查询中不存在的列的数据,面向列的数据库在完成查询时花费的时间更少。因此,对于某些工作负载(如OLAP),这些数据库可以比传统的基于行的系统更快地计算和返回结果。
为什么ClickHouse能够异军突起
优异的性能
根据官网的介绍(https://clickhouse.tech/benchmark/dbms/),ClickHouse在相同的服务器配置与数据量下,平均响应速度:
-
Vertica的2.63倍(Vertica是一款收费的列式存储数据库)
-
InfiniDB的17倍(可伸缩的分析数据库引擎,基于Mysql搭建)
-
MonetDB的27倍(开源的列式数据库)
-
Hive的126倍
-
MySQL的429倍
-
Greenplum的10倍
-
Spark的1倍
性能是衡量 OLAP 数据库的关键指标,我们可以通过 ClickHouse 官方测试结果感受下 ClickHouse 的极致性能,其中绿色代表性能最佳,红色代表性能较差,红色越深代表性能越弱。
我们在之前用了一篇文章《数据告诉你ClickHouse有多快》 详细讲解了 ClickHouse的优异性能表现。
优势和局限性
ClickHouse主要特点:
-
ROLAP(关系型的联机分析处理,和它一起比较的还有OLTP联机事务处理,我们常见的ERP,CRM系统就属于OLTP)
-
在线实时查询
-
完整的DBMS(关系数据库)
-
列式存储(区别与HBase,ClickHouse的是完全列式存储,HBase具体说是列族式存储)
-
不需要任何数据预处理
-
支持批量更新
-
拥有完善的SQl支持和函数
-
支持高可用(多主结构,在后面的结构设计中会讲到)
-
不依赖Hadoop复杂生态(像ES一样,开箱即用)
一些不足:
-
不支持事务(这其实也是大部分OLAP数据库的缺点)
-
不擅长根据主键按行粒度查询(但是支持这种操作)
-
不擅长按行删除数据(但是支持这种操作)
核心概念和原理
ClickHouse 采用了典型的分组式的分布式架构,集群架构如下图所示:
这其中的角色包括:
-
Shard :集群内划分为多个分片或分组(Shard 0 … Shard N),通过 Shard 的线性扩展能力,支持海量数据的分布式存储计算。
-
Node :每个 Shard 内包含一定数量的节点(Node,即进程),同一 Shard 内的节点互为副本,保障数据可靠。ClickHouse 中副本数可按需建设,且逻辑上不同 Shard 内的副本数可不同。
-
ZooKeeper Service :集群所有节点对等,节点间通过 ZooKeeper 服务进行分布式协调。
ClickHouse基础架构如下图所示:
-
Column与Field
Column和Field是ClickHouse数据最基础的映射单元。内存中的一列数据由一个Column对象表示。Column对象分为接口和实现两个部分,在IColumn接口对象中,定义了对数据进行各种关系运算的方法。在大多数场合,ClickHouse都会以整列的方式操作数据,但凡事也有例外。如果需要操作单个具体的数值 ( 也就是单列中的一行数据 ),则需要使用Field对象,Field对象代表一个单值。与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式。在Field对象内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。
-
DataType
数据的序列化和反序列化工作由DataType负责。IDataType接口定义了许多正反序列化的方法,它们成对出现。IDataType也使用了泛化的设计模式,具体方法的实现逻辑由对应数据类型的实例承载。DataType虽然负责序列化相关工作,但它并不直接负责数据的读取,而是转由从Column或Field对象获取。
-
Block与Block流
ClickHouse内部的数据操作是面向Block对象进行的,并且采用了流的形式。Block对象可以看作数据表的子集。Block对象的本质是由数据对象、数据类型和列名称组成的三元组,即Column、DataType及列名称字符串。仅通过Block对象就能完成一系列的数据操作。Block并没有直接聚合Column和DataType对象,而是通过ColumnWithTypeAndName对象进行间接引用。Block流操作有两组顶层接口:IBlockInputStream负责数据的读取和关系运算,IBlockOutputStream负责将数据输出到下一环节。IBlockInputStream接口定义了读取数据的若干个read虚方法,而具体的实现逻辑则交由它的实现类来填充。IBlockInputStream接口总共有60多个实现类,这些实现类大致可以分为三类:
-
第一类用于处理数据定义的DDL操作
-
第二类用于处理关系运算的相关操作
-
第三类则是与表引擎呼应,每一种表引擎都拥有与之对应的BlockInputStream实现
IBlockOutputStream的设计与IBlockInputStream如出一辙。这些实现类基本用于表引擎的相关处理,负责将数据写入下一环节或者最终目的地。
-
Table
在数据表的底层设计中并没有所谓的Table对象,它直接使用IStorage接口指代数据表。表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现。IStorage接口负责数据的定义、查询与写入。IStorage负责根据AST查询语句的指示要求,返回指定列的原始数据。后续的加工、计算和过滤则由下面介绍的部分进行。
-
Parser与Interpreter
Parser分析器负责创建AST对象;而Interpreter解释器则负责解释AST,并进一步创建查询的执行管道。它们与IStorage一起,串联起了整个数据查询的过程。Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析。Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行"业务逻辑" ( 例如分支判断、设置参数、调用接口等 );最终返回IBlock对象,以线程的形式建立起一个查询执行管道。
-
Functions 与Aggregate Functions
ClickHouse主要提供两类函数—普通函数(Functions)和聚合函数(Aggregate Functions)。普通函数由IFunction接口定义,拥有数十种函数实现,采用向量化的方式直接作用于一整列数据。聚合函数由IAggregateFunction接口定义,相比无状态的普通函数,聚合函数是有状态的。以COUNT聚合函数为例,其AggregateFunctionCount的状态使用整型UInt64记录。聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。
-
Cluster与Replication
ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成。这种分层的概念,在一些流行的分布式系统中十分普遍。这里有几个与众不同的特性。ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片、1副本,则至少需要部署2个服务节点。分片只是一个逻辑概念,其物理承载还是由副本承担的。
ClickHouse的核心特性
ClickHouse的特性有很多,一款被认可的OLAP引擎能够得到大家的频繁使用一定是有独特的特性,小编列举了几个ClickHouse异于常人的特性:
列式存储&数据压缩
按列存储与按行存储相比,前者可以有效减少查询时所需扫描的数据量,这一点可以用一个示例简单说明。假设一张数据表A拥有50个字段A1~A50,以及100行数据。现在需要查询前5个字段并进行数据分析,那么通过列存储,我们仅需读取必要的列数据,相比于普通行存,可减少 10 倍左右的读取、解压、处理等开销,对性能会有质的影响。
如果数据按行存储,数据库首先会逐行扫描,并获取每行数据的所有50个字段,再从每一行数据中返回A1~A5这5个字段。不难发现,尽管只需要前面的5个字段,但由于数据是按行进行组织的,实际上还是扫描了所有的字段。如果数据按列存储,就不会发生这样的问题。由于数据按列组织,数据库可以直接获取A1~A5这5列的数据,从而避免了多余的数据扫描。
按列存储相比按行存储的另一个优势是对数据压缩的友好性。ClickHouse的数据按列进行组织,属于同一列的数据会被保存在一起,列与列之间也会由不同的文件分别保存 ( 这里主要指MergeTree表引擎 )。数据默认使用LZ4算法压缩,在Yandex.Metrica的生产环境中,数据总体的压缩比可以达到8:1 ( 未压缩前17PB,压缩后2PB )。列式存储除了降低IO和存储的压力之外,还为向量化执行做好了铺垫。
向量化执行
坊间有句玩笑,即"能用钱解决的问题,千万别花时间"。而业界也有种调侃如出一辙,即"能升级硬件解决的问题,千万别优化程序"。有时候,你千辛万苦优化程序逻辑带来的性能提升,还不如直接升级硬件来得简单直接。这虽然只是一句玩笑不能当真,但硬件层面的优化确实是最直接、最高效的提升途径之一。向量化执行就是这种方式的典型代表,这项寄存器硬件层面的特性,为上层应用程序的性能带来了指数级的提升。
向量化执行,可以简单地看作一项消除程序中循环的优化。这里用一个形象的例子比喻。小胡经营了一家果汁店,虽然店里的鲜榨苹果汁深受大家喜爱,但客户总是抱怨制作果汁的速度太慢。小胡的店里只有一台榨汁机,每次他都会从篮子里拿出一个苹果,放到榨汁机内等待出汁。如果有8个客户,每个客户都点了一杯苹果汁,那么小胡需要重复循环8次上述的榨汁流程,才能榨出8杯苹果汁。如果制作一杯果汁需要5分钟,那么全部制作完毕则需要40分钟。为了提升果汁的制作速度,小胡想出了一个办法。他将榨汁机的数量从1台增加到了8台,这么一来,他就可以从篮子里一次性拿出8个苹果,分别放入8台榨汁机同时榨汁。此时,小胡只需要5分钟就能够制作出8杯苹果汁。为了制作n杯果汁,非向量化执行的方式是用1台榨汁机重复循环制作n次,而向量化执行的方式是用n台榨汁机只执行1次。
为了实现向量化执行,需要利用CPU的SIMD指令。SIMD的全称是Single Instruction Multiple Data,即用单条指令操作多条数据。现代计算机系统概念中,它是通过数据并行以提高性能的一种实现方式 ( 其他的还有指令级并行和线程级并行 ),它的原理是在CPU寄存器层面实现数据的并行操作。
在计算机系统的体系结构中,存储系统是一种层次结构。典型服务器计算机的存储层次结构如图1所示。一个实用的经验告诉我们,存储媒介距离CPU越近,则访问数据的速度越快。
从上图中可以看到,从左向右,距离CPU越远,则数据的访问速度越慢。从寄存器中访问数据的速度,是从内存访问数据速度的300倍,是从磁盘中访问数据速度的3000万倍。所以利用CPU向量化执行的特性,对于程序的性能提升意义非凡。
ClickHouse目前利用SSE4.2指令集实现向量化执行。
关系模型与SQL查询
相比HBase和Redis这类NoSQL数据库,ClickHouse使用关系模型描述数据并提供了传统数据库的概念 ( 数据库、表、视图和函数等 )。与此同时,ClickHouse完全使用SQL作为查询语言 ( 支持GROUP BY、ORDER BY、JOIN、IN等大部分标准SQL ),这使得它平易近人,容易理解和学习。因为关系型数据库和SQL语言,可以说是软件领域发展至今应用最为广泛的技术之一,拥有极高的"群众基础"。也正因为ClickHouse提供了标准协议的SQL查询接口,使得现有的第三方分析可视化系统可以轻松与它集成对接。在SQL解析方面,ClickHouse是大小写敏感的,这意味着SELECT a 和 SELECT A所代表的语义是不同的。
关系模型相比文档和键值对等其他模型,拥有更好的描述能力,也能够更加清晰地表述实体间的关系。更重要的是,在OLAP领域,已有的大量数据建模工作都是基于关系模型展开的 ( 星型模型、雪花模型乃至宽表模型 )。ClickHouse使用了关系模型,所以将构建在传统关系型数据库或数据仓库之上的系统迁移到ClickHouse的成本会变得更低,可以直接沿用之前的经验成果。
多样化的表引擎
也许因为Yandex.Metrica的最初架构是基于MySQL实现的,所以在ClickHouse的设计中,能够察觉到一些MySQL的影子,表引擎的设计就是其中之一。与MySQL类似,ClickHouse也将存储部分进行了抽象,把存储引擎作为一层独立的接口。截至本书完稿时,ClickHouse共拥有合并树、内存、文件、接口和其他6大类20多种表引擎。其中每一种表引擎都有着各自的特点,用户可以根据实际业务场景的要求,选择合适的表引擎使用。
通常而言,一个通用系统意味着更广泛的适用性,能够适应更多的场景。但通用的另一种解释是平庸,因为它无法在所有场景内都做到极致。
在软件的世界中,并不会存在一个能够适用任何场景的通用系统,为了突出某项特性,势必会在别处有所取舍。其实世间万物都遵循着这样的道理,就像信天翁和蜂鸟,虽然都属于鸟类,但它们各自的特点却铸就了完全不同的体貌特征。信天翁擅长远距离飞行,环绕地球一周只需要1至2个月的时间。因为它能够长时间处于滑行状态,5天才需要扇动一次翅膀,心率能够保持在每分钟100至200次之间。而蜂鸟能够垂直悬停飞行,每秒可以挥动翅膀70~100次,飞行时的心率能够达到每分钟1000次。如果用数据库的场景类比信天翁和蜂鸟的特点,那么信天翁代表的可能是使用普通硬件就能实现高性能的设计思路,数据按粗粒度处理,通过批处理的方式执行;而蜂鸟代表的可能是按细粒度处理数据的设计思路,需要高性能硬件的支持。
将表引擎独立设计的好处是显而易见的,通过特定的表引擎支撑特定的场景,十分灵活。对于简单的场景,可直接使用简单的引擎降低成本,而复杂的场景也有合适的选择。
多线程与分布式
ClickHouse几乎具备现代化高性能数据库的所有典型特征,对于可以提升性能的手段可谓是一一用尽,对于多线程和分布式这类被广泛使用的技术,自然更是不在话下。
如果说向量化执行是通过数据级并行的方式提升了性能,那么多线程处理就是通过线程级并行的方式实现了性能的提升。相比基于底层硬件实现的向量化执行SIMD,线程级并行通常由更高层次的软件层面控制。现代计算机系统早已普及了多处理器架构,所以现今市面上的服务器都具备良好的多核心多线程处理能力。由于SIMD不适合用于带有较多分支判断的场景,ClickHouse也大量使用了多线程技术以实现提速,以此和向量化执行形成互补。
如果一个篮子装不下所有的鸡蛋,那么就多用几个篮子来装,这就是分布式设计中分而治之的基本思想。同理,如果一台服务器性能吃紧,那么就利用多台服务的资源协同处理。为了实现这一目标,首先需要在数据层面实现数据的分布式。因为在分布式领域,存在一条金科玉律—计算移动比数据移动更加划算。在各服务器之间,通过网络传输数据的成本是高昂的,所以相比移动数据,更为聪明的做法是预先将数据分布到各台服务器,将数据的计算查询直接下推到数据所在的服务器。ClickHouse在数据存取方面,既支持分区 ( 纵向扩展,利用多线程原理 ),也支持分片 ( 横向扩展,利用分布式原理 ),可以说是将多线程和分布式的技术应用到了极致。
多主架构
HDFS、Spark、HBase和Elasticsearch这类分布式系统,都采用了Master-Slave主从架构,由一个管控节点作为Leader统筹全局。而ClickHouse则采用Multi-Master多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果。这种多主的架构有许多优势,例如对等的角色使系统架构变得更加简单,不用再区分主控节点、数据节点和计算节点,集群中的所有节点功能相同。所以它天然规避了单点故障的问题,非常适合用于多数据中心、异地多活的场景。
在线查询
ClickHouse经常会被拿来与其他的分析型数据库作对比,比如Vertica、SparkSQL、Hive和Elasticsearch等,它与这些数据库确实存在许多相似之处。例如,它们都可以支撑海量数据的查询场景,都拥有分布式架构,都支持列存、数据分片、计算下推等特性。这其实也侧面说明了ClickHouse在设计上确实吸取了各路奇技淫巧。与其他数据库相比,ClickHouse也拥有明显的优势。例如,Vertica这类商用软件价格高昂;SparkSQL与Hive这类系统无法保障90%的查询在1秒内返回,在大数据量下的复杂查询可能会需要分钟级的响应时间;而Elasticsearch这类搜索引擎在处理亿级数据聚合查询时则显得捉襟见肘。
正如ClickHouse的"广告词"所言,其他的开源系统太慢,商用的系统太贵,只有Clickouse在成本与性能之间做到了良好平衡,即又快又开源。ClickHouse当之无愧地阐释了"在线"二字的含义,即便是在复杂查询的场景下,它也能够做到极快响应,且无须对数据进行任何预处理加工。
数据分片与分布式查询
数据分片是将数据进行横向切分,这是一种在面对海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。ClickHouse支持分片,而分片则依赖集群。每个集群由1到多个分片组成,而每个分片则对应了ClickHouse的1个服务节点。分片的数量上限取决于节点数量 ( 1个分片只能对应1个服务节点 )。
ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse提供了本地表 ( Local Table ) 与分布式表 ( Distributed Table ) 的概念。一张本地表等同于一份数据的分片。而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
这种设计类似数据库的分库和分表,十分灵活。例如在业务系统上线的初期,数据体量并不高,此时数据表并不需要多个分片。所以使用单个节点的本地表 ( 单个数据分片 ) 即可满足业务需求,待到业务增长、数据量增大的时候,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。这就好比一辆手动挡赛车,它将所有的选择权都交到了使用者的手中。
简单入门
关于ClickHouse的安装,我们在这里不再详细展开,官网有详细的文档可以参考。
我们使用Java客户端连接ClickHouse进行一些简单的操作。首先Clickhouse 有两种 JDBC 驱动实现:
一种是官方给出的
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.2.4</version>
</dependency>
还有一种第三方提供的驱动:
<dependency>
<groupId>com.github.housepower</groupId>
<artifactId>clickhouse-native-jdbc</artifactId>
<version>2.5.2</version>
</dependency>
两者间的主要区别如下:
驱动类加载路径不同,分别为 ru.yandex.clickhouse.ClickHouseDriver 和 com.github.housepower.jdbc.ClickHouseDriver
默认连接端口不同,分别为 8123 和 9000
连接协议不同,官方驱动使用 HTTP 协议,而三方驱动使用 TCP 协议
需要注意的是,两种驱动不可共用,同个项目中只能选择其中一种驱动。
Class.forName("com.github.housepower.jdbc.ClickHouseDriver");
Connection connection = DriverManager.getConnection("jdbc:clickhouse://192.168.60.131:9000");
Statement statement = connection.createStatement();
statement.executeQuery("create table test.example(day Date, name String, age UInt8) Engine=Log");
PreparedStatement pstmt = connection.prepareStatement("insert into test.example values(?, ?, ?)");
// insert 10 records
for (int i = 0; i < 10; i++) {
pstmt.setDate(1, new Date(System.currentTimeMillis()));
pstmt.setString(2, "panda_" + (i + 1));
pstmt.setInt(3, 18);
pstmt.addBatch();
}
pstmt.executeBatch();
Statement statement = connection.createStatement();
String sql = "select * from test.jdbc_example";
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
// ResultSet 的下标值从 1 开始,不可使用 0,否则越界,报 ArrayIndexOutOfBoundsException 异常
System.out.println(rs.getDate(1) + ", " + rs.getString(2) + ", " + rs.getInt(3));
}
通过 clickhouse-client 命令行界面查看表情况:
ck-master :) show tables;
SHOW TABLES
┌─name─────────┐
│ hits │
│ jdbc_example │
└──────────────┘
ck-master :) select * from example;
SELECT *
FROM jdbc_example
┌────────day─┬─name─────┬─age─┐
│ 2019-04-25 │ panda_1 │ 18 │
│ 2019-04-25 │ panda_2 │ 18 │
│ 2019-04-25 │ panda_3 │ 18 │
│ 2019-04-25 │ panda_4 │ 18 │
│ 2019-04-25 │ panda_5 │ 18 │
│ 2019-04-25 │ panda_6 │ 18 │
│ 2019-04-25 │ panda_7 │ 18 │
│ 2019-04-25 │ panda_8 │ 18 │
│ 2019-04-25 │ panda_9 │ 18 │
│ 2019-04-25 │ panda_10 │ 18 │
└────────────┴──────────┴─────┘
企业级应用
从2019年起,已经有很多大厂开始在生产环境使用ClickHouse,小编在这里根据各大公司使用情况作了一些总结,适用场景、方案、优化等等。
ClickHouse在携程酒店数据智能平台的应用
下图是携程实际应用ClickHouse的架构图,底层数据大部分是离线的,一部分是实时的,离线数据现在大概有将近 3000 多个 job 每天都是把数据从 HIVE 拉到 ClickHouse 里面去,实时数据主要是接外部数据,然后批量写到 ClickHouse 里面。数据智能平台 80%以上的数据都在 ClickHouse 上面。
同时,携程还存在全量数据同步和增量数据同步的场景。
全量数据同步的流程流程如下图:
-
清空A_temp表,将最新的数据从Hive通过ETL导入到A_temp表
-
将A rename 成A_temp_temp
-
将A_temp rename成 A
-
将A_temp_temp rename成 A_tem
需要注意的是,ClickHouse每执行一个 insert 的时候会产生一个进程 ID,如果没有执行完,直接 Rename 就会造成数据丢失,数据就不对了,所以必须要有一个 job 在轮询看这边是不是执行完了,只有当 insert 的进程 id 执行完成后再做后面一系列的 rename。
增量的数据同步流程入下图所示:
-
清空A_temp表,将最近3个月的数据从Hive通过ETL导入到A_temp表
-
将A表中3个月之前的数据select into到A_temp表
-
将A rename 成A_temp_temp
-
将A_temp rename成 A
-
将A_temp_temp rename成 A_tem
以下是携程使用ClickHouse的经验:
1、数据导入之前要评估好分区字段
ClickHouse 因为是根据分区文件存储的,如果说你的分区字段真实数据粒度很细,数据导入的时候就会把你的物理机打爆。其实数据量可能没有多少,但是因为你用的字段不合理,会产生大量的碎片文件,磁盘空间就会打到底。
2、数据导入提前根据分区做好排序,避免同时写入过多分区导致 clickhouse 内部来不及 Merge
数据导入之前我们做好排序,这样可以降低数据导入后 ClickHouse 后台异步 Merge 的时候涉及到的分区数,肯定是涉及到的分区数越少服务器压力也会越小。
3、左右表 join 的时候要注意数据量的变化
再就是左右表 join 的问题,ClickHouse 它必须要大表在左边,小表在右边。但是我们可能某些业务场景跑着跑着数据量会返过来了,这个时候我们需要有监控能及时发现并修改这个 join 关系。
4、根据数据量以及应用场景评估是否采用分布式
分布式要根据应用场景来,如果你的应用场景向上汇总后数据量已经超过了单物理机的存储或者 CPU/内存瓶颈而不得不采用分布式 ClickHouse 也有很完善的 MPP 架构,但同时你也要维护好你的主 keyboard。
5、监控好服务器的 CPU/内存波动
再就是做好监控,我前面说过 ClickHouse 的 CPU 拉到 60%的时候,基本上你的慢查询马上就出来了,所以我这边是有对 CPU 和内存的波动进行监控的,类似于 dump,这个我们抓下来以后就可以做分析。
6、数据存储磁盘尽量采用 SSD
数据存储尽量用 SSD,因为我之前也开始用过机械硬盘,机械硬盘有一个问题就是当你的服务器要运维以后需要重启,这个时候数据要加载,我们现在单机数据量存储有超过了 200 亿以上,这还是我几个月前统计的。这个数据量如果说用机械硬盘的话,重启一次可能要等上好几个小时服务器才可用,所以尽量用 SSD,重启速度会快很多。
当然重启也有一个问题就是说会导致你的数据合并出现错乱,这是一个坑。所以我每次维护机器的时候,同一个集群我不会同时维护几台机器,我只会一台一台维护,A 机器好了以后会跟它的备用机器对比数据,否则机器起来了,但是数据不一定是对的,并且可能是一大片数据都是不对的。
7、减少数据中文本信息的冗余存储
要减少一些中文信息的冗余存储,因为中文信息会导致整个服务器的 IO 很高,特别是导数据的时候。
8、特别适用于数据量大,查询频次可控的场景,如数据分析、埋点日志系统
对于它的应用,我认为从成本角度来说,就像以前我们有很多业务数据的修改日志,大家开发的时候可能都习惯性的存到 MySQL 里面,但是实际上我认为这种数据非常适合于落到 ClickHouse 里面,比落到 MySQL 里面成本会更低,查询速度会更快。
Clickhouse在快手的应用
Clickhouse在QQ的应用
QQ音乐大数据团队基于ClickHouse+Superset等基础组件,结合腾讯云EMR产品的云端能力,搭建起高可用、低延迟的实时OLAP分析计算可视化平台。
集群日均新增万亿数据,规模达到上万核CPU,PB级数据量。整体实现秒级的实时数据分析、提取、下钻、监控数据基础服务,大大提高了大数据分析与处理的工作效率。
通过OLAP分析平台,极大降低了探索数据的门槛,做到全民BI,全民数据服务,实现了实时PV、UV、营收、用户圈层、热门歌曲等各类指标高效分析,全链路数据秒级分析定位,加强数据上报规范,形成一个良好的正循环。
面对上万核集群规模、PB级的数据量,经过QQ音乐大数据团队和腾讯云EMR双方技术团队无数次技术架构升级优化,性能优化,逐步形成高可用、高性能、高安全的OLAP计算分析平台。
(1)基于SSD盘的ZooKeeper
ClickHouse依赖于ZooKeeper实现分布式系统的协调工作,在ClickHouse并发写入量较大时,ZooKeeper对元数据存储处理不及时,会导致ClickHouse副本间同步出现延迟,降低集群整体性能。
解决方案:采用SSD盘的ZooKeeper大幅提高IO的性能,在表个数小于100,数据量级在TB级别时,也可采用HDD盘,其他情况都建议采用SSD盘。
(2)数据写入一致性
数据在写入ClickHouse失败重试后内容出现重复,导致了不同系统,如Hive离线数仓中分析结果,与ClickHouse集群中运算结果不一致。
解决方案:基于统一全局的负载均衡调度策略,完成数据失败后仍然可写入同一Shard,实现数据幂等写入,从而保证在ClickHouse中数据一致性。
(3)实时离线数据写入
ClickHouse数据主要来自实时流水上报数据和离线数据中间分析结果数据,如何在架构中完成上万亿基本数据的高效安全写入,是一个巨大的挑战。
解决方案:基于Tube消息队列,完成统一数据的分发消费,基于上述的一致性策略实现数据幂同步,做到实时和离线数据的高效写入。
(4)表分区数优化
部分离线数据仓库采用按小时落地分区,如果采用原始的小时分区更新同步,会造成ClickHouse中Select查询打开大量文件及文件描述符,进而导致性能低下。
解决方案:ClickHouse官方也建议,表分区的数量建议不超过10000,上述的数据同步架构完成小时分区转换为天分区,同时程序中完成数据幂等消费。
(5)读/写分离架构
频繁的写动作,会消耗大量CPU/内存/网卡资源,后台合并线程得不到有效资源,降低Merge Parts速度,MergeTree构建不及时,进而影响读取效率,导致集群性能降低。
解决方案:ClickHouse临时节点预先完成数据分区文件构建,动态加载到线上服务集群,缓解ClickHouse在大量并发写场景下的性能问题,实现高效的读/写分离架构,具体步骤和架构如下:
a)利用K8S的弹性构建部署能力,构建临时ClickHouse节点,数据写入该节点完成数据的Merge、排序等构建工作;
b)构建完成数据按MergeTree结构关联至正式业务集群。
当然对一些小数据量的同步写入,可采用10000条以上批量的写入。
(6)跨表查询本地化
在ClickHouse集群中跨表进行Select查询时,采用Global IN/Global Join语句性能较为低下。分析原因,是在此类操作会生成临时表,并跨设备同步该表,导致查询速度慢。
解决方案:采用一致性hash,将相同主键数据写入同一个数据分片,在本地local表完成跨表联合查询,数据均来自于本地存储,从而提高查询速度。
这种优化方案也有一定的潜在问题,目前ClickHouse尚不提供数据的Reshard能力,当Shard所存储主键数据量持续增加,达到磁盘容量上限需要分拆时,目前只能根据原始数据再次重建CK集群,有较高的成本。
ClickHouse从进入大众视野到开始广泛应用实践并不久,ClickHouse社区也在飞速发展中。ClickHouse仍然年轻,虽然在某些方面存在不足,但极致性能的存储引擎,使得ClickHouse成为一个非常优秀的存储底座。
趣头条实战 | 基于Flink+ClickHouse构建实时数据平台