2. Loki 架构回顾:读写路径、Ingester、Querier、Distributor 组件交互

在深入告警路由与性能调优之前,必须重新理解 Loki 的核心架构。Loki 的读写分离设计直接决定了告警规则的执行位置、查询延迟的瓶颈以及路由参数的生效范围。本节将拆解三个关键组件——Distributor、Ingester、Querier——以及它们之间的数据流与交互契约。

2.1 读写路径总览

Loki 采用 “写时复制索引、读时计算” 的哲学。写入路径追求低延迟、高吞吐;读取路径则依赖 Querier 对存储层和 Ingester 内存中的数据进行实时合并。下图展示了完整的读写分离架构:

Loki 读写路径与组件交互 Loki 读写路径与组件交互 写入路径 读取路径 Promtail / Agent Distributor Ingester (写) 对象存储 Query Frontend Querier Ingester (读) 对象存储 内存数据共享 Ingester 是读写路径的交汇点:写入时负责接收并缓存,读取时直接提供未刷盘的数据

2.2 核心组件职责与交互

组件 职责 关键交互对象 告警路由影响点
Distributor 接收日志流,验证合法性,基于哈希环将数据分片转发至 Ingester Ingester、Consul/Etcd(哈希环) 无直接路由影响,但决定了数据分布均匀性
Ingester 缓存写入数据,定期刷盘至对象存储;同时响应 Querier 的实时数据查询 Distributor、Querier、对象存储 内存中未刷盘数据影响告警的实时性
Querier 执行 LogQL 查询,合并来自 Ingester(实时)和对象存储(历史)的结果 Ingester、对象存储、Query Frontend 告警规则在此执行,路由参数(如 parallelism)直接影响查询性能

2.3 Distributor:写入入口与数据分片

Distributor 是写入路径的第一站。它不存储任何数据,只负责转发。其核心流程如下:

  1. 接收与验证:接收来自 Promtail、Docker 插件等 Agent 的日志流,检查标签合法性、时间戳范围。
  2. 哈希分片:根据标签组合(如 {job="nginx", env="prod"})计算哈希值,通过一致性哈希环确定目标 Ingester 副本。
  3. 复制转发:默认复制因子为 3,将日志流同时发送到多个 Ingester 以保证高可用。
告警路由关联点: Distributor 的 distributor.ring.replication-factor 参数控制副本数。如果副本数过高,Ingester 内存压力增大,可能导致告警查询时 Ingester 响应变慢。建议保持默认值 3,仅在极端高可用场景下调整为 5。

2.4 Ingester:读写交汇的内存枢纽

Ingester 是 Loki 中唯一同时参与读写路径的组件。它维护着最近写入的日志数据在内存中,并定期将数据压缩、索引后刷入对象存储。

2.4.1 写入阶段

  • 接收 Distributor 推送的日志流,追加到内存中的 chunk(数据块)。
  • 每个 chunk 默认最大 1.5MB 或 30 分钟,任一条件满足即触发刷盘。
  • 刷盘后,chunk 变为只读,Ingester 释放内存。

2.4.2 读取阶段

  • Querier 发起查询时,Ingester 返回内存中尚未刷盘的实时数据。
  • 对于已刷盘的数据,Querier 直接从对象存储读取,Ingester 不再参与。
性能陷阱: 如果告警规则的查询时间范围覆盖了 Ingester 内存中的大量未刷盘数据(例如最近 5 分钟的实时告警),Querier 必须等待所有 Ingester 返回结果。当 Ingester 数量超过 50 个时,建议启用 query-frontend 的 querier.ingester-query-probing 来减少等待超时。

2.5 Querier:告警规则的执行引擎

Querier 是告警规则的实际执行者。当 ruler 组件触发告警评估时,它会向 Querier 发送 LogQL 查询请求。Querier 的工作流程如下:

  1. 并行查询:将查询拆分为多个子查询,同时发送到所有 Ingester 和对象存储。
  2. 结果合并:等待所有子查询返回,按时间戳排序、去重、合并。
  3. 返回结果:将最终结果返回给 Ruler 进行告警规则匹配。

2.5.1 关键性能参数

参数 默认值 告警路由调优建议
querier.max-concurrent 10 告警规则较多时(>50 条),建议提升至 20-30,避免排队
querier.ingester-query-probing false Ingester 数量 >30 时开启,减少无效等待
querier.query-parallelism 14 告警查询涉及多标签组合时,适当降低至 8-10 防止 Ingester 过载
querier.split-queries-by-interval 1h 告警查询时间范围 >1h 时自动拆分,建议保持默认

2.6 组件交互时序:一次告警评估的完整路径

为了更直观地理解组件间的协作,下图展示了一次告警规则评估的完整时序:

告警评估时序:组件交互 告警评估时序:组件交互 Ruler Querier Ingester 对象存储 1. 触发评估 2. 并行查询 Ingester 3. 返回实时数据 4. 并行查询存储 5. 返回历史数据 6. 合并结果返回 告警评估必须等待 Ingester 和对象存储的查询全部返回,因此 Ingester 的响应速度直接影响告警延迟

2.7 组件交互对告警路由的启示

核心结论:

  • Ingester 是延迟瓶颈:告警规则查询最近数据时,必须等待所有 Ingester 返回。如果 Ingester 内存中积压了大量未刷盘数据,查询延迟会显著增加。
  • Querier 的并行度需要谨慎设置:querier.max-concurrent 和 querier.query-parallelism 决定了 Querier 对 Ingester 的并发请求数。设置过高可能导致 Ingester CPU 飙升,设置过低则告警评估排队。
  • Distributor 的哈希环影响数据局部性:告警规则如果按特定标签(如 namespace)查询,数据分布越均匀,Querier 的合并开销越小。