news 2026/9/10 11:59:37

Grafana Loki 架构完全解读:微服务组件、读写路径与索引/块存储格式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Grafana Loki 架构完全解读:微服务组件、读写路径与索引/块存储格式

Grafana Loki 架构完全解读:微服务组件、读写路径与索引/块存储格式

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

Loki("Like Prometheus, but for logs")采用微服务架构设计,以水平可扩展的分布式系统形态运行;同时它把全部组件编译进同一个二进制或 Docker 镜像,通过-target启动参数决定进程扮演的角色。本文以 Loki 架构官方文档 为主线,结合本仓库的 组件文档、部署模式文档、TSDB 存储文档 以及pkg/chunkenc/memchunk.go等源码,系统讲解 Loki 的总体架构、存储与数据格式、写入路径、读取路径和多租户机制,帮助你从"能跑"进阶到"真正理解 Loki 为什么这么设计"。

一、总体架构:单二进制承载的微服务

Loki 是一个由众多微服务组成的分布式系统,但它采用了独特的构建模型:所有微服务都存在于同一个二进制中。无论你是用 Docker 镜像还是直接编译的可执行文件启动 Loki,都会发现可执行文件是同一份,区别只在于启动时传入的-target命令行标志:

  • -target=all:单进程内同时运行全部组件(monolithic / 单二进制模式);
  • -target=read-target=write-target=backend:把组件逻辑分组为读、写、后端三部分(simple scalable deployment,简单可扩展部署,已被 HA Monolithic 模式取代);
  • -target=<具体组件名>:只运行某一个组件(microservices 微服务模式)。

这种"一份二进制、多角色启动"的设计带来两个关键能力:

  1. 快速上手:以单二进制模式启动,全部组件在一个进程内同时运行,几分钟即可完成本地体验;
  2. 平滑演进:Loki 将"存储的数据"与"摄取/查询数据的软件"解耦,因此当你的需求变化时,可以几乎不修改配置(或仅做极小改动)就把集群切换到另一种部署模式。具体模式对比与切换要求见 部署模式,各组件职责详见 组件说明。

二、存储架构:单一对象存储后端

Loki 将所有数据存放在同一个对象存储后端中,例如:

  • Amazon Simple Storage Service(S3)
  • Google Cloud Storage(GCS)
  • Azure Blob Storage
  • 以及其他 S3 兼容存储

这种模式通过一个名为index shipper(简称 shipper)的适配器,把索引文件(TSDB)像 chunk 文件一样存放在对象存储中。该模式自Loki 2.0起正式 GA(generally available),具备快速、经济、简单的特点,也是当前及未来所有开发工作的落点。2.0 之前 Loki 为索引和 chunk 使用不同的存储后端,这类"遗留存储"(legacy storage)方案在新版本中已不再推荐。

三、数据格式:索引(Index)与块(Chunk)

Loki 有两种主要文件类型:

  • index(索引):相当于一份"目录",记录了在特定标签集合下到哪里能找到日志;
  • chunk(块):承载特定标签集合下日志条目的容器。

下图展示了 chunk 与 index 中存储内容的高层概览:

3.1 索引格式:TSDB(推荐)

当前通过 index shipper 单一存储支持的索引格式只有一种——TSDB(Time Series Database)。TSDB 是 Prometheus 维护者为时序(指标)数据开发的一种索引格式,可扩展性强,相比已被弃用的 BoltDB 索引具有诸多优势;Loki 的新存储特性仅在 TSDB 上可用

TSDB 从 Loki v2.8 起成为推荐索引,详细说明见 Single Store TSDB 文档。在该文档中可以看到一个完整的启用示例(节选):

schema_config: configs: # 旧 boltdb-shipper schema,仅作参考,无需修改 - from: "2023-01-03" # <---- 过去的日期 index: period: 24h prefix: index_ object_store: gcs schema: v12 store: boltdb-shipper # 新的 TSDB schema - from: "2023-01-05" # <---- 未来的日期 index: period: 24h prefix: index_ object_store: gcs schema: v13 store: tsdb storage_config: tsdb_shipper: active_index_directory: /data/tsdb-index cache_location: /data/tsdb-cache index_gateway_client: server_address: dns:///index-gateway.<namespace>.svc.cluster.local:9095

TSDB 还带来两个值得一提的特性:

  • 动态查询分片(Dynamic Query Sharding):索引中额外记录了每个 chunk 的大小(KB)与行数,查询前端据此规划分片。默认目标为每个分片处理约 300–600MB 数据,由limits_config中的tsdb_max_bytes_per_shard控制(默认 600MB),超过目标时 Loki 会将分片数翻倍。
  • 无需索引缓存:TSDB 格式紧凑且经过优化,Loki 目前不为其使用索引缓存。

3.2 Chunk 格式

一个 chunk 是某个流(stream,即唯一标签集合)在特定时间范围内日志行的容器。chunk 按租户和标签集合唯一区分。以下 ASCII 图详细描述了 chunk 的磁盘布局:

---------------------------------------------------------------------------- | | | | | MagicNumber(4b) | version(1b) | encoding (1b) | | | | | ---------------------------------------------------------------------------- | #structuredMetadata (uvarint) | ---------------------------------------------------------------------------- | len(label-1) (uvarint) | label-1 (bytes) | ---------------------------------------------------------------------------- | len(label-2) (uvarint) | label-2 (bytes) | ---------------------------------------------------------------------------- | len(label-n) (uvarint) | label-n (bytes) | ---------------------------------------------------------------------------- | checksum(from #structuredMetadata) | ---------------------------------------------------------------------------- | block-1 bytes | checksum (4b) | ---------------------------------------------------------------------------- | block-2 bytes | checksum (4b) | ---------------------------------------------------------------------------- | block-n bytes | checksum (4b) | ---------------------------------------------------------------------------- | #blocks (uvarint) | ---------------------------------------------------------------------------- | #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) | ---------------------------------------------------------------------------- | #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) | ---------------------------------------------------------------------------- | #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) | ---------------------------------------------------------------------------- | #entries(uvarint) | mint, maxt (varint) | offset, len (uvarint) | ---------------------------------------------------------------------------- | checksum(from #blocks) | ---------------------------------------------------------------------------- | #structuredMetadata len (uvarint) | #structuredMetadata offset (uvarint) | ---------------------------------------------------------------------------- | #blocks len (uvarint) | #blocks offset (uvarint) | ----------------------------------------------------------------------------

字段含义要点:

  • mintmaxt分别描述该区块内日志的最小与最大 Unix 纳秒时间戳
  • structuredMetadata区段用于存储不重复的字符串,即来自结构化元数据的标签名与标签值;该区段内的标签字符串与长度信息是压缩存储的。

这些布局在源码中可以得到印证:pkg/chunkenc/memchunk.go定义了ChunkFormatV1ChunkFormatV4四个版本、magicNumber = 0x12EE56A(即图中的 MagicNumber)、blocksPerChunk = 10(默认每 chunk 最多 10 个 block),并采用 CRC32-Castagnoli 多项式对区块计算校验和。结构化元数据从 ChunkFormatV4 引入,对应的 schema 版本号 ≥ 13,且要求索引类型为tsdb

3.3 Block 格式

一个 block 由一系列 entry 组成,每个 entry 就是一条独立的日志行。block 的字节是压缩存储的,下图为其解压后的形态:

----------------------------------------------------------------------------------------------------------------------------------------------- | ts (varint) | len (uvarint) | log-1 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) | ----------------------------------------------------------------------------------------------------------------------------------------------- | ts (varint) | len (uvarint) | log-2 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) | ----------------------------------------------------------------------------------------------------------------------------------------------- | ts (varint) | len (uvarint) | log-3 bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) | ----------------------------------------------------------------------------------------------------------------------------------------------- | ts (varint) | len (uvarint) | log-n bytes | len(from #symbols) | #symbols (uvarint) | symbol-1 (uvarint) | symbol-n*2 (uvarint) | -----------------------------------------------------------------------------------------------------------------------------------------------
  • ts是日志的 Unix 纳秒时间戳,len是日志条目的字节长度;
  • symbols(符号)保存对 chunk 中structuredMetadata区段内实际标签名/标签值字符串的引用——即 block 内不重复存储完整字符串,而是用符号序号指向它们,从而大幅节省空间。

四、部署模式一览

Loki 的架构决定了你可以用三种方式部署(详见 部署模式):

维度Monolithic 单二进制HA Monolithic 高可用单二进制Microservices 微服务
数据持久性
高可用
执行路径分离
运维复杂度🟩 低🟧 中🟥 高
可扩展性🟥 低🟧 中🟩 高
  • Monolithic 模式-target=all):适合快速上手实验与约 20GB/天以内的小规模读写;
  • HA Monolithic 模式:通过共享对象存储 +memberlist环 + 复制因子 3 实现高可用,取代了将在 Loki 4.0 移除的 Simple Scalable Deployment(SSD);
  • Microservices 模式:每个组件独立进程,扩展粒度最细,主要面向 Kubernetes 大集群。

想查看当前版本支持的全部 target,可以运行:

docker run docker.io/grafana/loki:3.2.1 -config.file=/etc/loki/local-config.yaml -list-targets

组件与部署目标的映射

下表展示了各组件在不同-target下的归属(数据来自 组件文档):

组件单独运行allreadwritebackend
Distributor 分发器xxx
Ingester 摄取器xxx
Query Frontend 查询前端xxx
Query Scheduler 查询调度器xxx
Querier 查询器xxx
Index Gateway 索引网关xx
Compactor 压缩器xxx
Ruler 规则器xxx
Pattern ingester 模式摄取器(可选)xxx
Bloom Planner(实验性)xx
Bloom Builder(实验性)xx
Bloom Gateway(实验性)xx

五、写入路径(Write Path)

从高层看,Loki 的写入路径按以下顺序工作:

  1. distributor(分发器)收到携带 streams(流)与日志行的 HTTP POST 请求;
  2. 分发器对请求中的每个 stream 做哈希,根据一致性哈希环(consistent hash ring)中的信息确定该送往哪个 ingester 实例;
  3. 分发器把每个 stream 发送给对应的 ingester 及其副本(副本数量由配置的复制因子 replication factor决定);
  4. ingester(摄取器)收到带日志行的 stream,为该 stream 创建新 chunk 或追加到已有 chunk(chunk 按租户 + 标签集合唯一);
  5. ingester 确认写入;
  6. 分发器等待**大多数(quorum)**ingester 确认写入;
  7. 若至少达到 quorum 的写入被确认,分发器返回成功(2xx 状态码);否则返回错误(4xx 或 5xx 状态码)。

写入路径涉及的关键机制(详见 组件文档 与 一致性哈希环文档):

  • 复制因子(Replication Factor):通常为 3。quorum 定义为floor(replication_factor / 2) + 1,即复制因子为 3 时至少需要 2 个 ingester 写入成功。若 3 个中只成功 2 个,可容忍丢失 1 个 ingester 但不能容忍丢失 2 个。
  • WAL(Write Ahead Log):ingester 将写入持久化到磁盘的 WAL,即使进程崩溃,重启时可回放。复制因子保证滚动重启时写入不中断,WAL 保证单点崩溃不丢数据,两者互补。
  • 一致性哈希:stream 用租户 ID + 标签集合做哈希,在哈希环上顺时针找到第一个大于该哈希值的 token 所属 ingester;复制因子大于 1 时继续取后续属于不同 ingester 的 token。每个 token 负责一段哈希区间,从而实现数据在 ingester 间的均匀分布。
  • quorum 一致性:所有分发器共享同一个哈希环,写请求可发给任意分发器;分发器等待"半数 + 1"个 ingester 的肯定响应后才向客户端确认(Dynamo 风格)。

六、读取路径(Read Path)

从高层看,Loki 的读取路径按以下顺序工作:

  1. query frontend(查询前端)收到携带 LogQL 查询的 HTTP GET 请求;
  2. 查询前端把查询拆分为多个子查询,交给 query scheduler(查询调度器);
  3. querier(查询器)从调度器拉取子查询;
  4. 查询器把查询发给所有 ingester以获取内存中的热数据;
  5. ingester 返回命中的内存数据(如有);
  6. 若 ingester 返回的数据不足或没有,查询器**惰性(lazily)**地从后端存储加载数据并执行查询;
  7. 查询器遍历所有收到的数据并去重,把子查询结果返回给查询前端;
  8. 查询前端等待一个查询的所有子查询完成并返回;
  9. 查询前端合并各子查询结果,得到最终结果返回给客户端。

关键点补充(详见 组件文档):

  • 去重:由于复制因子的存在,查询器可能收到重复数据;查询器对"相同纳秒时间戳 + 相同标签集合 + 相同日志内容"的数据做内部去重。
  • 查询前端是一个可选服务,负责拆分大查询、缓存(指标查询结果缓存、日志查询的空结果负缓存)、FIFO 排队与租户间公平调度;建议运行 2 个副本。
  • 查询调度器同样可选,提供每租户独立队列以保证跨租户查询公平性。
  • 索引网关(Index Gateway):仅用于单存储 TSDB,负责元数据查询——查询前端向它获取日志量以决定分片,查询器向它获取 chunk 引用以决定取哪些 chunk。

七、多租户(Multi-tenancy)

Loki 支持多租户隔离:无论是内存数据还是长期存储数据,都可以按tenant ID(租户 ID)分区。当 Loki 运行在多租户模式时,租户 ID 取自请求中的X-Scope-OrgIDHTTP 头;当 Loki未开启多租户模式时,该头会被忽略,租户 ID 固定为fake——这个 ID 会出现在索引与存储的 chunk 中。

在源码中可以找到大量对X-Scope-OrgID的读取与透传逻辑,例如pkg/ingester/flush.gopkg/logcli/client/client.go等;各组件在 push、查询、flush 等路径上都会携带该头以完成租户路由与隔离。

八、深入阅读

围绕本文主题,可以继续在仓库中查阅以下一手资料:

  • 组件详解:分发器的校验/预处理/限流、摄取器生命周期、查询前端缓存策略等细节
  • 部署模式:三种模式对比与 HA Monolithic 配置要求
  • 一致性哈希环:common.ringmemberlist配置
  • Single Store TSDB:TSDB schema 迁移与动态查询分片
  • 结构化元数据:chunk 中structuredMetadata区段的开启与查询方式
  • WAL 说明、保留策略、日志删除
  • 源码实现:chunk 编码与校验位于 pkg/chunkenc/memchunk.go,组件入口统一在 cmd/loki/main.go 中按-target路由启动

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 11:58:39

Buzz 离线语音转写:4 步跑通 Faster-Whisper 本地加速方案

Buzz 离线语音转写&#xff1a;4 步跑通 Faster-Whisper 本地加速方案 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz 断网时…

作者头像 李华
网站建设 2026/9/10 11:56:00

分布式能源系统无功优化与GCC多目标控制策略

1. 项目背景与核心挑战 电网故障下的分布式能源系统无功优化是当前电力电子领域的前沿课题。随着可再生能源占比不断提升&#xff0c;分布式电源并网带来的电压波动、谐波污染等问题日益突出。我在参与某微电网示范项目时&#xff0c;曾遇到光伏逆变器在电网电压骤降时无法有效…

作者头像 李华