文章目录
1、MPP数据库架构介绍
MPP(Massively Parallel Processing)就是大规模并行处理.
1、MPP的基本介绍
MPP (Massively Parallel Processing),即大规模并行处理,在数据库非共享集群中,每个节点都有独立的磁盘存储系统和内存系统,业务数据根据数据库模型和应用特点划分到各个节点上,每台数据节点通过专用网络或者商业通用网络互相连接,彼此协同计算,作为整体提供数据库服务。非共享数据库集群有完全的可伸缩性、高可用、高性能、优秀的性价比、资源共享等优势。
简单来说,MPP是将任务并行的分散到多个服务器和节点上,在每个节点上计算完成后,将各自部分的结果汇总在一起得到最终的结果(与Hadoop相似)。
2、MPP数据库架构模型介绍
3、 MPP架构特征
任务并行执行;
● 数据分布式存储(本地化);
● 分布式计算;
● 私有资源;
● 横向扩展;
● Shared Nothing架构。
4、常见的MPP架构的数据库介绍
常见的MPP架构的数据库也有很多,例如像Greenplum,Elasticsearch,Presto,clickhouse等
5、MPP与hadoop的区别
https://blog.youkuaiyun.com/yimenglin/article/details/90510134
2、ClickHouse概述
1、ClickHouse基本简介
ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库管理系统(DBMS:Database Management System),简称CK。
主要用于在线分析处理查询(OLAP:Online Analytical Processing)
能够使用SQL查询实时生成分析数据报表
ClickHouse官方文档:https://clickhouse.tech/docs/en/
2、ClickHouse的特性
真正的面向列的DBMS
数据压缩
磁盘存储的数据
多核并行处理
多个服务器上分布式处理
SQL支持
向量化引擎
实时数据更新
索引
支持在线查询
支持近似计算
数据复制和对数据完整性的支持
ClickHouse的不完美
没有完整的事务支持
缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量修改或删除数据
稀疏索引使得clickhouse不适合通过其键检索单行的点查询
3、clickhouse的应用场景
OLAP场景关键特征:
大多数是读请求
数据总是以相当大的批(> 1000 rows)进行写入
不修改已添加的数据
每次查询都从数据库中读取大量的行,但是同时又仅需要少量的列
宽表,即每个表包含着大量的列
较少的查询(通常每台服务器每秒数百个查询或更少)
对于简单查询,允许延迟大约50毫秒
列中的数据相对较小: 数字和短字符串(例如,每个UR60个字节)
处理单个查询时需要高吞吐量(每个服务器每秒高达数十亿行)
事务不是必须的
对数据一致性要求低
每一个查询除了一个大表外都很小
查询结果明显小于源数据,换句话说,数据被过滤或聚合后能够被盛放在单台服务器的内存中
应用场景:
用于结构良好清晰且不可变的事件或日志流分析。
不适合的场景:
事务性工作(OLTP),高请求率的键值访问,低延迟的修改或删除已存在数据,Blob或文档存储,超标准化数据。
3、ClickHouse的安装部署
clickhouse官网并没有提供二进制或rpm安装包,但是Altinity公司提供了对应的rpm安装包,可以通过rpm方式去安装。
第一步:下载rpm包
下载地址:https://packagecloud.io/Altinity/clickhouse
第二步:上传rpm包
上传rpm包到服务器node01、node02、node03服务器上的/kkb/soft路径下
第三步:安装rpm包
每台节点安装下面的2个依赖
sudo yum install -y libtool
sudo yum install -y *unixODBC*
每台节点安装ck服务
cd /kkb/soft/
sudo rpm -ivh clickhouse*rpm
第四步:修改配置文件
修改config.xml
三台机器都修改配置文件
node01执行以下命令修改config.xml
sudo vim /etc/clickhouse-server/config.xml
将这一行注释文件给打开
<listen_host>::</listen_host>
将配置文件拷贝到其他机器上面去
sudo scp /etc/clickhouse-server/config.xml node02:/etc/clickhouse-server/
sudo scp /etc/clickhouse-server/config.xml node03:/etc/clickhouse-server/
创建metrika.xml
node01服务器创建metrika.xml
node01服务器的/etc 目录下新建metrika.xml文件
sudo vim /etc/metrika.xml
<yandex>
<!-- 集群配置 -->
<clickhouse_remote_servers>
<bip_ck_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node01</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node02</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node03</host>
<port>9000</port>
</replica>
</shard>
</bip_ck_cluster>
</clickhouse_remote_servers>
<!-- 本节点副本,不同的机器配置不同 -->
<macros>
<replica>node01</replica>
</macros>
<!-- ZK -->
<zookeeper-servers>
<node index="1">
<host>node01</host>
<port>2181</port>
</node>
<node index="2">
<host>node02</host>
<port>2181</port>
</node>
<node index="3">
<host>node03</host>
<port>2181</port>
</node>
</zookeeper-servers>
<!-- 监听网络 -->
<networks>
<ip>::/0</ip>
</networks>
<!-- 数据压缩算法 -->
<clickhouse_compression>
<case>
<min_part_size>10000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<method>lz4</method>
</case>
</clickhouse_compression>
</yandex>
node02服务器创建metrika.xml
node02服务器的/etc 目录下新建metrika.xml文件
sudo vim /etc/metrika.xml
<yandex>
<!-- 集群配置 -->
<clickhouse_remote_servers>
<bip_ck_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node01</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node02</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node03</host>
<port>9000</port>
</replica>
</shard>
</bip_ck_cluster>
</clickhouse_remote_servers>
<!-- 本节点副本,不同的机器配置不同 -->
<macros>
<replica>node02</replica>
</macros>
<!-- ZK -->
<zookeeper-servers>
<node index="1">
<host>node01</host>
<port>2181</port>
</node>
<node index="2">
<host>node02</host>
<port>2181</port>
</node>
<node index="3">
<host>node03</host>
<port>2181</port>
</node>
</zookeeper-servers>
<!-- 监听网络 -->
<networks>
<ip>::/0</ip>
</networks>
<!-- 数据压缩算法 -->
<clickhouse_compression>
<case>
<min_part_size>10000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<method>lz4</method>
</case>
</clickhouse_compression>
</yandex>
node03服务器创建metrika.xml
node03服务器的/etc 目录下新建metrika.xml文件
sudo vim /etc/metrika.xml
<yandex>
<!-- 集群配置 -->
<clickhouse_remote_servers>
<bip_ck_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node01</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node02</host>
<port>9000</port>
</replica>
</shard>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>node03</host>
<port>9000</port>
</replica>
</shard>
</bip_ck_cluster>
</clickhouse_remote_servers>
<!-- 本节点副本,不同的机器配置不同 -->
<macros>
<replica>node03</replica>
</macros>
<!-- ZK -->
<zookeeper-servers>
<node index="1">
<host>node01</host>
<port>2181</port>
</node>
<node index="2">
<host>node02</host>
<port>2181</port>
</node>
<node index="3">
<host>node03</host>
<port>2181</port>
</node>
</zookeeper-servers>
<!-- 监听网络 -->
<networks>
<ip>::/0</ip>
</networks>
<!-- 数据压缩算法 -->
<clickhouse_compression>
<case>
<min_part_size>10000000000</min_part_size>
<min_part_size_ratio>0.01</min_part_size_ratio>
<method>lz4</method>
</case>
</clickhouse_compression>
</yandex>
给予权限
§ 说明:以下两个目录在安装完ck,且没有开始使用时,是不存在的
§ 如果后边在使用的过程中,出现权限问题,再给每台机器的目录授权(避免后面操作异常)
sudo chmod -R 777 /var/lib/clickhouse/data
sudo chmod -R 777 /var/lib/clickhouse/metadata
§ 搭建clickhouse过程中会生成对应的目录
配置文件路径:/etc/clickhouse-server/config.xml
日志文件路径:/var/log/clickhouse-server/
建表信息路径:/var/lib/clickhouse/metadata
分区数据路径:/var/lib/clickhouse/data
clickHouse默认是数据单副本的存在,这种情况下如果其中一台服务器宕机,那么就会造成数据丢失的问题,clickHouse也可以数据多副本的方式来实现对外提供高可用
https://www.cnblogs.com/grapelet520/p/11642273.html
3.2 ClickHouse的单机版安装部署
1.新建一个节点,上传四个rpm包。
2.安装
#安装依赖
sudo yum install -y libtool
sudo yum install -y *unixODBC*
#如果报错的话,安装以下依赖
sudo yum install libicu.x86_64
#安装ck服务
cd /kkb/soft/
sudo rpm -ivh clickhouse*rpm
4、clickHouse的启动与停止
ClickHouse的启动
先启动zookeeper集群
再在三台机器上面运行以下命令来启动clickHouse服务
sudo service clickhouse-server start
可以通过 ps -ef | grep clickhouse 命令查看ck是否启动成功
ps -ef | grep clickhouse
连接客户端
[hadoop@node01 bin]$ clickhouse-client
node01.kaikeba.com :) show databases;
SHOW DATABASES
┌─name────┐
│ default │
│ system │
└─────────┘
2 rows in set. Elapsed: 0.006 sec.
ClickHouse的服务停止
三台节点上执行命令
sudo service clickhouse-server stop
5、 命令行客户端连接ClickHouse
通过命令行来访问 ClickHouse,您可以使用 clickhouse-client
语法格式示例
clickhouse-client -m -u [username] -h [ip] --password [password] --port [port]
--user ``或者`` -u
用户名。 默认值: default。
--password
密码。 默认值:空字符串。
--host ``或者`` -h
服务端的 host 名称, 默认是 ‘localhost’
--port
连接的端口,默认值: 9000。
--multiline ``或者`` -m
如果指定,允许多行语句查询(Enter 仅代表换行,不代表查询语句完结)。
演示:开启多行查询
clickhouse-client -m 或者 clickhouse-client -multiline
6、数据类型
官网文档 https://clickhouse.tech/docs/en/sql-reference/data-types/
1 整型
固定长度的整型,包括有符号整型或无符号整型
· 整型范围
Int8 :[-128 : 127]
Int16:[-32768 : 32767]
Int32:[-2147483648 : 2147483647]
Int64:[-9223372036854775808 : 9223372036854775807]
Int256
· 无符号整型范围
UInt8 : [0 : 255]
UInt16: [0 : 65535]
UInt32: [0 : 4294967295]
UInt64: [0 : 18446744073709551615]
UInt256
2 浮点型
· Float32: float
· Float64: double
建议尽可能以整数形式存储数据。
例如,将固定精度的数字转换为整数值,如时间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差。
node01 :) select 1-0.9;
SELECT 1 - 0.9
┌───────minus(1, 0.9)─┐
│ 0.09999999999999998 │
└─────────────────────┘
· 与标准SQL相比,ClickHouse 支持以下类别的浮点数:
o inf 正无穷
node01 :) select 1/0;
SELECT 1 / 0
┌─divide(1, 0)─┐
│ inf │
└──────────────┘
o -inf 负无穷
node01 :) select -1/0;
SELECT -1 / 0
┌─divide(-1, 0)─┐
│ -inf │
└───────────────┘
o NaN 非数字
node01 :) select 0/0;
SELECT 0 / 0
┌─divide(0, 0)─┐
│ nan │
└──────────────┘
3 布尔型
· 没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1。
4 字符串
· String
o 字符串可以任意长度的。它可以包含任意的字节集,包含空字节。
· FixedString(N)
o 固定长度 N 的字符串,N 必须是严格的正自然数。
o 当向ClickHouse中插入数据时
§ 如果字符串包含的字节数少于N
, 将对字符串末尾进行空字节填充。
§ 如果字符串包含的字节数大于N
, 将抛出Too large value for FixedString(N)
异常。
o 与String相比,极少会使用FixedString,因为使用起来不是很方便。
5 枚举类型
· 包括 Enum8 和 Enum16 类型。Enum 保存 ‘string’= integer 的对应关系。
o Enum8 用 ‘String’= Int8 对描述。
o Enum16 用 ‘String’= Int16 对描述。
· 用法演示
o 创建一个带有一个枚举 Enum8('hello' = 1, 'world' = 2)
类型的列:
§ 客户端连接server端命令(–multiline表示开启多行查询)
§ clickhouse-client --multiline
CREATE TABLE enum_table(
x Enum8('hello' = 1, 'world' = 2)
)ENGINE = TinyLog;
o 这个 x
列只能存储类型定义中列出的值:'hello'
或'world'
。如果您尝试保存任何其他值,ClickHouse 抛出异常。
--插入数据
node01 :) INSERT INTO enum_table VALUES ('hello'), ('world'), ('hello');
--插入非'hello'或者'world'的数据
node01 :) INSERT INTO enum_table VALUES ('a');
INSERT INTO enum_table VALUES
Exception on client:
Code: 49. DB::Exception: Unknown element 'a' for type Enum8('hello' = 1, 'world' = 2)
o 从表中查询数据时,ClickHouse 从 Enum
中输出字符串值
node01 :) select * from enum_table;
SELECT *
FROM enum_table
┌─────x─┐
│ hello │
│ world │
│ hello │
└───────┘
o 如果需要看到对应行的数值,则必须将 Enum
值转换为整数类型。
node01 :) SELECT CAST(x, 'Int8') FROM enum_table ;
SELECT CAST(x AS Int8)
FROM enum_table
┌─CAST(x, \'Int8\')─┐
│ 1 │
│ 2 │
│ 1 │
└───────────────────┘
6 数组
Array(T)
由 `T` 类型元素组成的数组。
`T` 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ClickHouse 对多维数组的支持有限。例如,不能存储在 `MergeTree` 表中存储多维数组。
创建数组
可以使用array函数来创建数组
array[T]
也可以使用方括号
案例
node01 :) SELECT array(1, 2) AS x, toTypeName(x) ;
SELECT
[1, 2] AS x,
toTypeName(x)
┌─x─────┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8) │
└───────┴─────────────────────────┘
node01 :) SELECT [1.1, 2.2] AS x, toTypeName(x);
SELECT
[1.1, 2.2] AS x,
toTypeName(x)
┌─x─────────┬─toTypeName([1.1, 2.2])─┐
│ [1.1,2.2] │ Array(Float64) │
└───────────┴────────────────────────┘
7 元组
· Tuple(T1, T2, …):元组,其中每个元素都有单独的类型。
· 创建元组
o 可以使用函数来创建元组
tuple(T1, T2, ...)
· 案例
node01 :) SELECT tuple(1,'a') AS x, toTypeName(x);
SELECT
(1, 'a') AS x,
toTypeName(x)
┌─x───────┬─toTypeName(tuple(1, \'a\'))─┐
│ (1,'a') │ Tuple(UInt8, String) │
└─────────┴─────────────────────────────┘
8 Date日期
· 日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值
CREATE TABLE dt
(
`timestamp` Date,
`event_id` UInt8
)
ENGINE = TinyLog;
INSERT INTO dt Values (1546300800, 1), ('2019-01-01', 2);
SELECT * FROM dt;
9 DateTime时间戳
· 时间戳类型。用四个字节(无符号的)存储 Unix 时间戳)。
· 允许存储与日期类型相同的范围内的值。最小值为 0000-00-00 00:00:00。时间戳类型值精确到秒
CREATE TABLE dtt
(
`timestamp` DateTime('Europe/Moscow'),
`event_id` UInt8
)
ENGINE = TinyLog;
INSERT INTO dtt Values (1546300800, 1), ('2019-01-01 00:00:00', 2);
SELECT * FROM dtt;
还有很多数据结构,可以参考官方文档:https://clickhouse.yandex/docs/zh/data_types/
7、表引擎
· 官网参考文档:https://clickhouse.tech/docs/en/engines/table-engines/
· 表引擎(即表的类型)决定了:
o 数据的存储方式和位置,写到哪里以及从哪里读取数据
o 支持哪些查询以及如何支持。
o 并发数据访问。
o 索引的使用(如果存在)。
o 是否可以执行多线程请求。
o 数据复制参数。
ClickHouse的表引擎有很多,下面介绍其中几种,对其他引擎有兴趣的可以去查阅官方文档:https://clickhouse.yandex/docs/zh/operations/table_engines/
1 TinyLog
· 最简单的表引擎,用于将数据存储在磁盘上。每列都存储在单独的压缩文件中。写入时,数据将附加到文件末尾。
· 该引擎没有并发控制
o 如果同时从表中读取和写入数据,则读取操作将抛出异常;
o 如果同时写入多个查询中的表,则数据将被破坏。
这种表引擎的典型用法是 write-once:首先只写入一次数据,然后根据需要多次读取。此引擎适用于相对较小的表(建议最多1,000,000行)。如果有许多小表,则使用此表引擎是适合的,因为它比Log引擎更简单(需要打开的文件更少)。当您拥有大量小表时,可能会导致性能低下,不支持索引。
· 案例
o 创建一个TinyLog引擎的表并插入一条数据
create table f1 (a UInt16, b String) ENGINE=TinyLog;
insert into f1 (a, b) values (1, 'abc');
o 此时我们到保存数据的目录/var/lib/clickhouse/data/default/f1中可以看到如下目录结构:
[root@node01 f1]# ll
-rw-rw-rw- 1 clickhouse clickhouse 28 Feb 18 21:58 a.bin
-rw-rw-rw- 1 clickhouse clickhouse 30 Feb 18 21:58 b.bin
-rw-rw-rw- 1 clickhouse clickhouse 60 Feb 18 21:58 sizes.json
其中a.bin 和 b.bin 是压缩过的对应的列的数据,
sizes.json 中记录了每个 *.bin 文件的大小
[root@node01 f1]# cat sizes.json
{"yandex":{"a%2Ebin":{"size":"28"},"b%2Ebin":{"size":"30"}}}
2 Memory
· 内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过10G/s)。
· 一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。
3 Merge
· Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存储数据,但可用于同时从任意多个其他的表中读取数据。 读是自动并行的,不支持写入。读取时,那些被真正读取到数据的表的索引(如果有的话)会被使用。
· Merge 引擎的参数:一个数据库名和一个用于匹配表名的正则表达式。
· 案例
o 先建t1,t2,t3三个表,然后用 Merge 引擎的 tt 表再把它们链接起来
–创建表
create table t1 (id UInt16, name String) ENGINE=TinyLog;
create table t2 (id UInt16, name String) ENGINE=TinyLog;
create table t3 (id UInt16, name String) ENGINE=TinyLog;
–向表中插入数据
insert into t1(id, name) values (1, ‘first’);
insert into t2(id, name) values (2, ‘second’);
insert into t3(id, name) values (3, ‘three’);
–创建t表 ,把defalut数据库中所有以t开头的表连接起来
create table t(id UInt16, name String) ENGINE=Merge(currentDatabase(), ‘^t’);
node01 😃 select * from t;
SELECT *
FROM t
┌─id─┬─name──┐
│ 1 │ first │
└────┴───────┘
┌─id─┬─name───┐
│ 2 │ second │
└────┴────────┘
┌─id─┬─name──┐
│ 3 │ three │
└────┴───────┘
4 MergeTree
· Clickhouse 中最强大的表引擎当属 MergeTree
(合并树)引擎及该系列(MergeTree)中的其他引擎。
· MergeTree
引擎系列的基本理念如下。当你有巨量数据要插入到表中,你要高效地一批批写入数据片段,并希望这些数据片段在后台按照一定规则合并。相比在插入时不断修改(重写)数据进存储,这种策略会高效很多。
· 语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE [=] MergeTree(date-column [, sampling_expression], (primary, key), index_granularity)
l date-column:类型为 Date 的列名。ClickHouse 会自动依据这个列创建分区。
l sampling_expression :采样表达式。
l (primary, key) :主键。类型为Tuple(),主键可以是任意表达式构成的元组(通常是列名称的元组).
l index_granularity :索引粒度。即索引中相邻”标记”间的数据行数。设为 8192 可以适用大部分场景。
案例:
--创建基于MergeTree的引擎表
create table mt_table (date Date, id UInt8, name String) ENGINE=MergeTree(date, (id, name), 8192);
--插入数据
insert into mt_table values ('2019-05-01', 1, 'zhangsan');
insert into mt_table values ('2019-06-01', 2, 'lisi');
insert into mt_table values ('2019-05-03', 3, 'wangwu');
--查询表
node01 :) select * from mt_table;
SELECT *
FROM mt_table
┌───────date─┬─id─┬─name───┐
│ 2019-05-03 │ 3 │ wangwu │
└────────────┴────┴────────┘
┌───────date─┬─id─┬─name─────┐
│ 2019-05-01 │ 1 │ zhangsan │
└────────────┴────┴──────────┘
┌───────date─┬─id─┬─name─┐
│ 2019-06-01 │ 2 │ lisi │
└────────────┴────┴──────┘
o 在/var/lib/clickhouse/data/default/mt_table目录下可以看到信息
[root@node01 mt_table]# ll
drwxrwxrwx 2 clickhouse clickhouse 157 Feb 19 12:51 20190501_20190501_2_2_0
drwxrwxrwx 2 clickhouse clickhouse 157 Feb 19 12:51 20190503_20190503_6_6_0
drwxrwxrwx 2 clickhouse clickhouse 157 Feb 19 12:51 20190601_20190601_4_4_0
drwxrwxrwx 2 clickhouse clickhouse 6 Feb 19 12:51 detached
o 进入到20190501_20190501_2_2_0目录中
[root@node01 20190501_20190501_2_2_0]# ll
-rw-rw-rw- 1 clickhouse clickhouse 255 Feb 19 12:51 checksums.txt
-rw-rw-rw- 1 clickhouse clickhouse 74 Feb 19 12:51 columns.txt
-rw-rw-rw- 1 clickhouse clickhouse 28 Feb 19 12:51 date.bin
-rw-rw-rw- 1 clickhouse clickhouse 16 Feb 19 12:51 date.mrk
-rw-rw-rw- 1 clickhouse clickhouse 27 Feb 19 12:51 id.bin
-rw-rw-rw- 1 clickhouse clickhouse 16 Feb 19 12:51 id.mrk
-rw-rw-rw- 1 clickhouse clickhouse 35 Feb 19 12:51 name.bin
-rw-rw-rw- 1 clickhouse clickhouse 16 Feb 19 12:51 name.mrk
-rw-rw-rw- 1 clickhouse clickhouse 10 Feb 19 12:51 primary.idx
##其中
*.bin是按列保存数据的文件
*.mrk保存块偏移量
primary.idx保存主键索引
5 ReplacingMergeTree
这个引擎是在 MergeTree 的基础上,添加了“处理重复数据”的功能,该引擎和MergeTree的不同之处在于它会删除具有相同主键的重复项。
数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。因此,ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。
n 语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE [=] ReplacingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, [ver])
--可以看出他比MergeTree只多了一个ver
--这个ver指代版本列,它和时间一起配置用来区分哪条数据是最新的。
--如果指定了ver,则保留ver值最大的版本;若没指定,则保留最后一条
· 案例
--创建一个基于ReplacingMergeTree引擎的表
create table rmt_table (date Date, id UInt8, name String,point UInt8) ENGINE= ReplacingMergeTree(date, (id, name), 8192,point);
--插入数据
insert into rmt_table values ('2019-07-10', 1, 'a', 20);
insert into rmt_table values ('2019-07-10', 1, 'a', 30);
insert into rmt_table values ('2019-07-11', 1, 'a', 20);
insert into rmt_table values ('2019-07-11', 1, 'a', 30);
insert into rmt_table values ('2019-07-11', 1, 'a', 10);
--等待一段时间或者使用 optimize table rmt_table 命令手动触发merge
--查询数据(返回去重之后最新的数据)
node01 :) select * from rmt_table;
SELECT *
FROM rmt_table
┌───────date─┬─id─┬─name─┬─point─┐
│ 2019-07-11 │ 1 │ a │ 30 │
└────────────┴────┴──────┴───────┘
6 SummingMergeTree
l 该引擎继承自 MergeTree。
l 区别在于: 当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值。
l 在 merge 时,主键相同的行,被指定列的值会相加,没有被指定的列会取最先出现的值
语法:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE [=] SummingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, [columns])
o columns :包含将要被汇总的列的列名的元组;所选的列必须是数值类型,并且不可位于主键中
· 案例
----创建一个基于SummingMergeTree引擎的表
create table smt_table (date Date, name String, a UInt16, b UInt16) ENGINE=SummingMergeTree(date, (date, name), 8192, (a));
--插入数据
insert into smt_table (date, name, a, b) values ('2019-07-10', 'a', 1, 2);
insert into smt_table (date, name, a, b) values ('2019-07-10', 'b', 2, 1);
insert into smt_table (date, name, a, b) values ('2019-07-11', 'b', 3, 7);
insert into smt_table (date, name, a, b) values ('2019-07-11', 'b', 3, 8);
insert into smt_table (date, name, a, b) values ('2019-07-11', 'a', 3, 1);
insert into smt_table (date, name, a, b) values ('2019-07-12', 'c', 1, 3);
----等待一段时间或者使用 optimize table smt_table 命令手动触发merge
--查询数据
node01 :) select * from smt_table;
SELECT *
FROM smt_table
┌───────date─┬─name─┬─a─┬─b─┐
│ 2019-07-10 │ a │ 1 │ 2 │
│ 2019-07-10 │ b │ 2 │ 1 │
│ 2019-07-11 │ a │ 3 │ 1 │
│ 2019-07-11 │ b │ 6 │ 7│
│ 2019-07-12 │ c │ 1 │ 3 │
└────────────┴──────┴───┴───┘
--发现2019-07-11,b的a列合并相加了,b列取了7(因为b列为7的数据最先插入)
7 Distributed
· 分布式引擎,本身不存储数据, 但可以在多个服务器上进行分布式查询。 读是自动并行的。读取时,远程服务器表的索引(如果有的话)会被使用。
· 语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE [=] Distributed(cluster_name, database, table [, sharding_key])
o 参数解析:
§ cluster_name :服务器配置文件中的集群名,在/etc/metrika.xml中配置的
§ database :数据库名
§ table :表名
§ sharding_key :数据分片键
· 案例:
(1)在node01,node02,node03上分别创建一个表 kaikeba
create table kaikeba(id UInt16, name String) ENGINE=TinyLog;
(2)在三台机器的 kaikeba 表中插入一些数据
insert into kaikeba (id, name) values (1, 'zhangsan');
insert into kaikeba (id, name) values (2, 'lisi');
(3)在 node01上创建分布式表
node01 :) create table distributed_table(id UInt16, name String) ENGINE=Distributed(bip_ck_cluster, default, kaikeba, id);
(4)查询 distributed_table 表的数据
node01 :) select * from distributed_table;
SELECT *
FROM distributed_table
┌─id─┬─name─────┐
│ 2 │ lisi │
│ 1 │ zhangsan │
└────┴──────────┘
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
└────┴──────────┘
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
└────┴──────────┘
(5)向distributed_table插入数据
insert into distributed_table select * from kaikeba;
node01 :) select * from distributed_table;
SELECT *
FROM distributed_table
┌─id─┬─name─────┐
│ 2 │ lisi │
│ 1 │ zhangsan │
└────┴──────────┘
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
│ 2 │ lisi │
└────┴──────────┘
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
│ 1 │ zhangsan │
└────┴──────────┘
--可以看到每个节点分布的数据差不多
· 总结
分布式表实际上是Clickhouse集群本地表的一种“视图”。对分布式表的SELECT查询,会利用集群所有分片资源进行执行。你可以配置多个集群,并创建多个分布式表,给不同的集群提供视图
8、SQL语法
1 create
1.1 create database
· 用于创建指定名称的数据库,语法如下:
CREATE DATABASE [IF NOT EXISTS] db_name
· 演示
create database if not exists test;
1.2 create table
· 对于创建表,语法如下
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = engine
--DEFAULT expr : 默认值,用法与SQL类似。
--MATERIALIZED expr :物化表达式,被该表达式指定的列不能被INSERT,因为它总是被计算出来的。 对于INSERT而言,不需要考虑这些列。 另外,在SELECT查询中如果包含星号,此列不会被查询。
--ALIAS expr :别名。
· 创建表的三种方式
(1)直接创建
node01 :) create table t_demo1(id UInt16,name String) engine=TinyLog;
(2)创建一个与其他表具有相同结构的表
语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name AS [db2.]name2 [ENGINE = engine]
演示
node01 :) create table t_demo2 as t_demo1 engine=Memory;
node01 :) desc t_demo2;
DESCRIBE TABLE t_demo2
┌─name─┬─type───┬─default_type─┬─default_expression─┐
│ id │ UInt16 │ │ │
│ name │ String │ │ │
└──────┴────────┴──────────────┴────────────────────┘
(3)使用指定的引擎创建一个与SELECT子句的结果具有相同结构的表,并使用SELECT子句的结果填充它。
语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name ENGINE = engine AS SELECT ..
演示
--向t_demo1表中添加数据
insert into t_demo1 values(1,'zhangsan'),(2,'lisi'),(3,'wangwu');
create table t_demo3 engine=TinyLog as select * from t_demo1;
--查询表数据
node01 :) select * from t_demo3;
SELECT *
FROM t_demo3
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
│ 3 │ wangwu │
└────┴──────────┘
2 insert into
insert主要用于向系统中添加数据.
语法:
INSERT INTO [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23), ...
演示
create table t_demo4(id UInt16,name String) engine=TinyLog;
insert into t_demo4 values(1,'zhangsan'),(2,'lisi');
--查询
node01 :) select * from t_demo4;
SELECT *
FROM t_demo4
┌─id─┬─name─────┐
│ 1 │ zhangsan │
│ 2 │ lisi │
└────┴──────────┘
注意
clickhouse不支持的其他用于修改数据的查询:UPDATE, DELETE, REPLACE, MERGE, UPSERT, INSERT UPDATE。 但是可以使用 ALTER TABLE ... DROP PARTITION查询来删除一些旧的数据。
3 alter
ALTER只支持MergeTree系列,Merge和Distributed引擎的表
语法
ALTER TABLE [db].name [ON CLUSTER cluster] ADD|DROP|MODIFY COLUMN ...
参数解析
ADD COLUMN 向表中添加新列
DROP COLUMN 在表中删除列
MODIFY COLUMN 更改列的类型
· 演示
(1)创建一个MergerTree引擎的表
create table mt_table_demo1 (date Date, id UInt8, name String) ENGINE=MergeTree(date, (id, name), 8192);
(2)向表中插入数据
insert into mt_table_demo1 values ('2019-05-01', 1, 'zhangsan');
insert into mt_table_demo1 values ('2019-06-01', 2, 'lisi');
insert into mt_table_demo1 values ('2019-05-03', 3, 'wangwu');
(3)新增age列
--添加age列
node01 :) alter table mt_table_demo1 add column age UInt8;
--查看表结构
node01 :) desc mt_table_demo1;
DESCRIBE TABLE mt_table_demo1
┌─name─┬─type───┬─default_type─┬─default_expression─┐
│ date │ Date │ │ │
│ id │ UInt8 │ │ │
│ name │ String │ │ │
│ age │ UInt8 │ │ │
└──────┴────────┴──────────────┴────────────────────┘
--查询表数据
node01 :) select * from mt_table_demo1;
SELECT *
FROM mt_table_demo1
┌───────date─┬─id─┬─name───┬─age─┐
│ 2019-05-03 │ 3 │ wangwu │ 0 │
└────────────┴────┴────────┴─────┘
┌───────date─┬─id─┬─name─────┬─age─┐
│ 2019-05-01 │ 1 │ zhangsan │ 0 │
└────────────┴────┴──────────┴─────┘
┌───────date─┬─id─┬─name─┬─age─┐
│ 2019-06-01 │ 2 │ lisi │ 0 │
└────────────┴────┴──────┴─────┘
(4)更改age列的类型
--更改age列的类型为UInt16
node01 :) alter table mt_table_demo1 modify column age UInt16
--查看表结构
node01 :) desc mt_table_demo1;
DESCRIBE TABLE mt_table_demo1
┌─name─┬─type───┬─default_type─┬─default_expression─┐
│ date │ Date │ │ │
│ id │ UInt8 │ │ │
│ name │ String │ │ │
│ age │ UInt16 │ │ │
└──────┴────────┴──────────────┴────────────────────┘
(5)删除age列
--删除age列
node01 :) alter table mt_table_demo1 drop column age;
--查看表结构
node01 :) desc mt_table_demo1;
DESCRIBE TABLE mt_table_demo1
┌─name─┬─type───┬─default_type─┬─default_expression─┐
│ date │ Date │ │ │
│ id │ UInt8 │ │ │
│ name │ String │ │ │
└──────┴────────┴──────────────┴────────────────────┘
4 check table
检查表中的数据是否损坏,它会返回两种结果:
0:表数据已损坏
1:表数据完整
该命令只支持Log、TinyLog和StripeLog引擎
演示
node01 :) check TABLE mt_table_demo1;
CHECK TABLE t1
┌─result─┐
│ 1 │
└────────┘
9、clickhouse集成外部存储系统
1 kafka
把消息从kafka集群中实时写入到clickhouse中
1.1 创建一个topic
cd /kkb/install/kafka_2.11-1.1.0
kafka-topics.sh --create --partitions 3 --replication-factor 2 --topic ck --zookeeper node01:2181,node02:2181,node03:2181
1.2 创建表
在node01上启动 clickhouse-client -m 客户端,执行sql语句
CREATE TABLE queue (
timestamp UInt64,
level String,
message String
) ENGINE = Kafka SETTINGS kafka_broker_list ='node01:9092,node02:9092,node03:9092',
kafka_topic_list = 'ck',
kafka_group_name = 'consumer_ck',
kafka_format = 'JSONEachRow',
kafka_num_consumers = 3;
l kafka_broker_list:指定kafka集群地址
l kafka_topic_list:topic 列表
l kafka_group_name:Kafka 消费组名称
l kafka_format:消息体格式,使用与 SQL 部分的 FORMAT
函数相同表示方法,例如 JSONEachRow
l kafka_num_consumers:消费者线程数
1.3 向topic写入数据
利用脚本写数据到ck主题中
bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic ck
##发送一条数据
{"timestamp":153000000,"level":"1","message":"hello ck"}
1.4 查询数据
node01 :) select * from queue;
SELECT *
FROM queue
┌─timestamp─┬─level─┬─message──┐
│ 153000000 │ 1 │ hello ck │
└───────────┴───────┴──────────┘
注意
--这里使用select查询每条数据数据只能够读取一次。可以使用物化视图创建实时线程更实用。
--实现步骤:
--(1)使用引擎创建一个 Kafka 消费者并作为一条数据流。
--(2)创建一个结构表。
--(3)创建物化视图,改视图会在后台转换引擎中的数据并将其放入之前创建的表中。
--当 MATERIALIZED VIEW 添加至引擎,它将会在后台收集数据。可以持续不断地从 Kafka 收集数据并通过 SELECT 将数据转换为所需要的格式。
CREATE TABLE daily (
day Date,
level String,
total UInt64
) ENGINE = SummingMergeTree(day, (day, level), 8192);
CREATE MATERIALIZED VIEW consumer TO daily
AS SELECT toDate(toDateTime(timestamp)) AS day, level, count() as total
FROM queue GROUP BY day, level;
SELECT level, sum(total) FROM daily GROUP BY level;
2 HDFS
该引擎允许通过ClickHouse 管理HDFS上的数据,从而与Apache Hadoop生态系统集成。
2.1 创建一张表
格式:
ENGINE = HDFS(URI, format)
--URI:指定hdfs文件目录
--format:指定数据的格式
演示:
CREATE TABLE hdfs_engine_table (id Int32, name String, age Int32) ENGINE=HDFS('hdfs://node01:8020/other_storage/*', 'CSV');
--数据来自于hdfs上other_storage目录下的所有文件
--文件的格式是CSV(字段之前的分隔符是逗号)
支持的数据格式 :
https://clickhouse.tech/docs/zh/interfaces/formats/#formats
2.2 准备数据
node01执行以下命令创建文件,然后上传到hdfs对应文件夹
cd /kkb/install
vim file1.txt
1,zhangsan,20
2,lisi,29
3,wangwu,25
4,zhaoliu,35
5,tianqi,35
6,kobe,40
上传文件到hdfs上对应的目录
hdfs dfs -mkdir -p /other_storage
hdfs dfs -put file1.txt /other_storage
2.3 查看表数据
node01 :) select * from hdfs_engine_table;
SELECT *
FROM hdfs_engine_table
┌─id─┬─name─────┬─age─┐
│ 1 │ zhangsan │ 20 │
│ 2 │ lisi │ 29 │
│ 3 │ wangwu │ 25 │
│ 4 │ zhaoliu │ 35 │
│ 5 │ tianqi │ 35 │
│ 6 │ kobe │ 40 │
└────┴──────────┴─────┘
10、 实战
需求:基于clickhouse对航班飞行数据进行分析处理
1 准备数据
node01服务器执行以下命令,创建shell脚本
sudo yum -y install wget
cd /kkb/install/
vim down_data.sh
#!/bin/bash*
for s in `seq 1987 2018`
do
for m in `seq 1 12`
do
wget https://transtats.bts.gov/PREZIP/On_Time_Reporting_Carrier_On_Time_Performance_1987_present_${s}
_${m}.zip
done
done
该脚本主要是为了下载从1987年到 2018年航班飞行数据,由于数据量比较大,这里只使用1988年和1989年的飞行数据
2 创建表
node01服务器进入clickhouse的客户端,然后准备创建表
进入客户端,然后创建表
clickhouse-client -m
CREATE TABLE `ontime` (
`Year` UInt16,
`Quarter` UInt8,
`Month` UInt8,
`DayofMonth` UInt8,
`DayOfWeek` UInt8,
`FlightDate` Date,
`UniqueCarrier` FixedString(7),
`AirlineID` Int32,
`Carrier` FixedString(2),
`TailNum` String,
`FlightNum` String,
`OriginAirportID` Int32,
`OriginAirportSeqID` Int32,
`OriginCityMarketID` Int32,
`Origin` FixedString(5),
`OriginCityName` String,
`OriginState` FixedString(2),
`OriginStateFips` String,
`OriginStateName` String,
`OriginWac` Int32,
`DestAirportID` Int32,
`DestAirportSeqID` Int32,
`DestCityMarketID` Int32,
`Dest` FixedString(5),
`DestCityName` String,
`DestState` FixedString(2),
`DestStateFips` String,
`DestStateName` String,
`DestWac` Int32,
`CRSDepTime` Int32,
`DepTime` Int32,
`DepDelay` Int32,
`DepDelayMinutes` Int32,
`DepDel15` Int32,
`DepartureDelayGroups` String,
`DepTimeBlk` String,
`TaxiOut` Int32,
`WheelsOff` Int32,
`WheelsOn` Int32,
`TaxiIn` Int32,
`CRSArrTime` Int32,
`ArrTime` Int32,
`ArrDelay` Int32,
`ArrDelayMinutes` Int32,
`ArrDel15` Int32,
`ArrivalDelayGroups` Int32,
`ArrTimeBlk` String,
`Cancelled` UInt8,
`CancellationCode` FixedString(1),
`Diverted` UInt8,
`CRSElapsedTime` Int32,
`ActualElapsedTime` Int32,
`AirTime` Int32,
`Flights` Int32,
`Distance` Int32,
`DistanceGroup` UInt8,
`CarrierDelay` Int32,
`WeatherDelay` Int32,
`NASDelay` Int32,
`SecurityDelay` Int32,
`LateAircraftDelay` Int32,
`FirstDepTime` String,
`TotalAddGTime` String,
`LongestAddGTime` String,
`DivAirportLandings` String,
`DivReachedDest` String,
`DivActualElapsedTime` String,
`DivArrDelay` String,
`DivDistance` String,
`Div1Airport` String,
`Div1AirportID` Int32,
`Div1AirportSeqID` Int32,
`Div1WheelsOn` String,
`Div1TotalGTime` String,
`Div1LongestGTime` String,
`Div1WheelsOff` String,
`Div1TailNum` String,
`Div2Airport` String,
`Div2AirportID` Int32,
`Div2AirportSeqID` Int32,
`Div2WheelsOn` String,
`Div2TotalGTime` String,
`Div2LongestGTime` String,
`Div2WheelsOff` String,
`Div2TailNum` String,
`Div3Airport` String,
`Div3AirportID` Int32,
`Div3AirportSeqID` Int32,
`Div3WheelsOn` String,
`Div3TotalGTime` String,
`Div3LongestGTime` String,
`Div3WheelsOff` String,
`Div3TailNum` String,
`Div4Airport` String,
`Div4AirportID` Int32,
`Div4AirportSeqID` Int32,
`Div4WheelsOn` String,
`Div4TotalGTime` String,
`Div4LongestGTime` String,
`Div4WheelsOff` String,
`Div4TailNum` String,
`Div5Airport` String,
`Div5AirportID` Int32,
`Div5AirportSeqID` Int32,
`Div5WheelsOn` String,
`Div5TotalGTime` String,
`Div5LongestGTime` String,
`Div5WheelsOff` String,
`Div5TailNum` String
) ENGINE = MergeTree(FlightDate, (Year, FlightDate), 8192);
3 加载数据到表中
· 在数据所在的目录执行下面命令
将所有的数据上传到node01服务器的/kkb/soft/flydatas路径下
然后执行以下命令加载数据
sudo yum -y install unzip
for i in *.zip; do echo $i; unzip -cq $i '*.csv' | sed 's/\.00//g' | clickhouse-client --query="INSERT INTO ontime FORMAT CSVWithNames"; done
· 加载成功后,查看表数据
node01 :) select count(*) from ontime;
SELECT count(*)
FROM ontime
┌──count()─┐
│ 10243296 │
└──────────┘
1 rows in set. Elapsed: 0.005 sec. Processed 10.24 million rows, 10.24 MB (2.26 billion rows/s., 2.26 GB/s.)
4 需求指标分析
4.1 查询所有月份中航班数的最大值、最小值、平均值
SELECT max(c1),min(c1),avg(c1)
FROM
(
SELECT Year, Month, count(*) AS c1
FROM ontime
GROUP BY Year, Month
);
┌─max(c1)─┬─min(c1)─┬─avg(c1)─┐
│ 446769 │ 395176 │ 426804 │
└─────────┴─────────┴─────────┘
4.2 查询从1988年到1989年中周内的航班数
SELECT DayOfWeek, count(*) AS c
FROM ontime
WHERE Year>=1988 AND Year<=1989
GROUP BY DayOfWeek
ORDER BY c desc;
┌─DayOfWeek─┬───────c─┐
│ 5 │ 1497912 │
│ 3 │ 1493143 │
│ 2 │ 1492544 │
│ 1 │ 1489357 │
│ 4 │ 1484423 │
│ 7 │ 1420042 │
│ 6 │ 1365875 │
└───────────┴─────────┘
4.3 查询从1988年到1989年中周内延误超过10分钟的航班数。
SELECT DayOfWeek, count(*) AS c
FROM ontime
WHERE DepDelay>10 AND Year>=1988 AND Year<=1989
GROUP BY DayOfWeek
ORDER BY c DESC;
┌─DayOfWeek─┬──────c─┐
│ 4 │ 307762 │
│ 5 │ 304375 │
│ 3 │ 290879 │
│ 2 │ 270656 │
│ 1 │ 243052 │
│ 7 │ 234229 │
│ 6 │ 219208 │
└───────────┴────────┘
4.4 查询1988年到1989年每个机场延误超过10分钟以上的次数
SELECT Origin, count(*) AS c
FROM ontime
WHERE DepDelay>10 AND Year>=1988 AND Year<=1989
GROUP BY Origin
ORDER BY c DESC
LIMIT 10;
┌─Origin──┬──────c─┐
│ ORD\0\0 │ 138100 │
│ DFW\0\0 │ 80991 │
│ ATL\0\0 │ 80184 │
│ CLT\0\0 │ 68363 │
│ PIT\0\0 │ 67484 │
│ LAX\0\0 │ 63626 │
│ DEN\0\0 │ 60269 │
│ SFO\0\0 │ 58458 │
│ EWR\0\0 │ 55438 │
│ STL\0\0 │ 53238 │
└─────────┴────────┘
4.5 查询1988年各航空公司延误超过10分钟以上的次数
SELECT Carrier, count(*) as num
FROM ontime
WHERE DepDelay>10 AND Year=1988
GROUP BY Carrier
ORDER BY num DESC;
┌─Carrier─┬────num─┐
│ UA │ 113208 │
│ AA │ 96946 │
│ DL │ 96443 │
│ PI │ 94642 │
│ CO │ 80925 │
│ AL │ 78368 │
│ EA │ 66918 │
│ NW │ 64962 │
│ TW │ 51121 │
│ WN │ 44572 │
│ US │ 26604 │
│ HP │ 21175 │
│ AS │ 13352 │
│ PA │ 10181 │
│ PS │ 5020 │
└─────────┴────────┘
4.6 查询1988年各航空公司延误超过10分钟以上的百分比
SELECT Carrier, c, c2, c*100/c2 as c3
FROM
(
SELECT
Carrier,
count(*) AS c
FROM ontime
WHERE DepDelay>10
AND Year=1988
GROUP BY Carrier
)
ANY INNER JOIN
(
SELECT
Carrier,
count(*) AS c2
FROM ontime
WHERE Year=1988
GROUP BY Carrier
) USING Carrier
ORDER BY c3 DESC;
---更好的查询版本
SELECT Carrier, avg(DepDelay>10)*100 AS c3
FROM ontime
WHERE Year=1988
GROUP BY Carrier
ORDER BY Carrier;
┌─Carrier─┬──────c─┬─────c2─┬─────────────────c3─┐
│ AL │ 78368 │ 361059 │ 21.705039896526607 │
│ PI │ 94642 │ 470957 │ 20.095677524699706 │
│ US │ 26604 │ 133324 │ 19.954396807776543 │
│ UA │ 113208 │ 587144 │ 19.281130353030942 │
│ TW │ 51121 │ 275819 │ 18.534256160743094 │
│ CO │ 80925 │ 457031 │ 17.70667635236997 │
│ EA │ 66918 │ 389292 │ 17.18966739619617 │
│ WN │ 44572 │ 262422 │ 16.984856452584005 │
│ NW │ 64962 │ 431440 │ 15.057018357129612 │
│ AS │ 13352 │ 89822 │ 14.864955133486228 │
│ PA │ 10181 │ 72264 │ 14.088619506254844 │
│ AA │ 96946 │ 694757 │ 13.953943609060435 │
│ DL │ 96443 │ 753983 │ 12.791137200706117 │
│ PS │ 5020 │ 41911 │ 11.9777624012789 │
│ HP │ 21175 │ 180871 │ 11.707238860845575 │
└─────────┴────────┴────────┴────────────────────┘
4.7 同上一个查询一致,只是查询范围扩大到1988年到1989年
SELECT Carrier, avg(DepDelay>10)*100 AS c3
FROM ontime
WHERE Year>=1988 AND Year<=1989
GROUP BY Carrier
ORDER BY Carrier;
┌─Carrier─┬─────────────────c3─┐
│ AA │ 14.973459265773348 │
│ AL │ 21.705039896526607 │
│ AS │ 14.402829935622318 │
│ CO │ 16.96160617418744 │
│ DL │ 14.788366379301934 │
│ EA │ 16.675253490185977 │
│ HP │ 13.738282256500709 │
│ NW │ 14.799160318481144 │
│ PA │ 15.59199720311088 │
│ PI │ 24.084359219776232 │
│ PS │ 11.9777624012789 │
│ TW │ 18.715348820366113 │
│ UA │ 22.14408797247073 │
│ US │ 25.955873831985677 │
│ WN │ 20.385723408269595 │
└─────────┴────────────────────┘
4.8 每年航班延误超过10分钟的百分比
SELECT Year, c1/c2
FROM
(
select
Year,
count(*)*100 as c1
from ontime
WHERE DepDelay>10
GROUP BY Year
)
ANY INNER JOIN
(
select
Year,
count(*) as c2
from ontime
GROUP BY Year
) USING (Year)
ORDER BY Year;
---更好的查询版本
SELECT Year, avg(DepDelay>10)
FROM ontime
GROUP BY Year
ORDER BY Year;
┌─Year─┬─────divide(c1, c2)─┐
│ 1988 │ 16.61709049583091 │
│ 1989 │ 19.950091248115527 │
└──────┴────────────────────┘
4.9 每年更受人们喜爱的目的地
SELECT DestCityName, uniqExact(OriginCityName) AS u
FROM ontime
WHERE Year >= 1988 and Year <= 1989
GROUP BY DestCityName
ORDER BY u DESC LIMIT 10;
--uniqExact去重
┌─DestCityName──────────┬───u─┐
│ Chicago, IL │ 111 │
│ Atlanta, GA │ 92 │
│ Dallas/Fort Worth, TX │ 89 │
│ Denver, CO │ 86 │
│ Pittsburgh, PA │ 79 │
│ St. Louis, MO │ 78 │
│ Minneapolis, MN │ 75 │
│ Charlotte, NC │ 69 │
│ Detroit, MI │ 69 │
│ Phoenix, AZ │ 67 │
└───────────────────────┴─────┘
11、clickhouse为什么辣么快
在巨大的单表测试运行当中,ck有着无与伦比的性能,那么ck究竟是如何实现的,才能够实现惊人的速度呢?
ClickHouse从OLAP场景需求出发,定制开发了一套全新的高效列式存储引擎,并且实现了数据有序存储、主键索引、稀疏索引、数据Sharding、数据Partitioning、TTL、主备复制等丰富功能。以上功能共同为ClickHouse极速的分析性能奠定了基础
clickhouse的存储存优势
1、列式存储
就像hbase等一些nosql的数据库一样,clickhouse也采用了列式存储的方式。相比于行式存储,列式存储在分析场景下有着许多优良的特性。
1)如前所述,分析场景中往往需要读大量行但是少数几个列。在行存模式下,数据按行连续存储,所有列的数据都存储在一个block中,不参与计算的列在IO时也要全部读出,读取操作被严重放大。而列存模式下,只需要读取参与计算的列即可,极大的减低了IO cost,加速了查询。
2)同一列中的数据属于同一类型,压缩效果显著。列存往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本。
3)更高的压缩比意味着更小的data size,从磁盘中读取相应数据耗时更短。
4)自由的压缩算法选择。不同列的数据具有不同的数据类型,适用的压缩算法也就不尽相同。可以针对不同列类型,选择最合适的压缩算法。
5)高压缩比,意味着同等大小的内存能够存放更多数据,系统cache效果更好。
官方数据显示,通过使用列存,在某些分析场景下,能够获得100倍甚至更高的加速效应。
2、数据有序存储
ClickHouse支持在建表时,指定将数据按照某些列进行sort by。
排序后,保证了相同sort key的数据在磁盘上连续存储,且有序摆放。在进行等值、范围查询时,where条件命中的数据都紧密存储在一个或若干个连续的Block中,而不是分散的存储在任意多个Block, 大幅减少需要IO的block数量。另外,连续IO也能够充分利用操作系统page cache的预取能力,减少page fault。
3、主键索引
ClickHouse支持主键索引,它将每列数据按照index granularity(默认8192行)进行划分,每个index granularity的开头第一行被称为一个mark行。主键索引存储该mark行对应的primary key的值。
对于where条件中含有primary key的查询,通过对主键索引进行二分查找,能够直接定位到对应的index granularity,避免了全表扫描从而加速查询。
4、稀疏索引
ClickHouse支持对任意列创建任意数量的稀疏索引。其中被索引的value可以是任意的合法SQL Expression,并不仅仅局限于对column value本身进行索引。之所以叫稀疏索引,是因为它本质上是对一个完整index granularity(默认8192行)的统计信息,并不会具体记录每一行在文件中的位置。目前支持的稀疏索引类型包括:
Ø minmax: 以index granularity为单位,存储指定表达式计算后的min、max值;在等值和范围查询中能够帮助快速跳过不满足要求的块,减少IO。
Ø set(max_rows):以index granularity为单位,存储指定表达式的distinct value集合,用于快速判断等值查询是否命中该块,减少IO。
Ø ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed):将string进行ngram分词后,构建bloom filter,能够优化等值、like、in等查询条件。
Ø tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed): 与ngrambf_v1类似,区别是不使用ngram进行分词,而是通过标点符号进行词语分割。
Ø bloom_filter([false_positive]):对指定列构建bloom filter,用于加速等值、like、in等查询条件的执行。
5、数据sharding
ClickHouse支持单机模式,也支持分布式集群模式。在分布式模式下,ClickHouse会将数据分为多个分片,并且分布到不同节点上。不同的分片策略在应对不同的SQL Pattern时,各有优势。ClickHouse提供了丰富的sharding策略,让业务可以根据实际需求选用。
1) random随机分片:写入数据会被随机分发到分布式集群中的某个节点上。
2) constant固定分片:写入数据会被分发到固定一个节点上。
3)column value分片:按照某一列的值进行hash分片。
4)自定义表达式分片:指定任意合法表达式,根据表达式被计算后的值进行hash分片。
数据分片,让ClickHouse可以充分利用整个集群的大规模并行计算能力,快速返回查询结果。
更重要的是,多样化的分片功能,为业务优化打开了想象空间。比如在hash sharding的情况下,JOIN计算能够避免数据shuffle,直接在本地进行local join; 支持自定义sharding,可以为不同业务和SQL Pattern定制最适合的分片策略;利用自定义sharding功能,通过设置合理的sharding expression可以解决分片间数据倾斜问题等。
另外,sharding机制使得ClickHouse可以横向线性拓展,构建大规模分布式集群,从而具备处理海量数据的能力。
6、数据Partitioning
ClickHouse支持PARTITION BY子句,在建表时可以指定按照任意合法表达式进行数据分区操作,比如通过toYYYYMM()将数据按月进行分区、toMonday()将数据按照周几进行分区、对Enum类型的列直接每种取值作为一个分区等。
数据Partition在ClickHouse中主要有两方面应用:
Ø 在partition key上进行分区裁剪,只查询必要的数据。灵活的partition expression设置,使得可以根据SQL Pattern进行分区设置,最大化的贴合业务特点。
Ø 对partition进行TTL管理,淘汰过期的分区数据。
7、数据TTL
在分析场景中,数据的价值随着时间流逝而不断降低,多数业务出于成本考虑只会保留最近几个月的数据,ClickHouse通过TTL提供了数据生命周期管理的能力。
ClickHouse支持几种不同粒度的TTL:
1) 列级别TTL:当一列中的部分数据过期后,会被替换成默认值;当全列数据都过期后,会删除该列。
2)行级别TTL:当某一行过期后,会直接删除该行。
3)分区级别TTL:当分区过期后,会直接删除该分区。
8、高吞吐写入能力
ClickHouse采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类LSM tree的结构,ClickHouse在数据导入时全部是顺序append写,写入后数据段不可更改,在后台compaction时也是多个段merge sort后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在HDD上也有着优异的写入性能。
官方公开benchmark测试显示能够达到50MB-200MB/s的写入吞吐能力,按照每行100Byte估算,大约相当于50W-200W条/s的写入速度。
9、有限支持delete、update
在分析场景中,删除、更新操作并不是核心需求。ClickHouse没有直接支持delete、update操作,而是变相支持了mutation操作,语法为alter table delete where filter_expr,alter table update col=val where filter_expr。
目前主要限制为删除、更新操作为异步操作,需要后台compation之后才能生效。
10、主备同步
ClickHouse通过主备复制提供了高可用能力,主备架构下支持无缝升级等运维操作。而且相比于其他系统它的实现有着自己的特色:
1)默认配置下,任何副本都处于active模式,可以对外提供查询服务;
2)可以任意配置副本个数,副本数量可以从0个到任意多个;
3)不同shard可以配置不提供副本个数,用于解决单个shard的查询热点问题;
ClickHouse计算层优势
ClickHouse在计算层做了非常细致的工作,竭尽所能榨干硬件能力,提升查询速度。它实现了单机多核并行、分布式计算、向量化执行与SIMD指令、代码生成等多种重要技术。
1、多核并行
ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity,然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。
在这种设计下,单条Query就能利用整机所有CPU。极致的并行处理能力,极大的降低了查询延时。
2、分布式计算
除了优秀的单机并行处理能力,ClickHouse还提供了可线性拓展的分布式计算能力。ClickHouse会自动将查询拆解为多个task下发到集群中,然后进行多机并行处理,最后把结果汇聚到一起。
在存在多副本的情况下,ClickHouse提供了多种query下发策略:
Ø 随机下发:在多个replica中随机选择一个;
Ø 最近hostname原则:选择与当前下发机器最相近的hostname节点,进行query下发。在特定的网络拓扑下,可以降低网络延时。而且能够确保query下发到固定的replica机器,充分利用系统cache。
Ø in order:按照特定顺序逐个尝试下发,当前一个replica不可用时,顺延到下一个replica。
Ø first or random:在In Order模式下,当第一个replica不可用时,所有workload都会积压到第二个Replica,导致负载不均衡。first or random解决了这个问题:当第一个replica不可用时,随机选择一个其他replica,从而保证其余replica间负载均衡。另外在跨region复制场景下,通过设置第一个replica为本region内的副本,可以显著降低网络延时。
3、向量化执行与SIMD
ClickHouse不仅将数据按列存储,而且按列进行计算。传统OLTP数据库通常采用按行计算,原因是事务处理中以点查为主,SQL计算量小,实现这些技术的收益不够明显。但是在分析场景下,单个SQL所涉及计算量可能极大,将每行作为一个基本单元进行处理会带来严重的性能损耗:
1)对每一行数据都要调用相应的函数,函数调用开销占比高;
2)存储层按列存储数据,在内存中也按列组织,但是计算层按行处理,无法充分利用CPU cache的预读能力,造成CPU Cache miss严重;
3)按行处理,无法利用高效的SIMD指令;
ClickHouse实现了向量执行引擎(Vectorized execution engine),对内存中的列式数据,一个batch调用一次SIMD指令(而非每一行调用一次),不仅减少了函数调用次数、降低了cache miss,而且可以充分发挥SIMD指令的并行能力,大幅缩短了计算耗时。向量执行引擎,通常能够带来数倍的性能提升。
4、动态代码生成Runtime Codegen
在经典的数据库实现中,通常对表达式计算采用火山模型,也即将查询转换成一个个operator,比如HashJoin、Scan、IndexScan、Aggregation等。为了连接不同算子,operator之间采用统一的接口,比如open/next/close。在每个算子内部都实现了父类的这些虚函数,在分析场景中单条SQL要处理数据通常高达数亿行,虚函数的调用开销不再可以忽略不计。另外,在每个算子内部都要考虑多种变量,比如列类型、列的size、列的个数等,存在着大量的if-else分支判断导致CPU分支预测失效。
ClickHouse实现了Expression级别的runtime codegen,动态地根据当前SQL直接生成代码,然后编译执行。如下图例子所示,对于Expression直接生成代码,不仅消除了大量的虚函数调用(即图中多个function pointer的调用),而且由于在运行时表达式的参数类型、个数等都是已知的,也消除了不必要的if-else分支判断。
5、近似计算
近似计算以损失一定结果精度为代价,极大地提升查询性能。在海量数据处理中,近似计算价值更加明显。
ClickHouse实现了多种近似计算功能:
Ø 近似估算distinct values、中位数,分位数等多种聚合函数;
Ø 建表DDL支持SAMPLE BY子句,支持对于数据进行抽样处理;
6、复杂数据类型支持
ClickHouse还提供了array、json、tuple、set等复合数据类型,支持业务schema的灵活变更。
12、hive表迁移到clickHouse
由于历史原因,很多公司的历史数仓都是基于hive构建,数据表都是存储在hive当中,如何实现hive表数据迁移到clickHouse就至关重要了,接下来我们来一起看看如何将hive表数据全部平滑的迁移到clickHouse里面来
由于我们hive的数据全部都是存储在hdfs上面的,所以我们直接将hdfs的数据加载到clickhouse里面创建的表里面来即可,我们可以通过第三方插件例如datax或者waterdrop等数据传输工具,将hdfs的数据全部采集到clickhouse里面来即可
需求:将hive数据仓库当中的game_center这个库的ods_user_login表数据迁移到clickHouse表里面来
这里涉及到数据的迁移,将hive当中的表数据迁移到clickHouse里面来,数据迁移的工具很多,例如sqoop,datax,waterdrop等,其中有一个比较优秀的插件叫做waterdrop的,可以非常方便的实现我们各种数据的迁移,官网链接如下:
https://interestinglab.github.io/waterdrop/#/
通过简单的配置即可,我们就可以通过waterdrop将各个地方的数据进行抽取到各个目的地去
1、waterdrop的基本介绍
官网链接如下:
https://interestinglab.github.io/waterdrop/#/
通过简单的配置即可,我们就可以通过waterdrop将各个地方的数据进行抽取到各个目的地去
2、waterdrop的安装与使用
waterdrop使用spark的引擎来实现数据的抽取和传输到目的地,所以使用waterdrop之前需要我们先安装好spark的环境,
运行环境要求:
l java运行环境,java >= 8
l 如果您要在集群环境中运行Waterdrop,那么需要以下Spark集群环境的任意一种:
l Spark on Yarn
l Spark Standalone
l Spark on Mesos
l 如果您的数据量较小或者只是做功能验证,也可以仅使用local模式启动,无需集群环境,Waterdrop支持单机运行。
waterdrop不支持spark1.x的各种版本,最低需要安装spark2.x的版本,如果使用spark2.3.x的版本,那么waterdrop需要1.4.x及以上的版本
第一步:安装spark集群
我们这里已经安装成功,不用再进行安装,并且我们使用的spark集群版本是2.3.x版本(需要让spark、hadoop支持lzo压缩)
第二步:集成spark与hive
由于我们需要将hive表当中的数据,导入到clickhouse当中去,而waterdrop又是借助于spark来进行数据抽取的,所以我们需要整合spark与hive
node03执行以下命令,拷贝hive-site.xml
先将hive-site.xml中的tez引擎注释掉
cd /kkb/install/apache-hive-3.1.2/conf/
scp hive-site.xml node01:/kkb/install/spark-2.3.3-bin-hadoop2.7/conf/
scp hive-site.xml node02:/kkb/install/spark-2.3.3-bin-hadoop2.7/conf/
scp hive-site.xml node03:/kkb/install/spark-2.3.3-bin-hadoop2.7/conf/
第三步:启动hive的metastore以及hiveserver2服务
使用spark作为引擎,读取hive表数据,我们需要启动hive的metastore服务,node03执行以下命令启动hivedemetastore服务即可
cd /kkb/install/apache-hive-3.1.2
bin/hive --service metastore
bin/hive --service hiveserver2
第四步:下载安装waterdrop
我们这里使用waterdrop1.4.2的这个版本,waterdrop的下载地址如下:
https://github.com/InterestingLab/waterdrop/releases/download/v1.4.2/waterdrop-1.4.2.zip
node02执行以下命令下载waterdrop
cd /kkb/soft/
sudo yum -y install wget
wget https://github.com/InterestingLab/waterdrop/releases/download/v1.4.2/waterdrop-1.4.2.zip
node02执行以下命令进行解压
sudo yum -y install unzip
unzip waterdrop-1.4.2.zip -d /kkb/install/
第五步:修改waterdrop的配置文件
node02服务器执行以下命令,修改配置文件
cd /kkb/install/waterdrop-1.4.2/config
vim waterdrop-env.sh
SPARK_HOME= /kkb/install/spark-2.3.3-bin-hadoop2.7
第六步:上传数据及创建clickhouse数据库以及数据库表
一键执行所有的ods层的数据库表
开发shell脚本,一键执行ods层所有的表
将ods层的所有sh脚本,上传到/home/hadoop/bin目录
node03开发以下shell脚本
cd /home/hadoop/bin
vim execute_ods.sh
#!/bin/bash
odsshell=`ls ods*`
#echo "$odsshell"
for m in $odsshell
do
echo $m
/usr/bin/sh $m
done
执行脚本
sh execute_ods.sh
node02服务器进入到clickHouse客户端,然后创建数据库以及数据库表
clickhouse-client -m
node02.kaikeba.com :) create database game_center;
node02.kaikeba.com :) use game_center;
创建clickHouse表
CREATE TABLE game_center.ods_role_recharge(
plat_id String,
server_id Int32,
channel_id String,
user_id String,
role_id String,
role_name String,
event_time Int32,
order_id String,
acer_count Int32,
recharge_amount Float64,
order_status Int32,
recharge_purpose Int32,
part_date date
)ENGINE = MergeTree PARTITION BY part_date ORDER BY (part_date) SETTINGS index_granularity = 16384;
第七步:开发clickHouse数据处理配置文件
node02执行以下命令开发waterdrop的配置文件
cd /kkb/install/waterdrop-1.4.2/config
vim batch.conf
#配置文件内容如下
spark {
spark.app.name = "Waterdrop"
spark.executor.instances = 2
spark.executor.cores = 1
spark.executor.memory = "1g"
spark.sql.catalogImplementation = "hive"
}
input {
hive {
pre_sql = "select * from game_center.ods_role_recharge"
result_table_name = "ods_role_recharge2"
table_name = "ods_role_recharge2"
}
}
filter {
# split data by specific delimiter
}
output {
clickhouse {
host = "node02:8123"
database = "game_center"
table = "ods_role_recharge"
fields = ["plat_id","server_id","channel_id","user_id","role_id","role_name","event_time","order_id","acer_count","recharge_amount","order_status","recharge_purpose"]
username = ""
password = ""
}
}
第八步:启动waterdrop导入数据到clickhouse表当中去
node02执行以下命令,启动waterdrop开始导入数据
使用spark的local模式进行运行
cd /kkb/install/waterdrop-1.4.2/
bin/start-waterdrop.sh --config config/batch.conf -e client -m 'local[2]'
如果想要使用spark的standAlone模式进行运行,通过以下方式进行提交
# client 模式
./bin/start-waterdrop.sh --master spark://node01:7077 --deploy-mode client --config ./config/batch.conf
# cluster 模式
./bin/start-waterdrop.sh --master spark://node01:7077 --deploy-mode cluster --config ./config/*batch.conf
如果想要使用spark on yarn模式进行提交,那么通过以下方式进行提交
# client 模式
./bin/start-waterdrop.sh --master yarn --deploy-mode client --config ./config/batch.conf
# cluster 模式
./bin/start-waterdrop.sh --master yarn --deploy-mode cluster --config ./config/batch.conf
第九步:查询clickhouse数据,验证数据进入clickhouse
node02执行以下命令,查询clickhouse当中的数据
clickhouse-client -m
node02.kaikeba.com :) use game_center;
node02.kaikeba.com :) select count(1) from ods_role_recharge;