2. Loki 架构基础:整体架构、日志存储模型与 Prometheus 异同

在深入部署 Agent 之前,必须理解 Loki 的架构设计。Loki 的架构灵感来源于 Prometheus,但针对日志数据的特点做了根本性调整。本章将拆解其核心组件、存储模型,并与 Prometheus 进行对比,帮助你建立清晰的架构认知。

2.1 Loki 整体架构:三大核心组件

Loki 采用微服务架构,其读写路径分离,主要由三个组件构成:Distributor(分发器)、Ingester(写入器)和 Querier(查询器)。它们协同工作,实现了日志的高效写入与查询。

Loki 整体架构图 Loki 整体架构(读写路径分离) 日志 Agent Distributor Ingester 对象存储 Querier Grafana / CLI 写入路径:Agent → Distributor → Ingester → 对象存储 | 查询路径:Grafana → Querier → Ingester + 对象存储

2.1.1 Distributor(分发器)

  • 职责:接收来自 Agent(如 Promtail)的日志流,进行数据校验(如时间戳合法性、标签格式)和哈希分片。
  • 关键机制:使用一致性哈希环将日志流分发到对应的 Ingester 实例,确保同一日志流的日志被发送到同一个 Ingester。
  • 高可用:Distributor 是无状态的,可以水平扩展,通常部署多个副本。

2.1.2 Ingester(写入器)

  • 职责:负责将日志数据写入内存并打包成 Chunk,最终将 Chunk 刷写到对象存储(如 S3、GCS、MinIO)。
  • 状态管理:Ingester 是有状态的,它维护着当前正在写入的“热 Chunk”。
  • 数据可靠性:在 Chunk 刷写到对象存储之前,数据仅存在于 Ingester 内存中。如果 Ingester 崩溃,未刷写的数据会丢失。因此,生产环境通常启用 WAL(Write Ahead Log) 或部署多个副本。

2.1.3 Querier(查询器)

  • 职责:处理来自 Grafana 或 LogCLI 的 LogQL 查询请求。
  • 查询路径:Querier 会同时查询所有 Ingester 的内存数据(热数据)和对象存储中的历史 Chunk(冷数据),合并结果后返回。
  • 无状态:Querier 也是无状态的,可以轻松扩展以应对高并发查询。
架构要点: 写入路径是“推”模式,查询路径是“拉”模式。这种读写分离设计使得 Loki 能够独立扩展写入和查询能力,非常适合日志这种写入量远大于查询量的场景。

2.2 日志存储模型:Chunk 与 Index

Loki 的存储模型是其与 Elasticsearch 等传统日志系统最大的区别。它不建立全文索引,而是将日志数据组织成 Chunk,并仅对日志的标签(Labels)建立索引。

Loki 存储模型:Chunk 与 Index Loki 存储模型:Chunk + Index Index(索引) 存储内容:标签键值对 + 时间范围 示例:{job="nginx", env="prod"} → 指向 Chunk 的指针 存储后端:BoltDB / TSDB 查询方式:标签匹配 Chunk(数据块) 存储内容:原始日志行(压缩后) 大小:默认 ~1.5MB(可配置) 格式:gzip / snappy / lz4 压缩 存储后端:对象存储(S3/GCS) 查询方式:扫描整个 Chunk 指针 核心思想:只索引标签,不索引日志内容。查询时通过标签找到 Chunk,再扫描 Chunk 内的日志行。

2.2.1 Chunk(数据块)

  • 定义:Chunk 是 Loki 中日志数据的最小存储单元。它包含一组连续的、属于同一标签集的日志行。
  • 生命周期:Ingester 在内存中构建 Chunk,当 Chunk 达到一定大小(默认 1.5MB)或时间阈值(默认 1 小时)时,将其刷写到对象存储。
  • 压缩:Chunk 在刷写前会进行压缩,常用压缩算法包括 gzip、snappy 和 lz4,以降低存储成本。

2.2.2 Index(索引)

  • 定义:Index 存储的是标签(Labels)到 Chunk 的映射关系。它告诉 Loki:“对于标签 {job="nginx"},在时间范围 [T1, T2] 内,有哪些 Chunk 包含了相关日志”。
  • 存储后端:早期版本使用 BoltDB,新版本(2.8+)默认使用 TSDB(时间序列数据库)作为索引后端,性能更高。
  • 查询效率:由于只索引标签,Loki 的索引体积远小于 Elasticsearch 的全文索引,这是 Loki 存储成本低的关键原因。
重要提示: 不要将日志内容作为标签(如 request_id、user_email)。这会导致索引爆炸,使 Loki 性能急剧下降。标签应保持低基数(Low Cardinality),例如 job、instance、level。

2.3 Loki 与 Prometheus 的异同

Loki 的设计哲学与 Prometheus 高度一致,但针对日志数据的特点做了关键调整。理解它们的异同,有助于你更好地掌握 Loki 的使用方式。

对比维度 Prometheus Loki
数据模型 时间序列(指标名 + 标签 + 时间戳 + 数值) 日志流(标签 + 时间戳 + 日志行文本)
索引策略 对所有标签建立倒排索引 仅对标签建立索引,不索引日志内容
存储后端 本地磁盘(TSDB) 对象存储(S3/GCS/MinIO)
数据压缩 gorilla 压缩算法(针对浮点数优化) gzip/snappy/lz4(针对文本优化)
查询语言 PromQL(Prometheus Query Language) LogQL(Log Query Language,兼容 PromQL 语法)
数据推送 Pull 模式(主动拉取) Push 模式(Agent 主动推送)
典型用途 指标监控(CPU、内存、请求速率) 日志聚合与排查(错误日志、审计日志)

2.3.1 核心差异详解

  • 索引策略:Prometheus 对每个时间序列的所有标签建立索引,支持高效的标签过滤。Loki 只索引标签,日志内容不建索引,因此查询日志内容时必须进行全文扫描(类似于 grep)。
  • 存储成本:由于不索引日志内容,Loki 的存储成本通常比 Elasticsearch 低 5-10 倍,但查询速度在复杂全文搜索场景下会慢于 ES。
  • 数据推送方式:Prometheus 采用 Pull 模式,由 Prometheus Server 主动拉取目标数据。Loki 采用 Push 模式,由 Agent(如 Promtail)主动将日志推送到 Distributor。这使得 Loki 更适合动态环境(如 Kubernetes)。
架构启示: 如果你熟悉 Prometheus,可以将 Loki 理解为“日志版的 Prometheus”。它们共享标签体系,但 Loki 牺牲了全文索引能力,换取了极低的存储成本和运维复杂度。在日志量巨大的场景下,这种取舍是值得的。