2. Loki 核心概念:架构、日志流、标签与日志条目详解

在深入标签与链路关联优化之前,必须牢固掌握 Loki 的四个核心抽象:架构设计、日志流(Log Stream)、标签(Label)与日志条目(Log Entry)。这些概念决定了日志如何被写入、索引、存储和查询,也是后续所有优化策略的基石。

2.1 Loki 整体架构设计

Loki 采用微服务架构,核心组件包括 Distributor、Ingester、Querier 和 Storage。与 Prometheus 类似,Loki 使用标签(Label)作为索引的唯一维度,日志内容本身不做全文索引,从而大幅降低存储成本。

架构要点:Loki 不解析日志内容,只根据标签建立索引。查询时通过标签匹配缩小范围,再扫描对应日志流中的条目。
Loki 核心架构与数据流 日志客户端 Distributor Ingester 对象存储 Querier 推送日志 转发 持久化 实时查询 历史查询 组件职责说明 • Distributor:接收日志,校验格式,基于标签哈希分片转发至 Ingester。 • Ingester:内存缓存日志流,定期将数据块(Chunk)压缩后写入对象存储。 • Querier:接收 LogQL 查询,从 Ingester(实时)和 Storage(历史)合并结果。 • Storage:对象存储(S3/GCS/MinIO),存储压缩后的 Chunk 和索引。

2.2 日志流(Log Stream)

日志流是 Loki 中最核心的数据组织单元。一组具有完全相同标签集合的日志条目构成一个日志流。每个日志流在 Ingester 中对应一个内存缓冲区,最终持久化为一个或多个 Chunk。

关键理解:标签组合决定日志流数量。标签基数越高,日志流越多,Ingester 内存开销越大。合理控制标签基数(通常建议 < 10 个标签)是性能优化的第一步。
概念 定义 示例
日志流 具有相同标签集合的日志序列 {job="api-server", env="prod"}
Chunk 日志流的压缩存储单元 单个 Chunk 可包含数百至数千条日志
流哈希 标签集合的哈希值,用于分片和路由 Distributor 根据哈希将日志转发至对应 Ingester

2.3 标签(Label)

标签是 Loki 的唯一索引维度。每个标签是一个键值对(Key-Value),用于标识日志的来源、环境、服务等元信息。Loki 只对标签建立索引,不对日志内容建立全文索引。

标签分类

  • 结构化标签:由日志采集器(Promtail、Alloy)或推送客户端自动附加,如 job、instance、__path__。
  • 自定义标签:用户根据业务需求手动添加,如 service、env、team。
  • 衍生标签(Derived Labels):通过 LogQL 的 label_format 或 Relabel 规则从日志内容中提取。

标签设计原则

原则 说明 反例
低基数 每个标签的取值数量应有限(通常 < 100) user_id(百万级取值)
高区分度 标签应能有效缩小查询范围 level=info(区分度低)
稳定性 标签值不应频繁变化 pod(每次重启变化)
可枚举 标签值应可预定义,而非动态生成 request_id(每次请求不同)
常见陷阱:将高基数字段(如 trace_id、user_id、ip)直接作为标签,会导致日志流数量爆炸,Ingester 内存耗尽,查询性能急剧下降。高基数字段应放在日志内容中,通过 LogQL 的 |~ 或 json 解析器过滤。

2.4 日志条目(Log Entry)

日志条目是 Loki 存储的最小数据单元,包含三个部分:时间戳、日志行内容和元数据(可选)。

字段 类型 说明
Timestamp 纳秒精度 Unix 时间戳 日志产生的时间,用于排序和范围查询
Line 字符串 日志的原始文本内容
Metadata(可选) 键值对列表 附加的结构化信息,不参与索引
// 日志条目示例(JSON 序列化)
{
  "stream": {
    "job": "api-server",
    "env": "prod",
    "service": "user-svc"
  },
  "values": [
    ["1710000000000000000", "2025-03-09T12:00:00Z [INFO] 用户登录成功 userId=12345"],
    ["1710000001000000000", "2025-03-09T12:00:01Z [ERROR] 数据库连接超时 trace_id=abc-123"]
  ]
}

2.5 三者的关系

日志流、标签和日志条目之间的关系可以用下图概括:

标签 → 日志流 → 日志条目 层级关系 标签集合 job="api-server" env="prod" service="user-svc" 唯一标识 日志流 流哈希: 0x7a3f Ingester 内存缓冲区 Chunk 存储单元 包含 日志条目列表 [0] ts=1710000000 "用户登录成功" [1] ts=1710000001 "数据库连接超时" [2] ts=1710000002 "缓存命中" 数据流总结 1. 客户端推送日志时,必须携带一组标签(Label Set)。 2. Distributor 根据标签集合的哈希值,将日志路由到对应的 Ingester。 3. Ingester 将相同标签集合的日志条目追加到同一个日志流中。 4. 查询时,Querier 通过标签匹配找到目标日志流,再扫描时间范围内的日志条目。

2.6 与 OpenTelemetry 的关联点

在后续章节中,我们将利用这些核心概念实现日志与链路关联:

  • 标签作为关联桥梁:将 trace_id、span_id 作为标签,使日志流与链路直接绑定。
  • 日志流隔离:按 service + env 划分日志流,避免跨服务日志混流。
  • 条目元数据:利用 Loki 2.x 的 structured_metadata 功能,将链路上下文附加到日志条目中,不增加标签基数。
优化方向:在标签设计中预留 trace_id 标签位,但仅在需要关联的场景下使用,避免全局高基数。更优方案是使用结构化元数据(Structured Metadata)存储 trace_id,既保留关联能力,又不增加日志流数量。