2. Loki 核心架构回顾:Ingester、Querier、Distributor、Compactor 组件职责与数据流
在深入查询优化与压测水位监控之前,必须重新梳理 Loki 的读写路径与核心组件职责。Loki 采用 微服务架构,各组件可独立扩展。理解每个组件的边界与数据流瓶颈,是后续制定压测口径与优化查询效率的基础。
2.1 整体架构与数据流总览
Loki 的写入路径与读取路径分离,写入路径以 Ingester 为中心,读取路径以 Querier 为中心。下图展示了核心组件之间的数据流转关系:
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 写入路径数据流详解
- Agent 采集:Promtail、Fluentd、OTEL Collector 等 Agent 从日志文件或标准输出采集日志,附加标签(如
job、instance、namespace),通过 HTTP/gRPC 推送到 Distributor。 - Distributor 验证与分发:Distributor 检查日志流标签是否合法、时间戳是否在可接受范围内(默认 ±30 分钟)。通过一致性哈希算法计算日志流的哈希值,将数据转发到对应的 Ingester 副本。
- Ingester 构建 Chunk:Ingester 将接收到的日志流追加到内存中的 Chunk 结构。Chunk 按标签组合与时间范围组织,默认每 512 KB 或每 30 分钟刷写一次。Ingester 同时写入预写日志(WAL)以保证数据不丢失。
- 刷写到对象存储:Ingester 将完成的 Chunk 压缩(gzip/snappy)后上传到对象存储(S3、GCS、MinIO 等),并更新索引记录。上传成功后,Ingester 释放内存。
压测水位口径提示:写入路径的瓶颈通常出现在 Distributor 的 gRPC 连接数 和 Ingester 的内存上限。压测时应重点监控 Ingester 的
loki_ingester_memory_chunks 和 loki_ingester_chunk_utilization 指标。
2.4 读取路径数据流详解
- 查询请求到达:用户通过 Grafana 或 HTTP API 发送 LogQL 查询请求。请求首先到达 Query Frontend(如果部署了)。
- Query Frontend 分片与缓存:Query Frontend 将大查询按时间范围或标签拆分为多个子查询,检查结果缓存。如果缓存命中,直接返回;否则将子查询分发到 Querier。
- Querier 并行查询:Querier 接收子查询后,同时向所有 Ingester 查询内存中的热数据(通过 gRPC),并向对象存储查询历史 Chunk。Querier 合并两部分结果,去重后返回。
- 结果合并与返回:Query Frontend 收集所有子查询结果,按时间排序合并,最终返回给客户端。
常见性能陷阱:未部署 Query Frontend 时,每个 Querier 直接接收全量查询,容易导致 Ingester 内存查询压力过大。建议生产环境始终启用 Query Frontend 并配置结果缓存。
2.5 Compactor 的异步优化角色
Compactor 不参与实时读写路径,但其工作直接影响查询效率:
- Chunk 合并:将对象存储中的小 Chunk(小于目标大小)合并为大 Chunk,减少对象数量,降低 Querier 读取时的 API 调用次数。
- 索引构建:生成或更新 TSDB 索引,加速标签过滤查询。
- 数据保留与清理:根据配置的保留期删除过期数据,释放存储空间。
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 资源或调整合并周期。