ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

Grafana Loki 架构完全解读:微服务组件、读写路径与索引/块存储格式 Grafana Loki 架构完全解读微服务组件、读写路径与索引/块存储格式【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/lokiLokiLike Prometheus, but for logs采用微服务架构设计以水平可扩展的分布式系统形态运行同时它把全部组件编译进同一个二进制或 Docker 镜像通过-target启动参数决定进程扮演的角色。本文以 Loki 架构官方文档 为主线结合本仓库的 组件文档、部署模式文档、TSDB 存储文档 以及pkg/chunkenc/memchunk.go等源码系统讲解 Loki 的总体架构、存储与数据格式、写入路径、读取路径和多租户机制帮助你从能跑进阶到真正理解 Loki 为什么这么设计。一、总体架构单二进制承载的微服务Loki 是一个由众多微服务组成的分布式系统但它采用了独特的构建模型所有微服务都存在于同一个二进制中。无论你是用 Docker 镜像还是直接编译的可执行文件启动 Loki都会发现可执行文件是同一份区别只在于启动时传入的-target命令行标志-targetall单进程内同时运行全部组件monolithic / 单二进制模式-targetread、-targetwrite、-targetbackend把组件逻辑分组为读、写、后端三部分simple scalable deployment简单可扩展部署已被 HA Monolithic 模式取代-target具体组件名只运行某一个组件microservices 微服务模式。这种一份二进制、多角色启动的设计带来两个关键能力快速上手以单二进制模式启动全部组件在一个进程内同时运行几分钟即可完成本地体验平滑演进Loki 将存储的数据与摄取/查询数据的软件解耦因此当你的需求变化时可以几乎不修改配置或仅做极小改动就把集群切换到另一种部署模式。具体模式对比与切换要求见 部署模式各组件职责详见 组件说明。二、存储架构单一对象存储后端Loki 将所有数据存放在同一个对象存储后端中例如Amazon Simple Storage ServiceS3Google Cloud StorageGCSAzure Blob Storage以及其他 S3 兼容存储这种模式通过一个名为index shipper简称 shipper的适配器把索引文件TSDB像 chunk 文件一样存放在对象存储中。该模式自Loki 2.0起正式 GAgenerally available具备快速、经济、简单的特点也是当前及未来所有开发工作的落点。2.0 之前 Loki 为索引和 chunk 使用不同的存储后端这类遗留存储legacy storage方案在新版本中已不再推荐。三、数据格式索引Index与块ChunkLoki 有两种主要文件类型index索引相当于一份目录记录了在特定标签集合下到哪里能找到日志chunk块承载特定标签集合下日志条目的容器。下图展示了 chunk 与 index 中存储内容的高层概览3.1 索引格式TSDB推荐当前通过 index shipper 单一存储支持的索引格式只有一种——TSDBTime 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:9095TSDB 还带来两个值得一提的特性动态查询分片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) | ----------------------------------------------------------------------------字段含义要点mint与maxt分别描述该区块内日志的最小与最大 Unix 纳秒时间戳structuredMetadata区段用于存储不重复的字符串即来自结构化元数据的标签名与标签值该区段内的标签字符串与长度信息是压缩存储的。这些布局在源码中可以得到印证pkg/chunkenc/memchunk.go定义了ChunkFormatV1到ChunkFormatV4四个版本、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 模式-targetall适合快速上手实验与约 20GB/天以内的小规模读写HA Monolithic 模式通过共享对象存储 memberlist环 复制因子 3 实现高可用取代了将在 Loki 4.0 移除的 Simple Scalable DeploymentSSDMicroservices 模式每个组件独立进程扩展粒度最细主要面向 Kubernetes 大集群。想查看当前版本支持的全部 target可以运行docker run docker.io/grafana/loki:3.2.1 -config.file/etc/loki/local-config.yaml -list-targets组件与部署目标的映射下表展示了各组件在不同-target下的归属数据来自 组件文档组件单独运行allreadwritebackendDistributor 分发器xxxIngester 摄取器xxxQuery Frontend 查询前端xxxQuery Scheduler 查询调度器xxxQuerier 查询器xxxIndex Gateway 索引网关xxCompactor 压缩器xxxRuler 规则器xxxPattern ingester 模式摄取器可选xxxBloom Planner实验性xxBloom Builder实验性xxBloom Gateway实验性xx五、写入路径Write Path从高层看Loki 的写入路径按以下顺序工作distributor分发器收到携带 streams流与日志行的 HTTP POST 请求分发器对请求中的每个 stream 做哈希根据一致性哈希环consistent hash ring中的信息确定该送往哪个 ingester 实例分发器把每个 stream 发送给对应的 ingester 及其副本副本数量由配置的复制因子 replication factor决定ingester摄取器收到带日志行的 stream为该 stream 创建新 chunk 或追加到已有 chunkchunk 按租户 标签集合唯一ingester 确认写入分发器等待**大多数quorum**ingester 确认写入若至少达到 quorum 的写入被确认分发器返回成功2xx 状态码否则返回错误4xx 或 5xx 状态码。写入路径涉及的关键机制详见 组件文档 与 一致性哈希环文档复制因子Replication Factor通常为 3。quorum 定义为floor(replication_factor / 2) 1即复制因子为 3 时至少需要 2 个 ingester 写入成功。若 3 个中只成功 2 个可容忍丢失 1 个 ingester 但不能容忍丢失 2 个。WALWrite Ahead Logingester 将写入持久化到磁盘的 WAL即使进程崩溃重启时可回放。复制因子保证滚动重启时写入不中断WAL 保证单点崩溃不丢数据两者互补。一致性哈希stream 用租户 ID 标签集合做哈希在哈希环上顺时针找到第一个大于该哈希值的 token 所属 ingester复制因子大于 1 时继续取后续属于不同 ingester 的 token。每个 token 负责一段哈希区间从而实现数据在 ingester 间的均匀分布。quorum 一致性所有分发器共享同一个哈希环写请求可发给任意分发器分发器等待半数 1个 ingester 的肯定响应后才向客户端确认Dynamo 风格。六、读取路径Read Path从高层看Loki 的读取路径按以下顺序工作query frontend查询前端收到携带 LogQL 查询的 HTTP GET 请求查询前端把查询拆分为多个子查询交给 query scheduler查询调度器querier查询器从调度器拉取子查询查询器把查询发给所有 ingester以获取内存中的热数据ingester 返回命中的内存数据如有若 ingester 返回的数据不足或没有查询器**惰性lazily**地从后端存储加载数据并执行查询查询器遍历所有收到的数据并去重把子查询结果返回给查询前端查询前端等待一个查询的所有子查询完成并返回查询前端合并各子查询结果得到最终结果返回给客户端。关键点补充详见 组件文档去重由于复制因子的存在查询器可能收到重复数据查询器对相同纳秒时间戳 相同标签集合 相同日志内容的数据做内部去重。查询前端是一个可选服务负责拆分大查询、缓存指标查询结果缓存、日志查询的空结果负缓存、FIFO 排队与租户间公平调度建议运行 2 个副本。查询调度器同样可选提供每租户独立队列以保证跨租户查询公平性。索引网关Index Gateway仅用于单存储 TSDB负责元数据查询——查询前端向它获取日志量以决定分片查询器向它获取 chunk 引用以决定取哪些 chunk。七、多租户Multi-tenancyLoki 支持多租户隔离无论是内存数据还是长期存储数据都可以按tenant ID租户 ID分区。当 Loki 运行在多租户模式时租户 ID 取自请求中的X-Scope-OrgIDHTTP 头当 Loki未开启多租户模式时该头会被忽略租户 ID 固定为fake——这个 ID 会出现在索引与存储的 chunk 中。在源码中可以找到大量对X-Scope-OrgID的读取与透传逻辑例如pkg/ingester/flush.go、pkg/logcli/client/client.go等各组件在 push、查询、flush 等路径上都会携带该头以完成租户路由与隔离。八、深入阅读围绕本文主题可以继续在仓库中查阅以下一手资料组件详解分发器的校验/预处理/限流、摄取器生命周期、查询前端缓存策略等细节部署模式三种模式对比与 HA Monolithic 配置要求一致性哈希环common.ring与memberlist配置Single Store TSDBTSDB 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),仅供参考
返回列表