2. Loki 架构回顾:梳理读写路径

在分析版本漂移对数据的影响之前,必须重新建立 Loki 核心组件的职责边界与数据流转模型。Loki 采用“微服务 + 共享存储”架构,各组件可独立扩展,但版本不一致时,组件间的 gRPC 协议、数据编码、索引格式可能产生隐性不兼容。本节聚焦 Distributor、Ingester、Querier、Query Frontend 四个关键角色,梳理其读写路径。

2.1 整体架构分层

Loki 的架构可抽象为三层:写入层、存储层、查询层。写入层负责接收、验证、分发日志流;存储层将日志数据持久化到对象存储(如 S3、GCS)并管理索引;查询层负责解析 PromQL/LogQL、并行检索、结果聚合。

关键原则:写入路径与查询路径在 Ingester 处交汇。Ingester 既承担实时写入缓冲,也承担近期数据的查询服务。版本漂移最容易在此处引发数据不一致或查询超时。
Loki 整体架构与读写路径 Loki 整体架构与读写路径 写入层 Distributor 接收 / 验证 / 哈希分发 Ingester 内存缓冲 / 构建 Chunk 对象存储 Chunk / 索引 查询层 Query Frontend 拆分 / 缓存 / 合并 Querier 执行 LogQL / 拉取 Chunk 对象存储 Chunk / 索引 近期数据查询 说明: 写入路径:Distributor → Ingester → 对象存储(异步 flush) 查询路径:Query Frontend → Querier → 对象存储 + Ingester(近期数据)

2.2 写入路径详解

日志写入路径是 Push API → Distributor → Ingester → 对象存储 的流水线。每个环节都有明确的职责和版本敏感点。

组件 职责 版本漂移风险点
Distributor 接收 gRPC Push 请求;验证日志流标签合法性;基于一致性哈希将日志流分发到对应 Ingester 副本 gRPC 协议字段变更;哈希环算法差异;标签验证规则收紧
Ingester 将日志流追加到内存中的 Chunk;定期将 Chunk 刷写到对象存储;维护近期数据的索引 Chunk 编码格式变化;索引 schema 升级;刷写时机/策略差异
对象存储 持久化 Chunk 文件和索引表(Boltdb-shipper / TSDB) 索引格式不兼容;Chunk 元数据字段缺失
实践建议:升级时优先保证 Distributor 与 Ingester 版本匹配。如果 Ingester 升级后 Chunk 编码格式改变,旧版本 Querier 可能无法解析新 Chunk,导致查询失败。

2.3 查询路径详解

查询路径分为 实时查询 和 历史查询 两条子路径,最终在 Querier 中合并结果。

  • Query Frontend:接收 LogQL 请求;将大查询拆分为多个子查询(按时间范围分片);缓存查询结果;合并子查询结果返回给客户端。
  • Querier:从对象存储拉取历史 Chunk 和索引;从 Ingester 拉取仍在内存中的近期数据;执行 LogQL 过滤、聚合、标签匹配;返回结果集。

版本漂移在查询路径中表现为:

  • Query Frontend 拆分策略变化导致子查询超时或重复。
  • Querier 与 Ingester 之间的 gRPC QueryStream 接口字段不匹配。
  • Querier 无法解析新版索引格式(如从 Boltdb-shipper 切换到 TSDB 索引)。
Loki 查询路径时序 查询路径时序(实时 + 历史) 客户端 Query Frontend Querier 历史查询 对象存储 实时查询 Ingester 合并结果返回 版本漂移影响:Querier 与 Ingester 的 gRPC 接口版本不匹配 → 实时查询失败;Querier 无法解析新版索引 → 历史查询返回空结果。

2.4 组件间通信协议

Loki 组件间通信主要依赖 gRPC 和 HTTP 两种协议。版本升级时,gRPC 的 .proto 文件变更可能导致序列化/反序列化失败。

通信对 协议 主要接口 / 端点 版本敏感字段
Distributor → Ingester gRPC Push() Stream 标签结构、ChunkRef 格式
Querier → Ingester gRPC QueryStream() Series 编码、Chunk 数据块格式
Querier → 对象存储 HTTP / gRPC 对象存储 SDK 索引文件路径、Chunk 元数据 JSON 字段
Query Frontend → Querier gRPC Query() 查询请求参数、分片策略标识
版本漂移典型场景:假设集群将 Ingester 从 v2.8 升级到 v2.9,但 Querier 仍为 v2.8。v2.9 Ingester 在 QueryStream 响应中新增了 ChunkEncoding 字段,v2.8 Querier 无法识别该字段,导致 gRPC 反序列化错误,实时查询全部超时。这是升级过程中最常见的中断原因之一。

2.5 读写路径版本依赖总结

下表汇总了各组件在读写路径中的版本依赖关系,帮助你在升级规划中识别关键约束。

路径 依赖链 版本兼容要求 升级建议
写入 Distributor → Ingester 严格兼容(gRPC Push 接口) 同时升级,或先升级 Ingester 再升级 Distributor
写入 Ingester → 对象存储 向前兼容(Chunk 格式) Ingester 升级后,旧 Querier 需能读取新 Chunk
查询 Query Frontend → Querier 向后兼容(查询参数) 优先升级 Querier,再升级 Frontend
查询 Querier → Ingester 严格兼容(QueryStream 接口) 必须保持版本一致,或使用兼容版本组合
查询 Querier → 对象存储 向前兼容(索引格式) 确认索引 schema 版本,必要时重建索引