2. Loki 核心架构:读写路径、Ingester、Querier、Distributor 组件详解
理解 Loki 的核心架构是进行性能调优和告警聚合设计的基础。Loki 采用微服务架构,各组件职责清晰,通过读写分离路径实现高效的日志存储与查询。本章将深入剖析 Distributor、Ingester、Querier 三大核心组件及其协作流程。
2.1 整体架构概览:读写分离与水平扩展
Loki 的架构设计围绕“低成本、高可用、易扩展”展开。其核心思想是将日志数据的写入路径与读取路径完全分离,每个路径上的组件均可独立水平扩展。
架构核心原则:
- 写路径:Distributor → Ingester → 对象存储(如 S3、GCS)。
- 读路径:Querier → Ingester(内存数据)+ 对象存储(历史数据)。
- 无状态化:Distributor 和 Querier 为无状态组件,Ingester 为有状态组件。
2.2 Distributor:写入入口与数据分片
Distributor 是日志写入路径的第一站,负责接收来自 Agent(如 Promtail、Fluentd)的日志流。它的核心职责是验证、哈希和分发。
2.2.1 核心功能
- 数据验证:检查日志流标签是否符合规范,时间戳是否在可接受范围内(默认 ±1 小时)。
- 一致性哈希:根据日志流的标签集(Label Set)计算哈希值,通过一致性哈希环确定该日志流应被发送到哪个 Ingester 副本。
- 负载均衡:将日志流均匀分发到多个 Ingester 实例,避免单点瓶颈。
- 副本转发:根据配置的复制因子(默认 3),将同一份数据转发到多个 Ingester 副本,保证数据高可用。
2.2.2 性能调优关键点
| 参数 | 默认值 | 调优建议 |
|---|---|---|
distributor.ring.heartbeat-period |
5s | 集群规模较大时可适当增大至 10-15s,减少心跳网络开销。 |
distributor.ingestion-rate-limit-mb |
10 MB/s | 根据实际流量调整,避免限流导致写入失败。 |
distributor.ingestion-burst-size-mb |
20 MB | 允许短时突发流量,建议为 rate-limit 的 2 倍。 |
最佳实践:Distributor 是无状态组件,建议部署 2-3 个副本,并通过 K8s HPA 或手动扩容应对流量增长。监控
distributor_ingester_appends_total 和 distributor_ingester_append_failures_total 指标。
2.3 Ingester:内存写入与数据持久化
Ingester 是 Loki 中唯一的有状态组件,负责接收 Distributor 转发的日志数据,在内存中构建索引和块(Chunk),并最终将数据刷写到对象存储。
2.3.1 写入流程
- 接收数据:Ingester 通过 gRPC 接收来自 Distributor 的日志流。
- 内存构建:将日志流按时间顺序追加到内存中的 Chunk 里,同时构建倒排索引。
- WAL 预写日志:在数据写入内存前,先写入 WAL(Write-Ahead Log),防止进程崩溃导致数据丢失。
- Chunk 刷写:当 Chunk 达到最大大小(默认 1.5 MB)或最大时间跨度(默认 30 分钟)时,将 Chunk 压缩并上传到对象存储。
- 内存释放:刷写成功后,从内存中移除该 Chunk,释放资源。
2.3.2 关键数据结构
| 数据结构 | 作用 | 性能影响 |
|---|---|---|
| Chunk | 存储压缩后的日志数据块 | Chunk 大小影响 I/O 和查询效率 |
| WAL | 预写日志,保证数据持久性 | WAL 写入延迟影响写入吞吐 |
| 倒排索引 | 标签 → 日志流 ID 的映射 | 索引大小影响内存占用 |
2.3.3 性能调优关键点
- Chunk 大小:增大
ingester.chunk-block-size(默认 256KB)可减少存储对象数量,但会增加内存占用。 - WAL 路径:使用高性能 SSD 磁盘存放 WAL,避免与系统盘共用。
- 内存限制:通过
ingester.max-chunk-age和ingester.chunk-idle-period控制 Chunk 在内存中的驻留时间,防止 OOM。 - 刷写策略:调整
ingester.chunk-target-size(默认 1.5 MB)平衡内存与存储成本。
注意:Ingester 重启或滚动更新会导致内存中的 Chunk 丢失(如果 WAL 未启用或未刷写)。务必启用 WAL(
ingester.wal-enabled: true)并配置持久化卷。
2.4 Querier:查询执行与结果合并
Querier 是读取路径的核心,负责处理用户的 LogQL 查询请求。它需要从 Ingester(内存中的最新数据)和对象存储(历史数据)中获取结果,并进行合并与去重。
2.4.1 查询执行流程
- 接收查询:Querier 通过 HTTP API 接收 LogQL 查询请求。
- 并行查询:将查询拆分为多个子查询,并行发送到所有 Ingester 实例(查询内存数据)和对象存储(查询历史数据)。
- 结果合并:收集所有子查询结果,按时间戳排序,去除重复日志行。
- 返回结果:将合并后的结果返回给客户端。
2.4.2 性能调优关键点
| 参数 | 默认值 | 调优建议 |
|---|---|---|
querier.max-concurrent |
10 | 根据 CPU 和内存资源调整,增大可提升并发查询能力。 |
querier.query-timeout |
5m | 对于大范围查询,适当增大超时时间。 |
querier.engine.timeout |
5m | 与 query-timeout 配合使用,控制查询引擎执行时间。 |
查询性能瓶颈:
- Ingester 内存查询:如果 Ingester 内存中 Chunk 过多,查询延迟会显著增加。建议控制
ingester.max-chunk-age在合理范围。 - 对象存储查询:频繁的 S3 API 调用会增加延迟。使用
querier.index-cache-size和querier.chunk-cache-size缓存热点数据。 - 结果合并:大量日志行的排序和去重会消耗 CPU。建议在查询时使用
topk或limit减少返回数据量。
2.5 组件协作与数据流总结
为了更直观地展示三个组件在读写路径中的协作关系,下图聚焦于一次完整的日志写入与查询生命周期。
2.6 组件对比与选型建议
| 组件 | 状态 | 核心资源 | 扩展方式 | 故障影响 |
|---|---|---|---|---|
| Distributor | 无状态 | CPU / 网络 | 水平扩展(增加副本) | 写入中断,恢复后自动重试 |
| Ingester | 有状态 | 内存 / 磁盘(WAL) | 水平扩展(增加副本 + 重新分片) | 内存数据丢失(需 WAL 恢复) |
| Querier | 无状态 | CPU / 内存 | 水平扩展(增加副本) | 查询中断,客户端重试 |
架构选型建议:
- 小规模(< 1TB/天):单机模式或微服务模式,所有组件部署在同一节点。
- 中等规模(1-10TB/天):微服务模式,Distributor 和 Querier 各 2-3 副本,Ingester 3-5 副本。
- 大规模(> 10TB/天):微服务模式 + 分片(Zone Awareness),Ingester 按可用区分布,Querier 启用并行查询。