3. Loki 架构速览:Ingester、Querier、Distributor、Compactor 组件职责
在深入压测沙盘之前,必须建立对 Loki 核心组件的清晰认知。Loki 采用微服务架构,各组件各司其职,共同完成日志的接收、存储、查询与压缩。理解这些组件的职责边界,是后续分析写入放大与查询限流问题的前提。
3.1 组件全景与数据流
下图展示了 Loki 核心组件之间的数据流转关系。日志从左侧进入,经过分发、写入、查询与压缩四个阶段,最终持久化到对象存储。
架构要点:Loki 的写入路径(Distributor → Ingester → 对象存储)与查询路径(Querier → Ingester + 对象存储)是分离的,这种设计使得写入放大主要发生在 Ingester 侧,而查询限流则与 Querier 的并发控制密切相关。
3.2 组件职责详解
下表从职责、关键行为、压测关注点三个维度,逐一拆解四个核心组件:
| 组件 | 核心职责 | 关键行为 | 压测关注点 |
|---|---|---|---|
| Distributor | 接收日志推送,校验合法性,按哈希分片转发给 Ingester | gRPC 接收 → 标签校验 → 一致性哈希路由 → 批量转发 | 接收吞吐、转发延迟、限流触发阈值 |
| Ingester | 缓存日志流,构建内存索引,定期 Flush 到对象存储 | 追加写入 → 构建 Chunk → 内存索引 → Flush 落盘 | 写入放大系数、Chunk 切割策略、OOM 风险 |
| Querier | 接收 LogQL 查询,合并 Ingester 内存数据与对象存储数据 | 解析 LogQL → 并行读取 Ingester + 对象存储 → 去重聚合 | 查询并发数、超时控制、数据一致性 |
| Compactor | 合并对象存储中的小 Chunk,构建/更新倒排索引 | 扫描 Chunk → 合并 → 生成新索引 → 清理旧数据 | 压缩频率、索引膨胀、对象存储请求量 |
3.3 组件交互细节
3.3.1 Distributor:写入入口的守门人
Distributor 是 Loki 写入路径的第一站,负责以下关键操作:
- 标签校验:检查日志流标签是否符合命名规范,拒绝空标签或超长标签。
- 速率限制:基于每个租户(Tenant)的写入速率配额,超出时直接返回 429 限流响应。
- 一致性哈希路由:根据日志流标签计算哈希值,将同一流的所有日志转发到同一个 Ingester 副本,保证写入顺序。
- 副本转发:默认配置下,Distributor 会将每条日志转发到
replication_factor个 Ingester 副本(通常为 3),这是写入放大的第一个来源。
# Distributor 限流配置示例(loki.yaml)
distributor:
ring:
kvstore:
store: memberlist
rate_store:
max_global_rate: 50MB # 全局写入速率上限
max_global_burst: 100MB # 突发写入上限
ingester_client:
grpc_client_config:
max_recv_msg_size: 10485760 # 10MB
3.3.2 Ingester:写入放大的核心战场
Ingester 是写入放大问题的集中爆发点。它维护着内存中的日志流(Stream)和 Chunk 结构:
- Stream 与 Chunk:每个唯一的标签组合对应一个 Stream,每个 Stream 包含多个 Chunk。Chunk 是 Loki 的最小存储单元,默认大小约为 1.5MB(未压缩)。
- 写入放大来源:
- 副本写入:每个日志条目被写入
replication_factor个 Ingester,放大系数 = replication_factor。 - Chunk 切割:当 Chunk 达到容量上限或时间窗口(默认 30 分钟)时,Ingester 会关闭当前 Chunk 并创建新 Chunk,关闭的 Chunk 会被 Flush 到对象存储。频繁切割会导致大量小 Chunk,增加对象存储请求量。
- 内存索引:Ingester 在内存中维护倒排索引,用于快速定位 Stream。当 Stream 数量巨大时,索引本身也会消耗大量内存。
- 副本写入:每个日志条目被写入
压测警示:在 etcd 压测沙盘中,如果模拟大量短生命周期 Pod 的日志(即高频创建/删除 Stream),Ingester 的内存索引会急剧膨胀,同时产生大量小 Chunk,导致写入放大系数从正常的 3 倍飙升到 10 倍以上。这是课程后续重点分析的现象。
3.3.3 Querier:查询路径的调度中枢
Querier 负责处理所有 LogQL 查询请求,其工作流程如下:
- 查询解析:将 LogQL 语句解析为抽象语法树(AST),提取标签选择器、时间范围、聚合函数等。
- 分片查询:根据时间范围和标签选择器,将查询拆分为多个子查询,并行发送到所有 Ingester(查询内存数据)和对象存储(查询已落盘数据)。
- 结果合并:收集所有子查询结果,进行去重、排序、聚合,返回给客户端。
Querier 的并发控制直接影响查询限流行为:
# Querier 并发配置示例
querier:
max_concurrent: 10 # 最大并发查询数
query_timeout: 5m # 查询超时时间
engine:
max_look_back_period: 72h # 最大回溯时间范围
限流触发条件:当并发查询数超过
max_concurrent 时,后续查询请求会被排队或直接拒绝(返回 429)。在压测沙盘中,可以通过调整并发数观察 Querier 的限流行为与系统吞吐量的关系。
3.3.4 Compactor:存储效率的优化器
Compactor 是一个后台组件,定期执行以下任务:
- Chunk 合并:将对象存储中的多个小 Chunk 合并为更大的 Chunk(默认目标大小 10MB),减少对象存储中的文件数量。
- 索引构建:基于合并后的 Chunk 生成倒排索引(Boltdb-shipper 模式),加速查询时的 Chunk 定位。
- 数据清理:删除超过保留期限的 Chunk 和索引。
Compactor 的合并效率直接影响查询性能:
| 指标 | 合并前(小 Chunk 多) | 合并后(大 Chunk 少) |
|---|---|---|
| 对象存储文件数 | 高(10,000+) | 低(1,000-) |
| 查询时索引查找次数 | 高 | 低 |
| Compactor CPU 消耗 | 低 | 高(合并期间) |
3.4 组件职责总结
下图以矩阵形式总结四个组件在写入与查询两条路径中的角色定位: