2. Loki 核心架构回顾:Ingester、Querier、Distributor、Compactor 组件职责与数据流

在深入查询优化与压测水位监控之前,必须重新梳理 Loki 的读写路径与核心组件职责。Loki 采用 微服务架构,各组件可独立扩展。理解每个组件的边界与数据流瓶颈,是后续制定压测口径与优化查询效率的基础。

2.1 整体架构与数据流总览

Loki 的写入路径与读取路径分离,写入路径以 Ingester 为中心,读取路径以 Querier 为中心。下图展示了核心组件之间的数据流转关系:

Loki 核心架构与数据流 Loki 核心架构与数据流 写入路径 Agent (Promtail / OTEL) Distributor 验证 & 分发 Ingester 内存 Chunk 写入对象存储 对象存储 S3 / GCS / MinIO 读取路径 Query Frontend 分片 / 缓存 Querier 查询 Ingester + 对象存储 读取内存数据 读取历史 Chunk Compactor 合并 / 去重 / 索引 读取 & 写入合并后数据 说明:写入路径为实线箭头,读取路径为虚线箭头。Compactor 异步操作对象存储,不参与实时读写。

2.2 核心组件职责详解

组件 职责 关键特性 压测关注点
Distributor 接收 Agent 推送的日志流,验证数据合法性(标签格式、时间戳),通过一致性哈希将日志流分发到对应的 Ingester。 无状态,可水平扩展;负责限流与准入控制。 QPS 上限、网络带宽、gRPC 连接数。
Ingester 接收 Distributor 分发的日志流,在内存中构建 Chunk,定期将 Chunk 刷写到对象存储。维护最近一段时间的热数据。 有状态,每个 Ingester 负责特定哈希范围的流;Chunk 在内存中可被 Querier 实时查询。 内存占用、Chunk 刷写频率、磁盘 I/O(WAL)。
Querier 处理 LogQL 查询请求:从 Ingester 读取内存中的热数据,从对象存储读取历史 Chunk,合并结果后返回。 无状态,可水平扩展;支持并行查询与分片。 查询并发数、对象存储读取延迟、Ingester 内存查询延迟。
Query Frontend 可选组件,位于 Querier 之前,负责查询分片、缓存查询结果、合并子查询结果。 无状态;大幅降低 Querier 负载,提升大查询性能。 缓存命中率、分片策略、超时控制。
Compactor 异步合并对象存储中的小 Chunk 为大 Chunk,构建并更新索引(TSDB 或 BoltDB),清理过期数据。 无状态,周期性运行;减少对象存储碎片,提升查询效率。 合并周期、CPU 与内存消耗、对象存储 API 调用频率。

2.3 写入路径数据流详解

  1. Agent 采集:Promtail、Fluentd、OTEL Collector 等 Agent 从日志文件或标准输出采集日志,附加标签(如 job、instance、namespace),通过 HTTP/gRPC 推送到 Distributor。
  2. Distributor 验证与分发:Distributor 检查日志流标签是否合法、时间戳是否在可接受范围内(默认 ±30 分钟)。通过一致性哈希算法计算日志流的哈希值,将数据转发到对应的 Ingester 副本。
  3. Ingester 构建 Chunk:Ingester 将接收到的日志流追加到内存中的 Chunk 结构。Chunk 按标签组合与时间范围组织,默认每 512 KB 或每 30 分钟刷写一次。Ingester 同时写入预写日志(WAL)以保证数据不丢失。
  4. 刷写到对象存储:Ingester 将完成的 Chunk 压缩(gzip/snappy)后上传到对象存储(S3、GCS、MinIO 等),并更新索引记录。上传成功后,Ingester 释放内存。
压测水位口径提示:写入路径的瓶颈通常出现在 Distributor 的 gRPC 连接数 和 Ingester 的内存上限。压测时应重点监控 Ingester 的 loki_ingester_memory_chunks 和 loki_ingester_chunk_utilization 指标。

2.4 读取路径数据流详解

  1. 查询请求到达:用户通过 Grafana 或 HTTP API 发送 LogQL 查询请求。请求首先到达 Query Frontend(如果部署了)。
  2. Query Frontend 分片与缓存:Query Frontend 将大查询按时间范围或标签拆分为多个子查询,检查结果缓存。如果缓存命中,直接返回;否则将子查询分发到 Querier。
  3. Querier 并行查询:Querier 接收子查询后,同时向所有 Ingester 查询内存中的热数据(通过 gRPC),并向对象存储查询历史 Chunk。Querier 合并两部分结果,去重后返回。
  4. 结果合并与返回:Query Frontend 收集所有子查询结果,按时间排序合并,最终返回给客户端。
常见性能陷阱:未部署 Query Frontend 时,每个 Querier 直接接收全量查询,容易导致 Ingester 内存查询压力过大。建议生产环境始终启用 Query Frontend 并配置结果缓存。

2.5 Compactor 的异步优化角色

Compactor 不参与实时读写路径,但其工作直接影响查询效率:

  • Chunk 合并:将对象存储中的小 Chunk(小于目标大小)合并为大 Chunk,减少对象数量,降低 Querier 读取时的 API 调用次数。
  • 索引构建:生成或更新 TSDB 索引,加速标签过滤查询。
  • 数据保留与清理:根据配置的保留期删除过期数据,释放存储空间。
Compactor 工作流程 Compactor 异步合并流程 对象存储 小 Chunk 集合 Compactor 读取 & 合并 对象存储 大 Chunk + 索引 Compactor 优化效果 • 减少对象存储中的小文件数量,降低 Querier 读取时的 LIST/GET 请求次数 • 构建 TSDB 索引,将标签过滤查询从 O(n) 优化为 O(log n) • 清理过期数据,释放存储空间,降低存储成本 • 压测时需关注 Compactor 的 CPU 使用率与对象存储 API 限流

2.6 组件间通信协议与端口

组件对 协议 默认端口 说明
Agent → Distributor HTTP / gRPC 3100 (HTTP) / 9095 (gRPC) 日志推送接口
Distributor → Ingester gRPC 9095 内部转发,使用一致性哈希
Querier → Ingester gRPC 9095 读取内存热数据
Querier → 对象存储 HTTP / S3 API 443 / 9000 读取历史 Chunk
Compactor → 对象存储 HTTP / S3 API 443 / 9000 读写 Chunk 与索引

2.7 压测水位口径的关键启示

基于架构的压测水位口径建议:

  • 写入水位:以 Ingester 的内存使用率(loki_ingester_memory_chunks)和 Distributor 的 gRPC 错误率(loki_distributor_ingester_append_failures_total)为核心指标。当 Ingester 内存使用超过 70% 时,应触发扩容或限流。
  • 查询水位:以 Querier 的查询延迟 P99 和对象存储的 GET 请求延迟为核心指标。当 P99 延迟超过 5 秒或对象存储 GET 错误率超过 1% 时,应优化查询或增加 Querier 副本。
  • Compactor 水位:以 Compactor 的合并队列深度和对象存储的 LIST 请求频率为参考。合并队列积压超过 1000 个 Chunk 时,应增加 Compactor 资源或调整合并周期。