1. 使用Clickhouse并基于k8s的目的
- 从功能上来说:提供实时的指定的几列查询
- 从场景上来说:实时查看多维数据报表 基于云厂商的k8s集群可以节约公司非常多的实体机运维、监控成本,比如说电费、人工维护等等。长期来说,基于云的托管模式对于只需要专注于业务的公司来说,云提供的便利远大于支出。
本文的目的基于为提供clickhouse集群长期稳定的数据插入和查询而作。
具体内容有:
- 如何建表
- 如何基于k8s建立pod*(提供代码,不清楚的可以提问哈)*
- 如何处理各类常见的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 分布式表
分布式表主要的作用是:
- 可以通过kafka引擎拉取数据,写入分布式表,通过分布式表设置的key进行分发数据到各个replica中。kafka的角色主要是shard,shard由replica组成。replica之间会通过kafka同步数据,相互备份。
- 通过分布式表进行查询。分布式表会向各个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维护中的优化
| 原有 | 改进 | |
|---|---|---|
| 存储 | Pod间存储倾斜,资源浪费 | 存储均匀,合理利用资源 |
| 运维 | Pod被驱逐后,自启动失败 | 可自启动 |
| 插入 | 周期性出现数据无法插入 | 稳定支持每日40亿+数据插入 |
| 查询 | 每次查询数据结果不一致 | 支持幂等查询 |
3.1 存储倾斜
存储倾斜的情况: 通过排查每个节点的存储情况发现两个现象
- 不同分片的副本1间存储差异并不大
- 分片内副本1和副本2的存储差异较大
现象一说明了,clickhouse通过kafka引擎写入本地表而不是分布式表的方式,不会造成特别大的数据倾斜
现象二说明了,由于分片内副本2长时间未启用,与副本1内容差异较大,在同步的过程中,会把本地已存在的重复数据放在detach路径下,所以产生了大量的数据副本。导致了存储的浪费。
3.2 pod自启动失败
所以在问题2,pod自启动失败中发现:
经过对pod的线程排查发现,每次镜像启动会先chown所有文件。在pod长期运行期间,生成的文件数越多(有20w的量级),chown的时间就越长,直到超过了pod启动的时间限制,导致pod启动失败。
通过重新制作镜像中entrypoint的chown修复了该问题。
解决完这两个问题后,通过新集群与老集群的数据对比,发现新集群的数据条数大于老集群,并且新集群的数据包含老集群的数据。启动失败的副本2里面还有新的数据没和副本1同步,所以部分数据没有查询到。说明老集群报表的查询结果是有偏差的。对比了报表的内容,具体的数值不一样。
3.3 消费不了kafka数据
出现了几个现象:
- 显示表为只读模式
- 显示一直在与zk重连
- 写入表的速度大于merge的速度,所以无法插入数据
这里最关键的原因是zk部署的节点太少,负载太重。因为clickhouse每次写入数据后,都需要将元数据上传到zk,由zk生成异步任务,交给其他副本读取同步。所以,clickhouse对zk的依赖是比较严重的,批量写入ck,ck和zk的交互会很多,对zk会造成很大压力。
- Zk负载过重后,造成zk的操作超时,导致与ck出现会话频繁超时,为了保证数据的一致性,会使ck的本地表切换到只读模式。
- 切换到只读模式后,实时数据无法成功插入ck,触发kafka的rebalance,将当前的分区分配到其他的节点尝试插入。可插入的节点少了,数据频繁插入单一节点,所以merge速度赶不上,也拒绝数据插入。
- 由于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;