ClickHouse 相关组件
- OpenTelemetry Collector 是一个代理,用于接收、处理和导出遥测数据。基于 ClickHouse 的解决方案会使用该组件进行日志采集,并在批处理和 insert 之前处理事件。
- 语言 SDK 用于实现规范、API 以及遥测数据的导出。这些 SDK 可确保在应用程序代码中正确记录 trace,生成其组成的 span,并通过元数据确保上下文在服务之间传播——从而形成分布式链路追踪,并确保 span 之间可以关联。与此同时,围绕这些 SDK 还形成了一个生态系统,可为常见库和框架自动实现这些能力,因此用户无需修改代码,即可开箱即用地获得插桩能力。
发行版
- 缩小collector体积,从而减少collector的部署时间
- 通过减少可暴露的攻击面来提高collector的安全性
通过 OTel 摄取数据
collector 部署角色
- Agent - Agent 实例在边缘收集数据,例如在服务器或 Kubernetes 节点上,或者直接从使用 OpenTelemetry SDK 进行埋点的应用程序接收事件。在后一种情况下,agent 实例与应用程序一起运行,或运行在与应用程序相同的主机上 (例如作为 sidecar 或 DaemonSet 守护进程集) 。Agent 可以将数据直接发送到 ClickHouse,也可以发送到 gateway 实例。前一种情况通常称为 Agent 部署模式。
- Gateway - Gateway 实例提供独立服务 (例如 Kubernetes 中的一个部署) ,通常按 cluster、数据中心或区域部署。它们通过单个 OTLP 端点接收来自应用程序 (或作为 agent 的其他collector) 的事件。通常会部署一组 gateway 实例,并使用现成的负载均衡器在它们之间分摊负载。如果所有 agent 和应用程序都将其遥测数据发送到这一个端点,这通常称为 Gateway 部署模式。
收集日志
- 通过 Filelog receiver 抓取 - 该 receiver 会持续跟踪磁盘上的文件,并生成日志消息,然后将其发送到 ClickHouse。该 receiver 可处理多种复杂任务,例如检测多行消息、处理日志轮转、通过检查点机制增强重启后的稳健性,以及提取结构。该 receiver 还可以跟踪 Docker 和 Kubernetes 容器日志,并可作为 Helm 图表部署,从中提取结构,再结合 pod 详情对其进行富化。
提示:
otelbin.iootelbin.io 可用于验证和可视化配置。结构化与非结构化
示例
json_parser operator。请将路径修改为 access-structured.log 文件的实际路径。
考虑使用 ClickHouse 进行解析下面的示例会从日志中提取 timestamp。这需要使用
json_parser operator,它会将整行日志转换为 JSON 字符串,并将结果放入 LogAttributes。这样做的计算开销可能较高,而在 ClickHouse 中可以更高效地完成——使用 SQL 提取结构。与之对应的非结构化示例使用 regex_parser 实现相同效果,可在这里找到。filelog receiver) ;例如,用户下载的不应是 otelcol_0.102.1_darwin_arm64.tar.gz,而应是 otelcol-contrib_0.102.1_darwin_arm64.tar.gz。发布版本可在这里找到。
安装完成后,可以使用以下命令运行 OTel collector:
Body 字段中,但借助 json_parser,其中的 JSON 已自动提取到 Attributes 字段中。同一个操作符也被用来将时间戳提取到对应的 Timestamp 列。有关使用 OTel 处理日志的建议,请参见处理。
操作符操作符是日志处理的最基本单元。每个操作符只负责一项任务,例如从文件中读取行,或从某个字段中解析 JSON。随后,这些操作符会在管道中串联起来,以实现所需的处理效果。
TraceID 或 SpanID 字段。如果这些字段存在,例如在用户实现分布式链路追踪的场景中,也可以使用上文展示的相同技术从 JSON 中提取出来。
对于需要采集本地或 Kubernetes 日志文件的用户,我们建议先熟悉 filelog receiver 的可用配置选项,以及它如何处理偏移量和多行日志解析。
收集 Kubernetes 日志
ResourceAttributes 列中。ClickHouse 当前对该列使用 Map(String, String) 类型。有关如何处理和优化此类型的更多信息,请参阅 Using Maps 和 Extracting from maps。
收集链路追踪
示例
telemetrygen 工具来生成 trace 数据。安装请按照此处的说明进行。
以下配置会先通过 OTLP receiver 接收 trace 事件,然后将其发送到 stdout。
config-traces.xml
telemetrygen 向 collector 发送 trace 事件:
处理——过滤、转换和富化
-
处理器 - 处理器会对由接收器收集的数据进行修改或转换,然后再将其发送到导出器。处理器会按照 collector 配置中
processors部分定义的顺序依次应用。它们是可选的,但通常建议使用推荐的最小处理器集合。将 OTel collector 与 ClickHouse 一起使用时,我们建议将处理器限制为:- 使用 memory_limiter 来防止 collector 出现内存不足。有关建议,请参见资源估算。
- 任何基于上下文执行富集的处理器。例如,Kubernetes Attributes Processor 可利用 k8s 元数据,自动为 spans、指标和日志设置资源属性,例如用源 pod id 对事件进行富化。
- 如果链路追踪需要,可使用 Tail 或 head sampling。
- 基本过滤 - 丢弃不需要的事件;如果无法通过 operator 实现,可在这里完成 (见下文) 。
- 批处理 - 在与 ClickHouse 配合使用时至关重要,可确保数据按批次发送。参见“导出到 ClickHouse”。
- 操作符 - Operators 是接收器中最基础的处理单元。这里支持基本解析,可设置 Severity 和 Timestamp 等字段;同时也支持 JSON 和正则解析,以及事件过滤和基本转换。我们建议在这里执行事件过滤。
示例
regex_parser) 并过滤事件,同时使用处理器对事件进行批次处理并限制内存使用量。
config-unstructured-logs-with-processor.yaml
导出到 ClickHouse
使用 OpenTelemetry Collector ContribClickHouse exporter 属于 OpenTelemetry Collector Contrib,而不属于核心分发版。您既可以使用 Contrib 分发版,也可以自行构建收集器。
- pipelines - 上述配置展示了 pipelines 的用法。它由一组 receivers、processors 和 exporters 组成,并分别包含一个用于日志和链路追踪的管道。
- endpoint - 与 ClickHouse 的通信通过
endpoint参数配置。连接字符串tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1表示通过 TCP 进行通信。如果你因流量切换等原因更希望使用 HTTP,请按这里所述修改此连接字符串。完整的连接详细信息 (包括在此连接字符串中指定用户名和密码) 见这里。
- ttl - 此处的值决定数据保留的时长。更多详情见“管理数据”。该值应使用小时作为时间单位,例如 72h。下面的示例中我们禁用了 TTL,因为我们的数据来自 2019 年,如果插入,ClickHouse 会立即将其删除。
- traces_table_name 和 logs_table_name - 决定日志表和链路追踪表的名称。
- create_schema - 决定是否在启动时使用默认 schema 创建表。为了便于快速上手,默认值为 true。你应将其设为 false,并自行定义 schema。
- database - 目标数据库。
- retry_on_failure - 用于确定是否重试失败批次的设置。
- batch - batch 处理器可确保事件按批次发送。我们建议值至少为 10,000,timeout 为 5s (如果内存允许,最高可设为 100,000) 。哪个条件先达到,就会触发将一个批次 flush 到 exporter。降低这些值会让管道延迟更低,数据也能更快可供查询,但代价是会建立更多连接,并向 ClickHouse 发送更多批次。如果你未使用异步插入,则不建议这样做,因为这可能会导致 ClickHouse 中出现 parts 过多 问题。相反,如果你使用了异步插入,数据何时可供查询还将取决于异步插入相关设置——不过数据仍会更早从 connector flush 出去。更多详情请参见 Batching。
- sending_queue - 控制发送队列的大小。队列中的每一项都包含一个批次。如果超过该队列容量,例如由于 ClickHouse 不可达但事件仍持续到达,这些批次将被丢弃。
telemetrygen 工具运行以下命令:
开箱即用 schema
create_schema 禁用此行为。此外,日志表和链路追踪表的名称也可以通过上述设置修改,默认分别为 otel_logs 和 otel_traces。
在下方的 schema 中,我们假设生存时间 (TTL) 已启用并设置为 72h。
otelcol-contrib v0.102.1) :
- 默认情况下,该表通过
PARTITION BY toDate(Timestamp)按日期分区,因此可以高效删除过期数据。 - 生存时间 (TTL) 通过
TTL toDateTime(Timestamp) + toIntervalDay(3)设置,并与 collector 配置中设置的值一致。ttl_only_drop_parts=1表示仅当某个 part 中包含的所有行都已过期时,才会丢弃整个 part。这比删除 part 内部的行更高效,因为后者会触发代价高昂的删除操作。我们建议始终启用此设置。更多详情请参见使用 TTL 进行数据管理。 - 该表使用经典的
MergeTree引擎。这对于日志和链路追踪是推荐选择,通常无需修改。 - 该表按
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId)排序。这意味着查询会针对ServiceName、SeverityText、Timestamp和TraceId上的过滤条件进行优化——列表中越靠前的列,过滤速度越快。例如,按ServiceName过滤会明显快于按TraceId过滤。你应根据预期的访问模式调整此排序方式——参见选择主键。 - 上述 schema 对各列应用了
ZSTD(1)。这能为日志提供最佳压缩效果。你可以提高 ZSTD 压缩级别 (高于默认值 1) 以获得更好的压缩率,不过这种收益通常不大。提高该值会在写入时 (压缩期间) 带来更高的 CPU 开销,但解压缩性能 (以及查询性能) 应基本保持不变。更多详情见这里。此外,还对 Timestamp 额外应用了 delta 编码,以减少其磁盘占用。 - 注意,
ResourceAttributes、LogAttributes和ScopeAttributes都是 Map。理解它们之间的区别非常重要。关于如何访问这些 Map,以及如何优化其中键的访问,请参见“Using maps”。 - 这里大多数其他类型也都已做过优化,例如
ServiceName使用了 LowCardinality。另请注意,在我们的示例日志中,Body虽然是 JSON,但存储为 String。 - 布隆过滤器已应用于 Map 的键和值,以及
Body列。它们旨在提升访问这些列的查询性能,但通常并非必需。参见二级索引 / 数据跳过索引。
优化插入
批处理
- (1) 如果接收数据的节点出现问题,插入查询会超时 (或返回更具体的错误) ,并且不会收到确认。
- (2) 如果节点已经写入了数据,但由于网络中断,确认无法返回给查询发送方,那么发送方会收到超时或网络错误。
timeout 到达之前刷新批次,从而确保管道的端到端延迟保持在较低水平,同时批次大小也保持一致。
使用异步插入
timeout 一到,就会发送小批次数据。这可能会带来问题,此时就需要使用异步插入。这种情况通常出现在将 agent 角色的 collector 配置为直接向 ClickHouse 发送数据时。Gateway 作为聚合器可以缓解这一问题——请参见使用 Gateway 扩缩容。
如果无法保证大批次,你可以使用异步插入将批处理交给 ClickHouse。使用异步插入时,数据会先写入 buffer,之后再延迟写入数据库存储,也就是以异步方式写入。
启用异步插入后,当 ClickHouse ① 收到插入查询时,查询中的数据会先②立即写入内存 buffer。到③下一次 buffer flush 时,buffer 中的数据会被排序,并作为一个分片写入数据库存储。请注意,在数据 flush 到数据库存储之前,这些数据无法被查询到;buffer flush 是可配置的。
要为 collector 启用异步插入,请在 connection string 中添加 async_insert=1。我们建议使用 wait_for_async_insert=1 (默认值) 以获得交付保障——更多详情请参见这里。
异步插入的数据会在 ClickHouse buffer flush 后写入。当超过 async_insert_max_data_size 时,或自第一条 INSERT 查询起经过 async_insert_busy_timeout_ms 毫秒后,就会发生这种情况。如果 async_insert_stale_timeout_ms 设置为非零值,则会在距上一条查询过去 async_insert_stale_timeout_ms milliseconds 后插入数据。你可以调整这些设置,以控制管道的端到端延迟。更多可用于调优 buffer flush 的设置见这里。通常,默认值就很合适。
考虑自适应异步插入在 agent 数量较少、吞吐量较低但端到端延迟要求严格的场景下,自适应异步插入可能会有帮助。一般来说,它们并不适用于 ClickHouse 常见的高吞吐量可观测性用例。
async_insert_deduplicate。
有关此功能配置的完整说明,请参见这里;深入说明请参见这里。
部署架构
仅使用 agent
- 连接扩展 - 每个 agent 都会与 ClickHouse 建立一个连接。虽然 ClickHouse 能维持数百个 (甚至数千个) 并发 insert 连接,但这最终会成为限制因素,并降低 insert 效率——也就是说,ClickHouse 需要消耗更多资源来维持这些连接。使用 gateway 可以尽量减少连接数量,并提高 insert 效率。
- 边缘侧处理 - 在这种架构中,任何转换或事件处理都必须在边缘侧或 ClickHouse 中完成。这不仅限制较多,还可能意味着需要复杂的 ClickHouse materialized view,或者将大量计算下推到边缘侧——而那里往往资源紧张,也可能影响关键服务。
- 小批次和延迟 - 各个 agent collector 单独收集到的事件可能很少。这通常意味着需要将其配置为按固定时间间隔 flush,以满足交付 SLA。这可能导致 collector 向 ClickHouse 发送较小的批次。虽然这是一个缺点,但可以通过异步插入缓解——请参阅优化插入。