2. Loki核心架构回顾
在深入黄金指标与标签治理之前,必须重新理解 Loki 的读写路径与核心组件职责。Loki 的设计哲学与 Prometheus 高度一致:不索引日志内容,只索引标签(Label)。这一决策决定了其架构中各组件的分工与数据流方向。
2.1 整体架构概览
Loki 采用微服务架构,核心组件包括 Distributor、Ingester、Querier 以及可选的 Query Frontend。日志采集端通常由 Promtail、Fluentd、Fluent Bit 或 Grafana Agent 完成。下图展示了日志从采集到查询的完整路径:
架构要点: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 是有状态组件,负责接收并临时存储日志数据。其工作流程:
- 接收数据:从 Distributor 接收日志流,按标签组合(Stream)组织数据。
- 内存缓存:将日志数据压缩后存储在内存中,形成 Chunk(默认 1MB 或 1 小时触发 flush)。
- WAL 保护:在写入内存前先写入 Write-Ahead Log,防止进程崩溃导致数据丢失。
- 持久化:当 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 | 聚合返回 | 将多个子查询结果聚合后返回给客户端 |
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 内存消耗和查询效率。
- 标签治理必须从采集端开始,贯穿整个数据生命周期。