2000亿日写入量!eBay基于ClickHouse事件监控平台建设

时间:2023-01-03 13:08:28

本文根据李先成在 GOPS 2022·上海站演讲整理而成,更多精彩,请关注高效运维公众号。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

作者简介:

李先成,资深软件开发专家。eBay软件开发工程师,多年专注于云计算和监控平台开发,现负责eBay事件和日志监控平台的建设。

本次的分享有四个方面:

背景介绍

事件平台

典型案例

未来展望

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

一、背景介绍

在介绍事件平台之前先看下监控平台的总体情况,我们的监控平台主要有四种信号:指标、日志、追踪和事件。

基于这四种信号,我们提供了多维分析、告警、异常检测等能力,并基于这些能力开发了 BCD 解决方案,自动发现有问题的部署,还有 Groot 解决方案来做根因分析,Exemplar 解决方案来解决关联指标、日志、追踪,从而快速定位问题。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

什么是事件?可以分为用户事件和系统事件。用户事件包含部署、扩容、配置。系统事件包含告警、访问日志等。一般来说事件都是非周期性的,而且包含任意一组键值对,也意味着它的基数可能很高。

从这两个特性来看指标解决方案是不合适的。因为指标解决方案一般都是基于周期性这个假设的,而且指标解决方案通常很难支持基数高的场景。

此外事件一般都需要高效的多维分析,并基于聚合结果配置告警规则和异常检测。但是我们上图中的日志平台是一个非结构化的日志平台,而且缺乏高效的多维分析能力,所以我们就需要引入一个新的事件平台来满足需求,当然未来会继续迭代这个事件平台来支持半结构化和非结构化日志。因为他们也有聚合和分析的需求。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

二、事件平台

我们看下当前事件平台的状况,目前事件平台的写入量是2000亿/天,查询量每日超过500万,总共有400多个 ClickHouse 节点,超过1PB的存储。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

接下来看看为什么选择 ClickHouse 作为存储引擎。主要是从性能、成本、功能三个维度来分析,并最终选择了 ClickHouse 。因为时间问题我就不一一说明了,哪些才是降本增效的关键呢?

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

我们认为首先 ClickHouse 是按列存储,相似数据存储在一起,提高了压缩效率,再结合像 LowCardinality、Delta 等编码,以及LZ4、ZSTD 等压缩算法,通常 ClickHouse 可以达到 10倍甚至100倍的压缩比,相应的降低磁盘读取的耗时。左下角是线上的真实案例,一般的列都有 10 倍甚至几百倍的压缩比。

除了按列存储,ClickHouse还是按列计算的,列式计算由于数据在内存中是连续的,加上算子的向量化,可以大幅提高 CPU 缓存的命中率,减少虚函数的调用、降低分支预测判断失败的概率,带来更好的指令流水,并充分利用 CPU SIMD 指令的并发能力,从而成倍提高效率。

此外,ClickHouse 还部分支持运行时代码生成,对表达式和逻辑操作进行汇编级别的逻辑优化,避免了因解释执行而导致的动态类型转换,从而打断 CPU 流水线情况,大幅提高执行效率。

另外 ClickHouse 在查询并行化处理方面做了很多优化。从纵向看,ClickHouse 按照分区和 granule 把数据拆分,然后通过单机多核同时处理不同的 granule 来提升单机并发查询效率。

从横向看,ClickHouse 通过把查询拆分成多个任务下发到集群中多个分片同时处理来进一步的提高查询效率。从 ClickHouse 2022 年的计划中来看,ClickHouse 甚至计划单分片多副本并行化数据处理,也就是说单个分片的数据拆分到多个副本并行处理,这就是 ClickHouse 存储和查询效率高的主要原因。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

下面看下平台设计,我们的事件平台遵循一键化和自动化运维策略,完全容器化部署,使用 K8s 进行编排,通过 K8s CRD 来实现统一的资源管理。我们用 FCHI CRD 管理跨区域 ClickHouse 集群,我们通过 K8S CRD 统一管理表格、配额等资源。

同时我们的平台也遵循开源和标准化接口设计。事件收集方面采用 CNCF Otel 标准的数据模型、采集、处理和导出方案。查询方面除了 SQL 外还实现了 LogQL 来查询事件,从而无缝对接第三方可视化平台 Grafana 和告警平台 Prometheus。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

(如图)架构设计。Cluster1、Cluster2 和 ClusterN 分别代表K8S的集群,Node1、Node2 和 NodeN 分别代表 K8s 节点。LogAgent 部署在 K8s 节点的 Daemonset 里,用户只需要把事件写在 Pod 本地日志文件,LogAgent 根据注册路径把日志读出来,并根据注册格式,把非结构化日志转化为结构化数据,并为结构化数据加上位置信息。

用户也可以自定义处理逻辑,比如采样、聚合等,通过写入网关服务提供的OTel API写到 ClickHouse 集群。

除了把事件写入到日志文件中,用户也可以直接使用 Otel 标准 SDK 按照 Otel 日志数据模型,通过我们的写入网关服务,把事件写入到 ClickHouse 集群。幻灯片中间上半部分是事件平台的核心组件,其中元数据服务主要包含命名空间、ClickHouse 集群、数据路由等,写入网关服务提供了Otel 标准 API,根据元数据服务提供的数据路由信息找到合适的 ClickHouse 集群,批量写入数据。同时集成了认证、限流、数据验证等功能。

查询网关服务主要有SQL和LogQL两种查询语言,方便用户查询数据。中间下半部分是指标和事件共用的告警平台和异常检测平台,右边是自服务平台,实现事件注册、告警和异常检测服务。

下面详细说下事件平台的核心,我们怎么构建跨区域高可用和水平可扩展的 ClickHouse 集群。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

前面提到 eBay 完全容器化部署,使用 K8s 进行编排。通过 K8s CRD 实现统一的资源管理。这里引入了 CHI CRD 描述单个 K8s 集群的 ClickHouse,引入(FCHI) CRD 描述跨区域的 ClickHouse集群。跨区域意味着跨 K8s 集群,没法使用某个 K8s APIServer 定义 FCHI CRD。通过引入 Federation API servers 定义跨区域的 FCHI CRD,Federation API Server 部署在多个 K8s 集群中,它也是高可用的。

我们在多个K8s群中分别部署了 FCHI 控制器,具体的流程是首先 FCHI 控制器会根据Federation APIServers来选主,成为leader的FCHI控制器会watch Federation APIServer 上的 FCHI CR 根据配置生成多个 in-cluster 的 CHI CR,各个 in-cluster 的 CHI 控制器会将第二步生成的 CHI CR 部署 in-cluster 的 ClickHouse 集群。

右下角是一个2个分片、2个副本,跨两个k8s集群的 ClickHouse 集群的例子。最右边是他们的 remote server 的示例,这里主要是为了展示,所以没有采用 ClickHouse副本服务发现的方式,也就是说每个分片的所有副本都配置同一个路径,这样就可以添加删除节点,不需要更新每个 ClickHouse 节点 remote-server 的配置了。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

当我们实现了跨区域高可用后,接下来要解决水平扩展问题。我们通过FCHI CRD描述跨区域高可用 ClickHouse 集群,这个 CRD 也描述了 ClickHouse 集群的横向和纵向信息,所以我们可以通过 FCHI CRD 来实现 ClickHouse 集群的横向和纵向扩缩容。唯一的问题是 ClickHouse 集群依赖于 Zookeeper 做数据同步和分布式 DDL。

Zookeeper 是 ClickHouse 集群水平扩展的瓶颈,传统的 ClickHouse 集群采用的是单个 Zookeeper 集群,可以参考左图,为了解决 Zookeeper 无法水平扩展的问题,我们采用了两个方案,第一个方案是通过增强 FCHI CRD 把不同的分片指向不同的 Zookeeper 集群,以此实现 ClickHouse 集群的水平扩展。可以参考中间的图,但是缺点是配置复杂,而且引入了更多的 Zookeeper集群,增加了管理负担。

我们尝试了另外一个方案,每个分片用内置的 ClickHouse Keeper 集群。可以参考右边的图,这样既配置简单,同时也没有额外的Zookeeper集群的管理负担,另外还解决了很多问题,比如说ZXID rollover、ZooKeeper日志压缩的问题。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

内置的 ClickHouse Keeper 也有缺点,当副本数量是偶数时,为了让 ClickHouse Keeper 能够正常选主,就需要多区域异构部署,但这样就无法实现高可用,也就是不能容忍异构部署的那个多副本区域不可用。举个例子,从上图可以看到,假设我们的ClickHouse 集群有两个分片、两个副本,这两个副本分别部署在两个区域,区域1和区域2,为了能够有三个ClickHouse Keeper节点,从而可以选主,ClickHouse Zookeeper 只能在这两个区域进行异构部署,比如区域 1 上的两个节点和区域 2 上的一个节点组成 ClickHouse Keeper 的集群,区域 1 不可用的时候 ClickHouse 集群只剩下一个节点,没有达到半数,为了避免脑裂(没办法正常选组),从而导致 ClickHouse Zookeeper 集群也变得不可用了,进而导致整个 ClickHouse 集群也变得不可用。

如果我们采用的是独立的 Zookeeper 部署方式,如下图。虽然 ClickHouse 集群只部署在两个区域,但是 Zookeeper 仍然可以部署在 3 个区域,即使区域 1 不可用,区域 2 和区域 3 的 Zookeeper 节点数量仍然可以超过半数从而可以正常选主,ClickHouse 集群也可以正常工作。

综上所述,副本数量为偶数时,我们目前还是采用Zookeeper或者是把ClickHouse Keeper部署成独立模式而不是内置模式。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

刚刚提到 ClickHouse 集群可以水平扩展,但是从读写效率和数据隔离的角度,我们的事件平台还是采用了多个 ClickHouse 集群的方案。类似于K8s,我们通过命名空间来表示虚拟的 ClickHouse 资源,一个租户有一个或者多个命名空间,我们通过定义命名空间和 ClickHouse 集群的映射关系来定义数据路由。具体通过 WISB 和WIRI来定义。

WISB代表期待的数据路由,WIRI 代表真实的数据路由。当新的命名空间被注册时,数据路由控制器会找到空闲的 ClickHouse 集群,并创建 WISB,指定命名空间和空闲的ClickHouse 集群的映射关系。接着数据路由控制器会在创建命名空间下的所有表格完成后写入 WIRI,表示这条数据路由规则已完成。

数据迁移相对复杂一点,我们没有采用 ClickHouse Copier、Copy partition/parts 等去做,因为这种复制方式比较重,我们采用的是轻量级的虚拟集群+分布式表来实现无复制迁移。当我们需要数据迁移的时候,首先我们会为这个命名空间插入一个新的 WISB 记录,代表我们期待的命名空间和 ClickHouse 集群的映射关系。数据路由控制器会发现 WISB,并在目标的 ClickHouse 中创建表格,创建虚拟集群,包含新旧 ClickHouse集群,并更新分布式表指向虚拟集群,最后写入 WIRI 反映数据路由规则已完成。

我们的写入网关服务会按照 WIRI 指定的映射关系把数据写入新的 ClickHouse 集群,查询时通过分布式表指向虚拟集群,仍然可以查询当前和历史数据。当数据路由控制器发现有效期内的命名空间只有一个 WISB 的时候,就会更新分布式表到目标 ClickHouse 集群,并删除之前创建的虚拟集群。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

高可用水平可扩展和数据路由+迁移已经能满足大部分的用户需求,不过有些事件对于事件准确性要求很高,所以我们引入了读写分离的支持。

如果没有读写的分离能力,查询过多还是有可能导致数据丢失。我们没有采用 ClickHouse 存算分离的方案来实现读写分离,主要因为存算分离的方案需要使用远程存储,但是我们采用的是冷热分层的架构,而且热存储使用的是本地的 SSD 磁盘。为了实现读写分离,我们通过 FCHI 中引入 readWriteMod 实现读写分离,readWriteMod 大于 1 的时候表示启用读写分离。我们会通过 replica_num 对 readWriteMod 取余来确定节点的职责。

这么分配的原因主要是读的压力通常比较大,所以我们会分配一个写节点和多个读节点。FCHI 控制器会通过节点职责创建读和写两个子虚拟集群,把分布式表指向读子虚拟集群,同时写入网关也会通过写虚拟子群发现写节点,达到读写分离的效果。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

下面讨论一下查询语言,除了SQL DSL查询语言外,为了提供类似于指标类Prometheus PromQL 语法类似的 DSL 并兼容 Prometheus 告警平台及异常检测平台,我们开发了基于 ClickHouse 的 LogQL 的 DSL。有一个社区项目叫 cLoki 也是做的类似的事情,但是 cLoki 对不固定 schema 的支持没有完全遵循 LogQL 的语法,另外 cLoki 没有尽可能下推算子和聚合函数,所以有些查询性能不高。为了提高查询效率,我们尽可能下推几乎所有 LogQL 的算子和聚合函数,并且还可以自动回退到 LogQL 引擎来运算不能下推的部分。不过这样的话,查询性能会大打折扣。

原始数据查询的下推很简单,主要是过滤下推、限制下推等。聚合数据查询的下推会复杂一点,主要是时间维度和空间维度的聚合函数,比如min,max,count,quantile 等。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

虽然我们已经下推了绝大多数的算子和聚合函数,但是有时候没办法达到期待的查询性能,这往往是 ClickHouse SQL 执行计划的问题,主要原因是 ClickHouse 缺乏查询优化器的支持,这时候需要优化生成的查询语言来提高查询性能。比如这里是最简单的原始数据查询,选择了最近 6 个小时,按照时间降序排序,返回前 1000 条记录,如果我们下推了过滤、排序、限制等算子到 ClickHouse,那么生成的 SQL 就是左边这张图,但是这个查询在大数据量的时候表现并不好。比如最近6小时数据量有1亿条,往往需要几十秒才只能返回一千条数据。

根本原因是 ClickHouse 在根据分区和索引筛选完 Granule 以后,为了充分利用列存储优势,会首先读取 PreWhere 的列表,过滤掉不符合条件的数据。问题是这里符合条件的有1亿条数据,这时候 ClickHouse 是先读取这1亿条数据的其他列,而不是按照unixtime 排序,找到前一千行数据然后再读其他列,所以性能很差。

解决方法很简单(如右图),可以采用列裁剪,先找到符合排序和限制条件的子时间区间,利用子时间区间的 Prewhere 条件过滤掉1亿条这种大部分数据,然后再读取其他列,这样就可以充分利用列存储优势,查询性能有20-50倍的性能提升。 2000亿日写入量!eBay基于ClickHouse事件监控平台建设

提供 LogQL DSL后,熟悉 PromQL 的用户可以通过 LogQL 来快速创建可视化视图,左上图是我们通过 LogQL 创建的各个日志中所产生的错误数量,通过表格显示具体错误以及和上周错误数量比较的信息。对 LogQL不熟悉的用户可以通过Grafana提供的查询生成器,通过简单的下拉菜单选择完成聚合查询、明细查询和可视化视图的创建。

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

接下来分享一下 ClickHouse 最佳实践。

首先是写入优化,虽然 ClickHouse 在存储和查询的效率很高,但是 ClickHouse 也有很多问题,其中一个很重要的问题是 ClickHouse 过于手动挡,正常情况下一个写入就会产生一个 Part 文件,为了避免产生很多 Part 文件导致 ClickHouse 来不及 Merge,我们需要批量写入来提高写入性能,除了批量写入,我们还可以考虑复用 ClickHouse 网络链接,按照 SortingKey 排序后在写入 ClickHouse,进一步提高 ClickHouse 的写入性能。

第二是稀疏索引,一张表可能有千亿、万亿条数据,所以 ClickHouse 采用的是稀疏索引,ClickHouse 的点查性能并不是很好,不过分析型数据库主要是范围查询,我们只需要根据我们的查询方式,对数据进行排序,并设置主键索引和跳数索引来提高查询效率。如果有多种查询方式,一种数据排序方式没办法满足需求的时候,我们还可以通过投影来实现空间换时间,查询的时候可以自动选择最合适的数据排序方式提高查询效率。

定义复合索引时,一般我们的推荐是查询频率大和基数小的在前,另外一定要注意跳数索引的使用,如果跳数索引不能有效过滤 Granule,不应该设置跳数索引,会影响写入和查询性能。

第三是查询优化,查询的时候要注意列裁减和分区裁减,每个查询都应该只查询需要的列和分区,这样能充分利用列存储优势。ClickHouse 的 JOIN 很弱,尽可能地使用 IN 代替 JOIN,性能会比 JOIN 好很多。大小表 JOIN 的时候,一般是大表在左边,小表在右边。通常情况下,当右表数量大的时候,我们通常采用 Colocate JOIN。

最后是参数配置,虽然一般平台都会有比较完善的熔断,限流和降级功能,单个ClickHouse 集群节点还是需要更精细化的配置保证 ClickHouse 节点的稳定运行,比如说单个查询的最大 CPU 数量、最大内存以及最大扫描行和返回行等。

三、典型案例

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

eBay主要是使用服务网格来进行流量控制,包括路由和负载均衡。服务网格的监控一直是个难点,主要是数据量很大,比如我们面向用户的服务网格,一天有 300 亿条访问日志,并且为了全方位监控我们创建了大概 500 多个并发执行的异常检测的工作。

原来这些数据是存储在 ElasticSearch 上 ,当我们迁移到 ClickHouse 上,只用了30%的资源把保留时间从9天延长到30天,节省了90%的存储,并通过索引和物化视图把异常检测的查询性能也提高了10倍。

面向用户的服务网格只是一小部分,我们准备把整个公司的服务网格访问日志放到事件平台上,大概有100倍的流量,从成本角度考虑我们会在边缘启用采样和预聚合等来降低成本。

四、未来展望

2000亿日写入量!eBay基于ClickHouse事件监控平台建设

目前我们的事件平台只支持完全结构化的数据,并不支持非结构化数据和半结构化数据,我们打算扩展事件平台来支持它们。

这里有2个挑战,一个是 Free schema 的支持,这个问题我们初步的想法是采用 Map + 物化列,用 Map 存储 Free schema 信息,如果遇到某个 key 的查询性能瓶颈,就将这个 key 转化为物化列,提高存储和查询效率。使用物化列后,用户可以使用 Map 或者物化列访问这个 key 新老数据。另外 ClickHouse 的新版本已经有了 JSON 数据类型的支持,ClickHouse 为 JSON 列的每个 key 动态创建子列,存储和查询性能都是很好的,但是单 Key 基数过大的时候,写入还是不太稳定,官方建议是不超过10K,实际应该比这个更少。

另外一个挑战是对于大流量日志我们打算采用 In-region 部署节省跨区域网络带宽。从控制平面看,这种部署方式我们 FCHI CRD 已经可以支持,不过数据平面的跨区域聚合查询的优化是必不可少的。

假如用户需要计算 region1 和 region2 的跨区域的 group by x 的 y 的平均值,如果用左边的查询语句,ClickHouse 是没法自动优化为右边的查询语句,也就是说这个查询需要从远程节点分别拿到 region1 和 region2 两张表的原始数据,传输到发起人节点后再聚合,这样效率是很低的。优化的做法如右图,也就是说对 region1 和 region2 的表在远程进行预聚合,然后把预聚合的中间状态发送给发起人节点完成聚合。这样就可以避免原始数据的网络传输,从而大大提高查询效率。

我的分享就到这里,谢谢大家!