基于k8s的ClickHouse运维指南

1,003 阅读6分钟

1. 使用Clickhouse并基于k8s的目的

  • 从功能上来说:提供实时的指定的几列查询
  • 从场景上来说:实时查看多维数据报表 基于云厂商的k8s集群可以节约公司非常多的实体机运维、监控成本,比如说电费、人工维护等等。长期来说,基于云的托管模式对于只需要专注于业务的公司来说,云提供的便利远大于支出。

本文的目的基于为提供clickhouse集群长期稳定的数据插入和查询而作。

具体内容有:

  1. 如何建表
  2. 如何基于k8s建立pod*(提供代码,不清楚的可以提问哈)*
  3. 如何处理各类常见的ck问题

2. 建表方式

在实际业务中,采用ClickHouse提供的kafka引擎拉取数据,写入本地表中。 可参考demo:

2.1 本地表

存储的是完整的数据,需要关注的是根据什么key来分partition。这影响插入和查询的效率。

CREATE TABLE aladdin_monitor.bid_stats ON CLUSTER monitoring \
( \
    `timestamp` DateTime, \
    `publisherId` Int64,
    `appId` Int64, \
    `unitId` Int64, \
    `algorithm` String,
    `adType` String, \
    `platform` String,
    `sdkVersion` String, \
    `appVersion` String, \
    `countryCode` String,
    `channel` String, \
    `dspId` String, \
    `nbr` Int64, \
    `reject_code` Int64,
    `eventType` Int64, \
    `price` Float64,
    `mediationName` String,
    `filterMsg` String, \
    `total` UInt64 \
) \
ENGINE = ReplicatedMergeTree('/clickhouse/{cluster}/operator/aladdin_monitor/tables/bid_stats/{shard}', '{replica}') \
PARTITION BY (toYYYYMMDD(timestamp),platform) \
ORDER BY timestamp \
TTL timestamp + toIntervalDay(8) \
SETTINGS index_granularity = 8192, enable_mixed_granularity_parts = 1, merge_with_ttl_timeout = 3600;

2.2 分布式表

分布式表主要的作用是:

  1. 可以通过kafka引擎拉取数据,写入分布式表,通过分布式表设置的key进行分发数据到各个replica中。kafka的角色主要是shard,shard由replica组成。replica之间会通过kafka同步数据,相互备份。
  2. 通过分布式表进行查询。分布式表会向各个shard查询数据并汇总。
CREATE TABLE aladdin_monitor.bid_stats_cluster ON CLUSTER monitoring \
( \
    `timestamp` DateTime, \
    `publisherId` Int64, \
    `appId` Int64, \
    `unitId` Int64, \
    `algorithm` String,
    `adType` String, \
    `platform` String, \
    `sdkVersion` String, \
    `appVersion` String, \
    `countryCode` String,
    `channel` String, \
    `dspId` String,
    `nbr` Int64, \
    `reject_code` Int64,
    `eventType` Int64, \
    `price` Float64,
    `mediationName` String, \
    `filterMsg` String, \
    `total` UInt64 \
) \
ENGINE = Distributed('monitoring', 'aladdin_monitor', 'bid_stats', rand())

2.3 视图

视图可以选择将kafka引擎拉取的数据,写入本地表还是分布式表,并选择具体的字段插入。

本文的选择是插入本地表。原因后文会说明:

CREATE MATERIALIZED VIEW aladdin_monitor.bid_stats_consumer TO aladdin_monitor.bid_stats \
( \
    `timestamp` DateTime, \
    `publisherId` Int64, \
    `appId` Int64, \
    `unitId` Int64, \
    `algorithm` String, \
    `adType` String,
    `platform` String, \
    `sdkVersion` String,
    `appVersion` String,
    `countryCode` String, \
    `channel` String, \
    `dspId` String, \
    `nbr` Int64, \
    `reject_code` Int64, \
    `price` Float64, \
    `eventType` Int64, \
    `mediationName` String, \
    `filterMsg` String, \
    `total` UInt64 \
) AS \
SELECT \
    timestamp, \
    publisherId, \
    appId, \
    unitId, \
    algorithm, \
    adType, \
    platform, \
    sdkVersion, \
    appVersion, \
    countryCode, \
    channel, \
    dspId, \
    nbr, \
    reject_code, \
    price, \
    eventType, \
    mediationName, \
    filterMsg, \
    count() AS total \
FROM aladdin_monitor.bid_stats_raw \
GROUP BY \
    timestamp, \
    publisherId, \
    appId, \
    unitId, \
    algorithm, \
    adType, \
    platform, \
    sdkVersion, \
    appVersion, \
    countryCode, \
    channel, \
    dspId, \
    nbr, \
    reject_code, \
    price, \
    eventType, \
    mediationName, \
    filterMsg 

2.4 kafka引擎表

通过该表拉取kafka远程数据

CREATE TABLE aladdin_monitor.bid_stats_raw ON CLUSTER monitoring \
( \
    `timeString` String, \
    `timestamp` DateTime, \
    `publisherId` Int64, \
    `appId` Int64, \
    `unitId` Int64, \
    `algorithm` String, \
    `scenario` String, \
    `adType` String, \
    `platform` String, \
    `sdkVersion` String, \
    `appVersion` String, \
    `countryCode` String, \
    `cityCode` Int32, \
    `adSourceId` Int64, \
    `channel` String, \
    `dspId` String, \
    `token` String, \
    `bidId` String, \
    `price` Float64, \
    `nbr` Int64, \
    `reject_code` Int64, \
    `campaignId` String, \
    `eventType` Int64, \
    `clientIp` String, \
    `filterMsg` String, \
    `s2sBidId` String, \
    `buyeridTimestamp` String, \
    `bidIsNotUSDCur` Int8, \
    `currency` String, \
    `currencyPrice` Float64, \
    `adBackend` Int64, \
    `adBackendData` String, \
    `osVersion` String, \
    `brand` String, \
    `model` String, \
    `screenSize` String, \
    `orientation` Int8,
    `language` String, \
    `networkType` Int8, \
    `mccMnc` String, \
    `remoteIp` String, \
    `serverIp` String, \
    `imei` String, \
    `mac` String, \
    `androidId` String, \
    `gaid` String, \
    `idfa` String, \
    `extFlowtagId` String, \
    `adNum` Int8, \
    `randValue` Int64, \
    `backendConfig` String, \
    `thirdTemplate` String, \
    `extDsp` String, \
    `reqBackend` String, \ 
    `rejectData` String, \
    `priceFactor` String, \
    `testBidMode` Int8, \
    `bidFloor` Float64, \
    `rankerInfo` String, \
    `mediationName` String, \
    `extra3` String, \ 
    `extAlgo` String, \
    `extAdxAlgo` String, \
    `placementId` Int64,
    `extData` String, \
    `userAgent` String, \
    `idfv` String, \
    `useDynamicTmax` String, \
    `tmaxABTestTag` String, \
    `masResponseTime` String, \
    `request_type` Int8
)
ENGINE = Kafka
SETTINGS kafka_broker_list = 'ip+port', kafka_topic_list = 'aladdin-fluentd_bid-log_txt-0', kafka_group_name = 'operator-horus-aladdin', kafka_row_delimiter = '\n', kafka_format = 'TabSeparated', kafka_skip_broken_messages = 100

3. clickhouse维护中的优化

image.png

原有改进
存储Pod间存储倾斜,资源浪费存储均匀,合理利用资源
运维Pod被驱逐后,自启动失败可自启动
插入周期性出现数据无法插入稳定支持每日40亿+数据插入
查询每次查询数据结果不一致支持幂等查询

3.1 存储倾斜

image.png

存储倾斜的情况: 通过排查每个节点的存储情况发现两个现象

  1. 不同分片的副本1间存储差异并不大
  2. 分片内副本1和副本2的存储差异较大

现象一说明了,clickhouse通过kafka引擎写入本地表而不是分布式表的方式,不会造成特别大的数据倾斜

现象二说明了,由于分片内副本2长时间未启用,与副本1内容差异较大,在同步的过程中,会把本地已存在的重复数据放在detach路径下,所以产生了大量的数据副本。导致了存储的浪费。

3.2 pod自启动失败

image.png 所以在问题2,pod自启动失败中发现:

经过对pod的线程排查发现,每次镜像启动会先chown所有文件。在pod长期运行期间,生成的文件数越多(有20w的量级),chown的时间就越长,直到超过了pod启动的时间限制,导致pod启动失败。

通过重新制作镜像中entrypoint的chown修复了该问题。

解决完这两个问题后,通过新集群与老集群的数据对比,发现新集群的数据条数大于老集群,并且新集群的数据包含老集群的数据。启动失败的副本2里面还有新的数据没和副本1同步,所以部分数据没有查询到。说明老集群报表的查询结果是有偏差的。对比了报表的内容,具体的数值不一样。

3.3 消费不了kafka数据

image.png 出现了几个现象:

  • 显示表为只读模式
  • 显示一直在与zk重连
  • 写入表的速度大于merge的速度,所以无法插入数据

这里最关键的原因是zk部署的节点太少,负载太重。因为clickhouse每次写入数据后,都需要将元数据上传到zk,由zk生成异步任务,交给其他副本读取同步。所以,clickhouse对zk的依赖是比较严重的,批量写入ck,ck和zk的交互会很多,对zk会造成很大压力。

  1. Zk负载过重后,造成zk的操作超时,导致与ck出现会话频繁超时,为了保证数据的一致性,会使ck的本地表切换到只读模式。
  2. 切换到只读模式后,实时数据无法成功插入ck,触发kafka的rebalance,将当前的分区分配到其他的节点尝试插入。可插入的节点少了,数据频繁插入单一节点,所以merge速度赶不上,也拒绝数据插入。
  3. 由于ck与zk的连接频繁超时,所以zk会不断跟ck建立连接,重建会话。连接数的增多,zk的负载也会增加。偶然建立了会话后,ck会消费数据,跟zk进行若干的同步操作,zk的负载就降不下来,这样引发了雪球效应。

最后通过增加zk数量得以解决。

4. 常用的排查和脚本语句

参考链接【3】

以下提供经常使用的:

4.0 查询集群分布的节点,用于验证remote_servers.xml

SELECT cluster,host_name FROM  system.clusters;

4.1 排查时间段的慢查询

select count(*) 
from system.query_log 
where query_start_time > '2022-01-19 02:00:39' 
and query_start_time < '2022-01-19 12:37:39' 
and query_duration_ms > 24161

4.2 统计在某时段数据数

前提是表内有相关的时间戳

SELECT COUNT(*) 
FROM adn_monitor.adn_rich_creative_report_distributed 
where timestamp between  '2022-01-18 00:00:00' and '2022-01-18 23:59:59'

4.3 利用shell删除pod或者其他目的

for i in `seq 0 9` ; 
    do for j in `seq 0 1` ;
        do name=chi-clickhouse-shard-monitoring-${i}-0-0; 
        echo $name ; 
        kubectl delete pod ${name} -n ch-operator ; 
    done; 
done;

5. 运维代码

github.com/jessezax/Cl…

6. 参考链接

  1. clickhouse.com/docs/zh/eng…
  2. github.com/Altinity/cl…
  3. blog.csdn.net/qq_41018861…
  4. www.infoq.cn/article/g94…
  5. zhuanlan.zhihu.com/p/345354936
  6. xie.infoq.cn/article/378…