自从用cloki+clickhouse来替代日志系统后真是一波三折,问题不断Orz。 只能感叹不亏是小众的开源工具,还是一堆坑,下面记一下踩到的一些坑和对clickhouse的优化。
# 查询语句
WITH sel_a AS
(
SELECT
samples.string AS string,
samples.fingerprint AS fingerprint,
samples.timestamp_ns AS timestamp_ns
FROM cloki.samples_v3_dist AS samples
WHERE ((samples.timestamp_ns >= 1704440225031000000) AND (samples.timestamp_ns <= 1704440525031000000)) AND (samples.fingerprint IN (
SELECT sel_1.fingerprint
FROM
(
SELECT fingerprint
FROM cloki.time_series_gin
WHERE (key = 'app') AND (val = 'myapp')
) AS sel_1
ANY INNER JOIN
(
SELECT fingerprint
FROM cloki.time_series_gin
WHERE (key = 'cluster') AND (val = 'test-prod')
) AS sel_2 ON sel_1.fingerprint = sel_2.fingerprint
))
ORDER BY timestamp_ns DESC
LIMIT 100
)
SELECT
JSONExtractKeysAndValues(time_series.labels, 'String') AS labels,
sel_a.*
FROM sel_a
ANY LEFT JOIN cloki.time_series_dist AS time_series ON sel_a.fingerprint = time_series.fingerprint
ORDER BY
labels DESC,
timestamp_ns DESC
CREATE TABLE cloki.time_series
(
`date` Date,
`fingerprint` UInt64,
`labels` String,
`name` String,
INDEX idx_fingerprint_labels (fingerprint) TYPE minmax GRANULARITY 8192
)
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/time_series/{shard}', '{replica}', date)
PARTITION BY date
ORDER BY (fingerprint, labels)
TTL date + toIntervalDay(1)
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1, merge_with_ttl_timeout = 3600
Q:clickhouse其实更适合一次性插入几百万的数据,而不是短时间内大量一条一条的数据插入,就好像现在cloki每一条日志都是一个insert请求,导致大量的单条插入给到clickhouse,然后ck集群的同步会扛不住,会报错merge速度比不上insert速度,然后zookeeper或clickhouse-keeper的负载非常高,触发表readonly的保护机制,只要没merge完就不给写入,因为没有缓存的中间件,一堆日志写入失败丢失。
A:说白了就是clickhouse的使用方法不对,如果是数据量不大还好,官方说只要超过500k每秒的插入,基本上就会导致同步不过来表readonly无法写入。所以网上有一些解决方法:
Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes [,flush_time [,flush_rows [,flush_bytes]]])
, 用下面的例子里的参数解释就是,如果满足最大max的100s或者100w行或者100m的数据其中一个条件,就触发缓存写入到目标表,又或者同时满足所有min的条件过了10s且1w行且10m的数据就会触发写入数据到目标表。不过这个方法官方也说了其实不怎么适用,因为本身clickhouse还是不适合用于短时间大量单条数据的插入,更适合单条插入大量数据的场景,官方建议是每秒不超过1条插入语句。 CREATE TABLE cloki.samples_v3_buffer
(
`fingerprint` UInt64,
`timestamp_ns` Int64 CODEC(DoubleDelta),
`value` Float64 CODEC(Gorilla),
`string` String CODEC(ZSTD(5))
)
ENGINE = Buffer('cloki', 'samples_v3', 16, 10, 100, 10000, 1000000, 10000000, 100000000)
nohup ./clickhouse-bulk -config config.json >/dev/null 2>&1 &
启动即可,所有的小插入会先缓存然后满足配置条件后写入到clickhouse集群里,而且写入失败会生成dump文件,重新尝试插入,相当于缓存了。 {
"listen": ":8124", #启动的端口,clickhouse是8123 ,这个是8124,小心别漏了分号
"flush_count": 1000000, #缓存多少行后写入,我这边指定的是100w行写入一次
"flush_interval": 5000, #缓存多少秒后写入,我这边指定5秒后写入
"clean_interval": 0, #清理内部表的频率,例如插入到不同的临时表,或作为解决query_id等问题的方法,单位:毫秒
"remove_query_id": true, #有些驱动程序发送的query_id会阻止批量插入
"dump_check_interval": 60, #尝试发送转储文件的间隔(秒);-1表示禁用,就是重新尝试插入dump文件的时间
"debug": false, # 记录传入的请求日志
"log_queries": true,
"dump_dir": "dumps", # 转储未发送的数据的目录(如果ClickHouse出错)
"clickhouse": {
"down_timeout": 60, # 服务器宕机时等待的时间(秒)
"connect_timeout": 10, # 等待服务器连接的时间(秒)
"tls_server_name": "", # 覆盖用于证书验证的TLS serverName(例如,如果在多个节点上共享相同的"cluster"证书)
"insecure_tls_skip_verify": false, # 证书验证
"servers": [
"http://127.0.0.1:8123"
]
},
"use_tls": false,
"tls_cert_file": "",
"tls_key_file": ""
}
到目前为止大部分问题解决了,虽然还有一些小问题,但日志系统也慢慢趋于稳定,clickhouse数据库号称比传统mysql数据库快一万倍,但坑还是蛮多的,目前国内用的其实不多,但以后估计会变成香饽饽吧。