clickhouse从零开始(使用篇)

一、Clickhouse简介

Ø  1.真正的面向列的DBMS

Ø  2.数据高效压缩

Ø  3.磁盘存储的数据

Ø  4.多核并行处理

Ø  5.在多个服务器上分布式处理

Ø  6.SQL语法支持

Ø  7.向量化引擎

Ø  8.实时数据更新

Ø  9.索引

Ø  10.适合在线查询

Ø  11.支持近似预估计算

Ø  12.支持嵌套的数据结构

Ø  支持数组作为数据类型

Ø  13.支持限制查询复杂性以及配额

Ø  14.复制数据复制和对数据完整性的支持

#我们来看看其中的一些功能:

1.真正的面向列的DBMS

在一个真正的面向列的DBMS中,没有任何“垃圾”存储在值中。例如,必须支持定长数值,以避免在数值旁边存储长度“数字”。例如,十亿个UInt8类型的值实际上应该消耗大约1 GB的未压缩磁盘空间,否则这将强烈影响CPU的使用。由于解压缩的速度(CPU使用率)主要取决于未压缩的数据量,所以即使在未压缩的情况下,紧凑地存储数据(没有任何“垃圾”)也是非常重要的。

因为有些系统可以单独存储单独列的值,但由于其他场景的优化,无法有效处理分析查询。例如HBase,BigTable,Cassandra和HyperTable。在这些系统中,每秒钟可以获得大约十万行的吞吐量,但是每秒不会达到数亿行。

另外,ClickHouse是一个DBMS,而不是一个单一的数据库。ClickHouse允许在运行时创建表和数据库,加载数据和运行查询,而无需重新配置和重新启动服务器。

2.数据压缩

一些面向列的DBMS(InfiniDB CE和MonetDB)不使用数据压缩。但是,数据压缩确实提高了性能。

3.磁盘存储的数据

许多面向列的DBMS(SAP HANA和GooglePowerDrill)只能在内存中工作。但即使在数千台服务器上,内存也太小,无法在Yandex.Metrica中存储所有浏览量和会话。

4.多核并行处理

多核多节点并行化大型查询。

5.在多个服务器上分布式处理

上面列出的列式DBMS几乎都不支持分布式处理。在ClickHouse中,数据可以驻留在不同的分片上。每个分片可以是用于容错的一组副本。查询在所有分片上并行处理。这对用户来说是透明的。

6.SQL支持

如果你熟悉标准的SQL,我们不能真正谈论SQL的支持。NULL不支持。所有的函数都有不同的名字。JOIN支持。子查询在FROM,IN,JOIN子句中被支持;标量子查询支持。关联子查询不支持。

7.向量化引擎

数据不仅按列存储,而且由矢量 - 列的部分进行处理。这使我们能够实现高CPU性能。

8.实时数据更新

ClickHouse支持主键表。为了快速执行对主键范围的查询,数据使用合并树(MergeTree)进行递增排序。由于这个原因,数据可以不断地添加到表中。添加数据时无锁处理。

9.索引

例如,带有主键可以在特定的时间范围内为特定客户端(Metrica计数器)抽取数据,并且延迟时间小于几十毫秒。

10.支持在线查询

这让我们使用该系统作为Web界面的后端。低延迟意味着可以无延迟实时地处理查询,而Yandex.Metrica界面页面正在加载(在线模式)。

11.支持近似计算

1.系统包含用于近似计算各种值,中位数和分位数的集合函数。

2.支持基于部分(样本)数据运行查询并获得近似结果。在这种情况下,从磁盘检索比例较少的数据。

3.支持为有限数量的随机密钥(而不是所有密钥)运行聚合。在数据中密钥分发的特定条件下,这提供了相对准确的结果,同时使用较少的资源。

12.数据复制和对数据完整性的支持。

使用异步多主复制。写入任何可用的副本后,数据将分发到所有剩余的副本。系统在不同的副本上保持相同的数据。数据在失败后自动恢复

ClickHouse的不完美:

Ø  1.不支持事务(大数据量的事务会降低系统吞吐)。

Ø  2.不支持直接的Update/Delete操作,需要使用alter来更新或删除。

Ø  3.支持有限操作系统。

二、clickhouse常见的引擎(MergeTree家族)

1(Replicated)MergeTree

该引擎为最简单的引擎,存储最原始数据不做任何的预计算,任何在该引擎上的select语句都是在原始数据上进行操作的,常规场景使用最为广泛,其他引擎都是该引擎的一个变种。

2(Replicated)SummingMergeTree

该引擎拥有“预计算(加法)”的功能。

实现原理:在merge阶段把数据加起来(对于需要加的列需要在建表的时候进行指定),对于不可加的列,会取一个最先出现的值。

3(Replicated)ReplacingMergeTree

该引擎拥有“处理重复数据”的功能。

使用场景:“最新值”,“实时数据”。

4(Replicated)AggregatingMergeTree

该引擎拥有“预聚合”的功能。

使用场景:配合”物化视图”来一起使用,拥有毫秒级计算UV和PV的能力。

5(Replicated)CollapsingMergeTree

该引擎和ReplacingMergeTree的功能有点类似,就是通过一个sign位去除重复数据的。

需要注意的是,上述所有拥有"预聚合"能力的引擎都在"Merge"过程中实现的,所以在表上进行查询的时候SQL是需要进行特殊处理的。

如SummingMergeTree引擎需要自己sum(), ReplacingMergeTree引擎需要使用时间+版本进行order by + limit来取到最新的值,由于数据做了预处理,数据量已经减少了很多,所以查询速度相对会快非常多。

7、最佳实践

1)实时写入使用本地表,不要使用分布式表

分布式表引擎会帮我们将数据自动路由到健康的数据表进行数据的存储,所以使用分布式表相对来说比较简单,对于Producer不需要有太多的考虑,但是分布式表有些致命的缺点。

  • 数据的一致性问题,先在分布式表所在的机器进行落盘,然后异步的发送到本地表所在机器进行存储,中间没有一致性的校验,而且在分布式表所在机器时如果机器出现down机,会存在数据丢失风险;
  • 据说对zookeeper的压力比较大(待验证)。

2)推荐使用(*)MergeTree引擎,该引擎是clickhouse最核心的组件,也是社区优化的重点

数据有保障,查询有保障,升级无感知。

3)谨慎使用on clusterSQL

使用该类型SQL hang住的案例不少,我们也有遇到,可以直接写个脚本直接操作集群的每台进行处理。

三、安装启动clickhouse

sudo yum install yum-utils

sudo rpm --import https://repo.clickhouse.tech/CLICKHOUSE-KEY.GPG

sudo yum-config-manager --add-repo https://repo.clickhouse.tech/rpm/stable/x86_64

sudo yum install clickhouse-server clickhouse-client



sudo /etc/init.d/clickhouse-server start

clickhouse-client

启动:sudo /etc/init.d/clickhouse-server start

四、查询速度比较

查询sql

mysql全索引覆盖情况下查询结果

clickhouse查询结果

建表语句如下

SELECT
distinct
bc.user_id,
bc.channel_user_id,
bc.role_id role_id,
bc.role_name,
bc.game_channel_id,
bc.game_platform_id,
bc.game_server_id,
bc.created_at,
br.amount
FROM
`b_gm_dev_create` bc
LEFT JOIN ( SELECT role_id, max(amount) amount FROM `b_gm_dev_recharge` GROUP BY role_id ) br ON br.role_id = bc.role_id
LEFT JOIN ( SELECT role_id, created_time FROM `b_gm_dev_login_log` WHERE created_time BETWEEN '2020-10-20' AND '2020-10-22' GROUP BY role_id,created_time ) bl ON bc.role_id = bl.role_id
WHERE
bc.game_channel_id = '816'
AND bc.created_time BETWEEN '2020-07-28'
AND '2020-10-19'
AND br.amount >= 6
AND bl.created_time = '1970-01-01';

19:30后数据库低峰使用情况下查询时间为

2.785s  正常时间为 3.6s

查询时间385ms 数据拉取时间 252ms

共计637ms



create table table1 ENGINE = MergeTree PARTITION BY toYYYYMM(pay_time) ORDER BY (id, role_id, created_at) AS
SELECT *
FROM mysql(url:port', 'database', 'table', 'account',
'pqsswd');

 

 

 

 

 

 

 

 

五、可直连kafka从kafka获取数据

创建消费者

创建持久化表

创建物化视图

CREATE TABLE queue
(
    localTime         UInt64,
    deviceId          String,
    channel           String,
    gameVersion       String,
    sdkVersion        String,
    gameId            String,
    userId            String,
    userName          String,
    userSource        String,
    baseUrl           String,
    logUrl            String,
    packageName       String,
    appName           String,
    versionCode       String,
    versionName       String,
    androidVersion    String,
    androidSDKVersion String,
    netOperatorName   String,
    netTypeName       String,
    androidId         String,
    screenSize        String,
    display           String,
    product           String,
    manufacturer      String,
    brand             String,
    model             String,
    device            String,
    ssid              String,
    timeZone          String,
    currentLanguage   String,
    cupAbi            String,
    cupName           String,
    clientToken       Nullable(String),
    userToken         Nullable(String),
    eventName         String
) ENGINE = Kafka SETTINGS kafka_broker_list = 'url:port,url:port,url:port',
    kafka_topic_list = 'sdklog',
    kafka_group_name = 'kafka_test',
    kafka_format = 'JSONEachRow',
    kafka_num_consumers = 1;

CREATE TABLE sdklog
(
    localTime         UInt64,
    deviceId          String,
    channel           String,
    gameVersion       String,
    sdkVersion        String,
    gameId            String,
    userId            String,
    userName          String,
    userSource        String,
    baseUrl           String,
    logUrl            String,
    packageName       String,
    appName           String,
    versionCode       String,
    versionName       String,
    androidVersion    String,
    androidSDKVersion String,
    netOperatorName   String,
    netTypeName       String,
    androidId         String,
    screenSize        String,
    display           String,
    product           String,
    manufacturer      String,
    brand             String,
    model             String,
    device            String,
    ssid              String,
    timeZone          String,
    currentLanguage   String,
    cupAbi            String,
    cupName           String,
    clientToken       Nullable(String),
    userToken         Nullable(String),
    eventName         String
) ENGINE = MergeTree ORDER BY (eventName, gameId, channel);

CREATE MATERIALIZED VIEW consumer TO sdklog
AS
SELECT localTime,
       deviceId,
       channel,
       gameVersion,
       sdkVersion,
       gameId,
       userId,
       userName,
       userSource,
       baseUrl,
       logUrl,
       packageName,
       appName,
       versionCode,
       versionName,
       androidVersion,
       androidSDKVersion,
       netOperatorName,
       netTypeName,
       androidId,
       screenSize,
       display,
       product,
       manufacturer,
       brand,
       model,
       device,
       ssid,
       timeZone,
       currentLanguage,
       cupAbi,
       cupName,
       clientToken,
       userToken,
       eventName
FROM queue;

这样kafka的一个消费者服务就完成了   使用   ATTACH TABLE consumer;  DETACH TABLE consumer; 启停消费

六、注意点归纳

1Too many parts(304). Merges are processing significantly slower than inserts

相信很多同学在刚开始使用clickhouse的时候都有遇到过该异常,出现异常的原因是因为MergeTree的merge的速度跟不上目录生成的速度, 数据目录越来越多就会抛出这个异常, 所以一般情况下遇到这个异常,降低一下插入频次就ok了,单纯调整background_pool_size的大小是治标不治本的。

我们的场景:

我们的插入速度是严格按照官方文档上面的推荐”每秒不超过1次的insert request”,但是有个插入程序在运行一段时间以后抛出了该异常,很奇怪。

问题排查:

排查发现失败的这个表的数据有一个特性,它虽然是实时数据但是数据的eventTime是最近一周内的任何时间点,我们的表又是按照day + hour组合分区的那么在极限情况下,我们的一个插入请求会涉及7*24分区的数据,也就是我们一次插入会在磁盘上生成168个数据目录(文件夹),文件夹的生成速度太快,merge速度跟不上了,所以官方文档的上每秒不超过1个插入请求,更准确的说是每秒不超过1个数据目录。

case study

分区字段的设置要慎重考虑,如果每次插入涉及的分区太多,那么不仅容易出现上面的异常,同时在插入的时候也比较耗时,原因是每个数据目录都需要和zookeeper进行交互。

2DB::NetException: Connection reset by peer, while reading from socket xxx

查询过程中clickhouse-server进程挂掉。

问题排查:

排查发现在这个异常抛出的时间点有出现clickhouse-server的重启,通过监控系统看到机器的内存使用在该时间点出现高峰,在初期集群"裸奔"的时期,很多内存参数都没有进行限制,导致clickhouse-server内存使用量太高被OS KILL掉。

case study

上面推荐的内存参数强烈推荐全部加上,max_memory_usage_for_all_queries该参数没有正确设置是导致该case触发的主要原因。

3Memory limit (for query) exceeded:would use 9.37 GiB (attempt to allocate chunk of 301989888 bytes), maximum: 9.31 GiB

该异常很直接,就是我们限制了SQL的查询内存(max_memory_usage)使用的上线,当内存使用量大于该值的时候,查询被强制KILL。

对于常规的如下简单的SQL, 查询的空间复杂度为O(1) 。

select count(1) from table where condition1 and condition2

select c1, c2 from table where condition1 and condition2

对于group by, order by , count distinct,join这样的复杂的SQL,查询的空间复杂度就不是O(1)了,需要使用大量的内存。

  • 如果是group by内存不够,推荐配置上max_bytes_before_external_group_by参数,当使用内存到达该阈值,进行磁盘group by
  • 如果是order by内存不够,推荐配置上max_bytes_before_external_sort参数,当使用内存到达该阈值,进行磁盘order by
  • 如果是count distinct内存不够,推荐使用一些预估函数(如果业务场景允许),这样不仅可以减少内存的使用同时还会提示查询速度
  • 对于JOIN场景,我们需要注意的是clickhouse在进行JOIN的时候都是将"右表"进行多节点的传输的(右表广播),如果你已经遵循了该原则还是无法跑出来,那么好像也没有什么好办法了

4zookeepersnapshot文件太大,followerleader同步文件时超时

上面有说过clickhouse对zookeeper的依赖非常的重,表的元数据信息,每个数据块的信息,每次插入的时候,数据同步的时候,都需要和zookeeper进行交互,上面存储的数据非常的多。

就拿我们自己的集群举例,我们集群有60台机器30张左右的表,数据一般只存储2天,我们zookeeper集群的压力 已经非常的大了,zookeeper的节点数据已经到达500w左右,一个snapshot文件已经有2G+左右的大小了,zookeeper节点之间的数据同步已经经常性的出现超时。

问题解决:

  • zookeeper的snapshot文件存储盘不低于1T,注意清理策略,不然磁盘报警报到你怀疑人生,如果磁盘爆了那集群就处于“残废”状态;
  • zookeeper集群的znode最好能在400w以下;
  • 建表的时候添加use_minimalistic_part_header_in_zookeeper参数,对元数据进行压缩存储,对于高版本的clickhouse可以直接在原表上面修改该setting信息,注意修改完了以后无法再回滚的。

5zookeeper压力太大,clickhouse表处于”read only mode”,插入失败

  • zookeeper机器的snapshot文件和log文件最好分盘存储(推荐SSD)提高ZK的响应;
  • 做好zookeeper集群和clickhouse集群的规划,可以多套zookeeper集群服务一套clickhouse集群。

猜你喜欢

转载自blog.csdn.net/w4187402/article/details/110658335
今日推荐