2. Loki核心架构回顾

在深入黄金指标与标签治理之前,必须重新理解 Loki 的读写路径与核心组件职责。Loki 的设计哲学与 Prometheus 高度一致:不索引日志内容,只索引标签(Label)。这一决策决定了其架构中各组件的分工与数据流方向。

2.1 整体架构概览

Loki 采用微服务架构,核心组件包括 Distributor、Ingester、Querier 以及可选的 Query Frontend。日志采集端通常由 Promtail、Fluentd、Fluent Bit 或 Grafana Agent 完成。下图展示了日志从采集到查询的完整路径:

Loki 核心架构与数据流 Loki 核心架构与数据流 日志采集端 Promtail / Fluent Bit / Grafana Agent Distributor(分发器) 验证数据 / 哈希分片 / 批量转发 Ingester(写入器) 内存缓存 / 压缩 / 持久化到对象存储(S3/GCS/MinIO) 支持 Write-Ahead Log (WAL) 保证数据可靠性 对象存储(长期持久化) Chunks + Index Querier(查询器) 合并 Ingester 内存数据 + 存储数据 Query Frontend 缓存 / 分片 / 并行 关键设计原则 写路径:采集 → Distributor → Ingester → 存储 读路径:Query Frontend → Querier → Ingester + 存储
架构要点:Loki 将写路径与读路径分离。Distributor 负责写入入口的验证与分发,Ingester 负责内存缓存与持久化,Querier 负责从 Ingester 和对象存储中合并查询结果。这种设计使得 Loki 可以独立扩缩容写入与查询能力。

2.2 核心组件详解

2.2.1 日志采集端(Agent)

采集端负责从目标节点抓取日志文件或接收日志流,添加标签后推送到 Loki。常见采集端对比:

采集端 语言 标签发现 适用场景
Promtail Go Kubernetes 自动发现、文件静态配置 K8s 原生、与 Loki 集成最佳
Fluent Bit C 通过配置或 Lua 脚本 轻量级、边缘设备、多输出
Grafana Agent Go 兼容 Promtail 配置 统一指标、日志、Trace 采集
Fluentd Ruby 通过插件 已有 Fluentd 生态、复杂路由
标签治理起点:采集端是标签的第一生产者。一旦在采集端定义了错误的标签(如将 pod_name 设为动态标签),后续所有组件都会继承该问题。标签治理必须从采集端开始。

2.2.2 Distributor(分发器)

Distributor 是写入路径的入口,无状态,可水平扩展。其核心职责:

  • 数据验证:检查日志流的时间戳是否在可接受范围内(默认 ±30 分钟),标签格式是否合法。
  • 哈希分片:根据标签组合计算哈希值,通过一致性哈希环决定将数据发送到哪个 Ingester 实例。
  • 批量转发:将验证后的日志流批量发送到对应的 Ingester,减少网络开销。

2.2.3 Ingester(写入器)

Ingester 是有状态组件,负责接收并临时存储日志数据。其工作流程:

  1. 接收数据:从 Distributor 接收日志流,按标签组合(Stream)组织数据。
  2. 内存缓存:将日志数据压缩后存储在内存中,形成 Chunk(默认 1MB 或 1 小时触发 flush)。
  3. WAL 保护:在写入内存前先写入 Write-Ahead Log,防止进程崩溃导致数据丢失。
  4. 持久化:当 Chunk 达到阈值或超时,将 Chunk 刷新到对象存储(S3、GCS、MinIO 等),并更新 Index。
关键指标:Ingester 的内存使用量直接受 活跃 Stream 数量 影响。每个唯一的标签组合对应一个 Stream。如果标签基数过高(如将 request_id 作为标签),Ingester 内存会急剧膨胀,导致 OOM 或性能下降。这是标签治理的核心关注点。

2.2.4 Querier(查询器)

Querier 是无状态组件,负责执行 LogQL 查询。其查询策略:

  • 近期数据:直接从 Ingester 的内存中查询尚未持久化的数据,保证实时性。
  • 历史数据:从对象存储中读取已持久化的 Chunk 和 Index,进行解压与过滤。
  • 结果合并:将来自多个 Ingester 和存储的数据合并后返回给客户端或 Query Frontend。

2.3 数据流详解:写路径与读路径

理解数据流是诊断性能问题和设计标签策略的基础。下面分别拆解写路径与读路径的完整步骤。

2.3.1 写路径(日志采集 → 存储)

步骤 组件 动作 关键细节
1 采集端 读取日志文件/接收日志流 添加标签(如 job、instance、namespace)
2 采集端 通过 HTTP/gRPC 推送日志 推送到 Distributor 的 /loki/api/v1/push 端点
3 Distributor 验证数据合法性 检查时间戳、标签格式、日志大小
4 Distributor 哈希分片 根据标签组合计算哈希,选择目标 Ingester
5 Ingester 写入 WAL 先写磁盘 WAL,确保崩溃可恢复
6 Ingester 内存缓存 压缩后存入内存 Chunk
7 Ingester 刷新到对象存储 Chunk 达到阈值或超时后异步持久化

2.3.2 读路径(查询请求 → 结果返回)

步骤 组件 动作 关键细节
1 Query Frontend 接收 LogQL 查询 可选组件,负责缓存、分片、并行调度
2 Query Frontend 拆分查询 按时间范围拆分为多个子查询
3 Querier 查询 Ingester 获取内存中未持久化的近期数据
4 Querier 查询对象存储 根据 Index 定位 Chunk,读取并解压
5 Querier 合并结果 将 Ingester 数据与存储数据合并去重
6 Query Frontend 聚合返回 将多个子查询结果聚合后返回给客户端
写路径与读路径对比 写路径 vs 读路径 写路径(Push) 采集端 Distributor Ingester 对象存储 读路径(Query) Query Frontend Querier Ingester(内存) 对象存储 返回合并结果

2.4 组件协作与标签的关系

标签(Label)在 Loki 架构中扮演着数据路由与索引的双重角色:

  • Distributor 路由:根据标签组合的哈希值决定数据流向哪个 Ingester。标签组合越多,哈希分布越分散,但也会导致 Ingester 内存中维护的 Stream 数量膨胀。
  • Ingester 内存管理:每个唯一的标签组合对应一个 Stream,每个 Stream 在 Ingester 中独立维护 Chunk。标签基数过高会直接导致 Ingester 内存飙升。
  • Querier 查询效率:查询时通过标签匹配快速定位目标 Stream,避免全量扫描。合理的标签设计可以大幅降低查询延迟。
核心结论:标签是 Loki 架构中唯一的索引机制。标签治理不仅影响查询性能,更直接决定 Ingester 的稳定性与资源消耗。后续章节将围绕如何设计、优化和监控标签展开。

2.5 本章小结

  • Loki 架构由 采集端 → Distributor → Ingester → 对象存储 构成写路径,由 Query Frontend → Querier → Ingester + 存储 构成读路径。
  • Distributor 负责验证与分片,Ingester 负责内存缓存与持久化,Querier 负责合并查询。
  • 标签是数据路由与索引的核心,标签基数直接决定 Ingester 内存消耗和查询效率。
  • 标签治理必须从采集端开始,贯穿整个数据生命周期。