Loki 基础架构:组件、存储模型与 Prometheus 对比
在搭建变更窗口服务目录之前,必须深入理解 Loki 的核心架构。Loki 的设计哲学与 Prometheus 一脉相承,但针对日志数据的特点做了根本性调整。本节将拆解 Loki 的三大核心组件、日志存储模型,并通过与 Prometheus 的对比,帮助你建立清晰的架构认知。
2.1 Loki 核心组件
Loki 采用微服务架构,主要包含三个关键组件:Distributor(分发器)、Ingester(写入器)和 Querier(查询器)。它们协同工作,实现日志的高效写入与查询。
架构要点: Loki 的组件可以独立扩展,这意味着你可以根据写入压力单独扩容 Ingester,根据查询压力单独扩容 Querier,实现资源精准投放。
2.1.1 Distributor(分发器)
- 职责:接收来自 Promtail、Fluentd 等客户端的日志推送,是写入链路的入口。
- 核心功能:
- 数据校验:验证日志流标签(Label)格式是否正确,时间戳是否在可接受范围内。
- 哈希分片:根据标签集计算哈希值,决定将日志流发送到哪个 Ingester 实例。
- 复制转发:默认将日志流复制到多个 Ingester(通常为 3 副本),保证高可用。
- 无状态设计:Distributor 不存储任何数据,可以水平扩展以应对高并发写入。
2.1.2 Ingester(写入器)
- 职责:负责日志数据的临时缓存、去重和持久化写入。
- 核心功能:
- 内存缓存:接收到的日志先写入内存中的 Chunk(数据块),提供低延迟写入。
- 数据去重:基于时间戳和内容对重复日志进行去重。
- Chunk 刷新:当 Chunk 达到大小上限(默认 1MB)或时间上限(默认 1 小时)时,将 Chunk 刷新到对象存储(如 S3、GCS、MinIO)。
- 有状态组件:Ingester 持有内存中的 Chunk 数据,重启或故障可能导致数据丢失,因此需要配置 WAL(Write-Ahead Log)或依赖副本机制。
2.1.3 Querier(查询器)
- 职责:处理 LogQL 查询请求,聚合来自 Ingester(实时数据)和对象存储(历史数据)的结果。
- 核心功能:
- 实时查询:直接访问 Ingester 的内存 Chunk,获取尚未持久化的最新日志。
- 历史查询:从对象存储中读取已持久化的 Chunk 数据。
- 结果合并:将实时与历史结果合并、排序后返回给客户端。
- 无状态设计:Querier 可以水平扩展,查询性能随实例数线性提升。
2.2 日志存储模型
Loki 的存储模型是其与 Elasticsearch 等传统日志系统最大的区别。它借鉴了 Prometheus 的 TSDB 设计思路,但针对日志的“只追加、不修改”特性做了优化。
核心思想: Loki 不进行日志内容的全文索引,而是只索引日志的元数据(标签),日志内容以压缩块形式存储。这大幅降低了存储成本和写入开销,但牺牲了全文搜索能力。
2.2.1 存储层级
| 层级 | 组件 | 存储内容 | 特点 |
|---|---|---|---|
| 内存层 | Ingester | 未刷新的 Chunk(热数据) | 低延迟写入,数据不持久 |
| 本地磁盘层 | Ingester | WAL 日志(Write-Ahead Log) | 故障恢复,防止内存数据丢失 |
| 对象存储层 | 对象存储(S3/GCS/MinIO) | 已刷新的 Chunk + 索引 | 持久化存储,低成本,无限扩展 |
2.2.2 Chunk 与索引结构
- Chunk(数据块):日志内容的最小存储单元。一个 Chunk 包含同一日志流(相同标签集)的连续日志行,经过压缩(gzip/lz4/snappy)后存储。默认大小约 1MB。
- 索引:Loki 使用倒排索引,只索引标签(如
job、namespace、pod)。索引存储在对象存储中,与 Chunk 分离。 - 数据流:日志行 → 按标签分组 → 追加到对应 Chunk → Chunk 满/超时 → 压缩并刷新到对象存储 → 更新索引。
2.3 与 Prometheus 的对比
Loki 的设计深受 Prometheus 影响,但两者面向的数据类型和查询场景截然不同。理解它们的异同,有助于你在可观测性体系中合理分工。
| 维度 | Prometheus | Loki |
|---|---|---|
| 数据类型 | 时序指标(Metrics) | 日志行(Logs) |
| 数据模型 | 指标名 + 标签 + 时间戳 + 数值 | 标签 + 时间戳 + 日志内容(字符串) |
| 索引策略 | 全量索引(标签 + 时间戳) | 仅索引标签,不索引日志内容 |
| 存储引擎 | TSDB(时序数据库) | Chunk 存储 + 对象存储 |
| 查询语言 | PromQL | LogQL(PromQL 的超集) |
| 写入方式 | Pull(主动抓取) | Push(主动推送) |
| 典型用途 | 告警、趋势分析、仪表盘 | 故障排查、审计、日志聚合 |
| 存储成本 | 较高(全量索引) | 较低(仅索引标签) |
| 全文搜索 | 不支持 | 支持(通过 LogQL 过滤,但非索引) |
选型陷阱: 不要试图用 Loki 替代 Prometheus 做实时告警。Loki 的查询延迟通常在秒级,而 Prometheus 的告警延迟在毫秒级。正确的做法是:Prometheus 负责“是否异常”,Loki 负责“为什么异常”。
2.3.1 协同工作模式
在实际的可观测性体系中,Prometheus 和 Loki 通常配合使用:
- Prometheus 发现异常: 通过 PromQL 告警规则检测到指标异常(如 CPU 飙升、错误率上升)。
- Loki 定位根因: 告警触发后,通过 LogQL 查询对应时间窗口的日志,快速定位错误堆栈或异常日志。
- Grafana 统一展示: 在 Grafana 仪表盘中同时嵌入 Prometheus 的指标面板和 Loki 的日志面板,实现“指标 → 日志”的关联分析。
变更窗口场景: 在变更窗口服务目录中,Prometheus 负责监控变更后的系统指标(如延迟、错误率),Loki 负责聚合变更期间的部署日志、应用日志和系统日志。当指标告警触发时,运维人员可以直接从告警跳转到 Loki 的日志查询,查看变更窗口内的完整日志上下文。