2. Loki 核心架构:读写路径分离与核心组件详解
Loki 的架构设计核心思想是 读写路径分离,这与传统时序数据库(如 Prometheus)的架构有本质区别。这种设计使得 Loki 能够高效处理海量日志的写入,同时保证查询的实时性与资源隔离性。本章将深入剖析 Distributor、Ingester、Querier 三大核心组件,并通过数据流图展示日志从采集到查询的完整生命周期。
2.1 读写路径分离架构总览
Loki 将数据写入与查询请求分由不同的组件链处理,避免读写相互阻塞。写入路径负责接收、验证、分发日志流;读取路径负责索引查找、数据检索与结果聚合。
| 路径 | 核心组件 | 主要职责 | 数据流向 |
|---|---|---|---|
| 写入路径 | Distributor → Ingester | 接收日志、验证标签、分片、写入内存/对象存储 | 实时流式写入,低延迟 |
| 读取路径 | Querier → Ingester + 对象存储 | 解析查询、从内存和长期存储中检索日志 | 按需查询,支持并行扫描 |
设计优势: 写入路径不依赖查询索引,避免了传统日志系统(如 ELK)中写入压力大时查询性能急剧下降的问题。Ingester 的内存数据可被 Querier 实时查询,实现“写入即可见”。
2.2 核心组件详解
2.2.1 Distributor(分发器)
Distributor 是写入路径的入口,负责接收来自 Promtail、Fluentd 等采集器的日志推送。其核心工作包括:
- 标签验证与清洗: 检查标签名是否符合规范(如不包含特殊字符),拒绝无效数据。
- 哈希分片: 根据日志流的标签集合计算哈希值,通过一致性哈希环决定将日志转发到哪个 Ingester 实例。
- 负载均衡与副本控制: 默认将每个日志流复制到
replication_factor(通常为 3)个 Ingester 上,保证高可用。 - 速率限制: 对每个租户(tenant)的写入速率进行限制,防止恶意流量或突发写入压垮后端。
2.2.2 Ingester(写入器)
Ingester 是写入路径的核心状态组件,负责将日志数据暂存于内存,并最终刷写到对象存储(如 S3、GCS)或文件系统中。
| 阶段 | 行为 | 说明 |
|---|---|---|
| 接收 | 追加日志到内存中的 Chunk | 每个日志流对应一个 Chunk,Chunk 是压缩后的日志块 |
| 切分 | Chunk 达到大小或时间阈值后关闭 | 默认 1MB 或 1 小时,先到先切 |
| 刷写 | 将关闭的 Chunk 上传到对象存储 | 上传后 Chunk 变为只读,Ingester 释放内存 |
| 故障恢复 | 通过 WAL(Write Ahead Log)恢复未刷写数据 | Ingester 重启时从本地 WAL 重放日志,防止数据丢失 |
关键指标: 监控
loki_ingester_chunks_flushed_total 和 loki_ingester_memory_chunks。如果内存 Chunk 数量持续增长而不刷写,说明刷写队列阻塞,可能导致 OOM。
2.2.3 Querier(查询器)
Querier 是读取路径的入口,负责处理 LogQL 查询请求。它需要同时从两个数据源获取日志:
- Ingester 内存数据: 查询尚未刷写到对象存储的最新日志,保证实时性。
- 对象存储中的历史 Chunk: 查询已持久化的日志,通常通过索引(如 BoltDB、TSDB)加速定位。
Querier 采用 并行查询 策略:将查询拆分为多个子任务,分发到所有 Querier 实例(或通过 Query Frontend 进行缓存和分片),最后合并结果返回给客户端。
2.3 数据流图:写入路径与读取路径
以下两张 SVG 图分别展示日志写入和查询的完整数据流,帮助理解组件间的协作关系。
图 2-1:写入路径数据流
图 2-2:读取路径数据流
2.4 组件协作与故障排查要点
理解组件间的依赖关系,对于快速定位故障至关重要。以下列出常见故障场景及其关联组件:
| 故障现象 | 可能原因 | 涉及组件 | 排查命令/指标 |
|---|---|---|---|
| 日志写入延迟高 | Distributor 分片不均或 Ingester 刷写慢 | Distributor, Ingester | loki_distributor_ingester_appends_total, loki_ingester_chunks_flush_queue_length |
| 查询超时或返回空 | Querier 无法连接 Ingester 或索引损坏 | Querier, Ingester, 索引存储 | loki_querier_ingester_query_duration_seconds, 检查 Ingester 是否在 ring 中 |
| Ingester OOM | 内存 Chunk 未及时刷写,或日志流过多 | Ingester | loki_ingester_memory_chunks, 调整 chunk_target_size |
| 数据丢失 | Ingester 崩溃且 WAL 损坏 | Ingester, WAL | 检查 WAL 目录完整性,确认 ingester.wal-enabled |
最佳实践: 在部署 Loki 时,务必为 Distributor 和 Querier 配置独立的
ring 和 memberlist,确保组件发现稳定。同时,为 Ingester 挂载持久化卷用于 WAL,避免因 Pod 重启导致内存数据丢失。