共计 3692 个字符,预计需要花费 10 分钟才能阅读完成。
简述
ClickHouse 近两年在开源社区愈发炽热,不知从何开始大家争相用它来替换 ElasticSearch,大略因为 ElasticSearch 的开销太高,在不作为搜索引擎的场景下,肯定水平的暴力搜寻是能够容忍的。
咱们在应用 Skywalking 后发现,它对后端存储要求太高了,应用 (32C + 64G + 2T) x8 的配置,在云平台上每月两三万的开销,性能仍然十分捉急,查问常常超时。前前后后优化了小半年后,最终下定决心替换成 ClickHouse。
在应用为 ClickHouse 后,机器数量缩小了 50%;
查问链路列表从 5.53/s 进步到了 166/s,响应工夫从 3.52s 升高到了 166ms;
查问链路详情从 5.31/s 进步到了 348/s,响应工夫从 3.63s 升高到了 348ms;
链路数据存储工夫从 5 天进步到了 10 天,数据量达到数百亿;
值得一提的是,在与 ES 的比照中常常会提到磁盘空间升高,其实 ClickHouse 的压缩率没有那么夸大,起码在我的理论体验两者相差不大。如果 ES 空间占用很高,那很可能是因为没在索引中开启 codec: best_compression
。
ClickHouse 也并不是没有毛病,本篇文章分享下如何用 ClickHouse 作为 Skywalking 的后端存储。本文不会赘述 ClickHouse 的基本原理,须要读者对 ClickHouse 有肯定理解的状况下浏览。
(因为工作量微小,对 Skywalking 存储的革新仅限于存储链路数据,即 Segment,其余部分就抓大放小,仍应用 ElasticSearch 存储,没有进行革新)
表设计
ClickHouse 根本只能建设一种索引,即 Order By 的索引。而链路表查问条件泛滥,简直每个字段都是查问条件,且蕴含多种排序,设计起来比拟辣手。
查问条件:工夫、服务名称、服务实例、链路类型、链路 ID、链路名称、响应工夫、是否异样
排序条件:工夫、响应工夫
想要在一张表上设计出合乎所有查问模式,根本是不可能的(或齐全不可能),在参考了 jaeger-clickhouse 等泛滥设计后,更加动摇了这个论断。
尝试了数次后,最终的建表语句如下:
CREATE TABLE skywalking.segment
(
`segment_id` String,
`trace_id` String,
`service_id` LowCardinality(String),
`service_instance_id` LowCardinality(String),
`endpoint_name` String,
`endpoint_component_id` LowCardinality(String),
`start_time` DateTime64(3),
`end_time` DateTime64(3),
`latency` Int32,
`is_error` Enum8('success' = 0, 'error' = 1),
`data_binary` String,
INDEX idx_endpoint_name endpoint_name TYPE tokenbf_v1(2048, 2, 0) GRANULARITY 1,
PROJECTION p_trace_id
(
SELECT
trace_id,
groupArrayDistinct(service_id),
min(start_time) AS min_start_time,
max(start_time) AS max_start_time
GROUP BY trace_id
)
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(start_time)
ORDER BY (start_time, service_id, endpoint_name, is_error)
TTL toDateTime(start_time) + toIntervalDay(10)
首先,在 partition 上还是应用了天作为条件。在 Order By 时,应用 工夫 + 服务 + 链路名称 + 异样。为什么不是 服务 + 工夫,因为在很多查问中,工夫作为条件的状况比服务作为条件的频率更高,如果在服务放在前边,大部分时候 ClickHouse 须要在一个文件的不同局部去遍历,IO 会变得扩散。
针对应用链路名称的查问,采纳 ordre by + skip index 的形式优化。链路名称通常是接口名,而接口名在不同服务间反复的概率较小。这样在物理上,雷同 / 类似的链路名称是排列在一起的,再应用 skip index 进一步筛选 granularity,残余的数据很大概率是排列在一起的。这样就尽可能防止了扫描范畴内的所有数据。
针对最重要的 traceId 查问就比拟麻烦,因为查问能够不带任何条件(包含工夫),应用 traceId 实际上是跨工夫的。而 traceId 曾经没方法再塞到索引的任何地位了,在尝试过各种二级索引后,成果仍然十分不现实,能够说根本没什么成果。
最初我应用了过后还是 beta 个性的 Projection,其能够简略了解为表中表(理论在物理上也是这样存储的),即针对 partition 中的数据应用另一种构造存储。Projection 在 ClickHouse 中通常用来做 materialized view(物化视图)应用,相比后者长处是能够主动抉择,以及生命周期受控于 partition。
而在这里我应用 projection 存储了 traceId 对应的 最大起始工夫、最小起始工夫、去重的服务名称列表,再拿失去的后果回源查表。最终的成果还能够承受,这也是为什么前边压测的后果中,查问链路详情的响应工夫根本是查问列表的两倍,因为查了两遍:)
而这套设计形式也有不完满的中央,有两点:
- 响应工夫排序没有优化。查问列表时,如果筛选条件有余会十分慢。
- traceId 对应大量数据或时间跨度十分大时,会十分慢。有时候因为程序问题,或工夫较长的提早工作,会呈现这种状况。
刨除这两点不完满,整体应用下来还是很顺畅的,关上链路页面从转个十秒钟,到秒开。在后盾应用 ClickHouse 剖析服务的链路形成,响应工夫,甚至定时扫描设定告警等,都是额定提供的能力。数据的可见提早也有很大改善,在 ES 中为了进步写入性能,个别 60s 左右能力查问到,在 ClickHouse 中链路从上报到落地只有 5 秒。
ClickHouse 优化与踩坑
相比于 ElasticSearch 成熟的集群治理能力,ClickHouse 还是比拟难服侍的。
写入
客户端应用了官网的 JDBC,用 CSV 组装数据导入,尽量节俭内存。然而这个 JDBC 实现还是有肯定的限度,比方没方法压缩数据包,导致内存占用居高不下。这一点起初在其余我的项目上应用时有优化,后边开文另谈。
大规模写入时,发现 partition 数量居高不下,导致查问最近的数据反而很慢。于是引入了 CHProxy 作为代理,采纳写入本地表,读取分布式表的形式,partition 数量大大降低。
分布式命令
分布式命令(on cluster)是个一言难尽的货色,有时候感觉很不便,有时候又会出很多问题。如分布式命令会在 zookeeper 里沉积,一旦某个命令在一个节点上未执行 / 执行失败,会卡死后续的所有命令。如 create database xxx on cluster yyy
简直必然失败。
集群中退出了新的节点,该节点会执行历史的 on cluster 命令,此时很容易失败导致节点启动不胜利,须要手动到 zk 中清理历史命令。
Merge Partition 问题
ClickHouse 对 partition 治理有一套策略,可参考 ClickHouse 内核剖析 -MergeTree 的 Merge 和 Mutation 机制,参数比拟难调,基本上没什么染指的空间。有时候看它曾经沉积很多了,但它就是不紧不慢。针对较大 part 也不会再进一步 merge,于是还得在凌晨跑个定时工作,将头一天的 part 合并为一个(optimize table #table_name partition #partition_name final
),没方法通过配置解决。
还呈现过比拟诡异的景象,某个节点呈现了 too many part 写入回绝,下来一看 merge 工作在跑,但认真看没有一个工作在真正执行,每个都是进度跑到一半就归零从头开始。后果导致 part 数只增不减。
查阅一番无果,日志中也没有任何提醒。最终猜想是因为内存不足,merge 跑到一半申请不到内存,导致工作失败只得从头开始。尤其是在 merge 工作较多的状况下,会互相挤占内存,接连失败。在该节点查问执行一条 group by 命令,果然执行失败提醒内存不足(在其余节点能够失常执行)。最初缩小了配置中的 background_size,增大了一点内存占用,重启后问题解决。
结语
ClickHouse 是一个很有启发的软件,但也并不是万能的,归根结底更适宜剖析而不是点查。场景不对硬拗的话,很容易变成你服侍它而不是它服侍你。
目前咱们在实际应用 ClickHouse 作为日志存储平台,代替 ElasticSearch(又来)和云平台日志服务,将实现模式无关、存算拆散、租户隔离、疾速剖析等性能,届时会再分享一些教训。