2. Remote Write 架构解析:Agent 模式、WAL 机制与 Shard/Queue 内部结构

要精准调优 Remote Write 队列,必须先理解其底层架构。本章深入剖析三个核心组件:Prometheus Agent 模式(轻量级采集转发)、Write-Ahead-Log (WAL)(数据持久化与故障恢复基石)、以及 Shard 与 Queue(并发发送与流量控制引擎)。

2.1 Prometheus Agent 模式:专为 Remote Write 而生的轻量级角色

Prometheus 2.32.0 版本引入了 Agent 模式,它剥离了传统 Prometheus Server 的本地存储、查询、告警等重型组件,仅保留采集、服务发现、WAL 写入和 Remote Write 发送功能。这使得 Agent 模式成为边缘节点、Kubernetes 节点级采集、以及大规模联邦场景下的理想选择。

特性 Prometheus Server (默认模式) Prometheus Agent 模式
本地存储 (TSDB) 完整 TSDB,支持查询与告警 仅保留 WAL,无查询能力
内存开销 较高(需维护内存中的 TSDB 块) 较低(仅缓存 WAL 与发送队列)
磁盘占用 较大(存储历史数据块) 较小(仅 WAL 文件,可配置保留时间)
启动参数 --storage.tsdb.retention.time --agent 标志启用
适用场景 独立监控、本地告警、临时查询 集中式后端、边缘采集、大规模联邦
调优启示: Agent 模式下的 WAL 配置与 Server 模式完全一致,但 Agent 模式没有 TSDB 压缩和查询干扰,WAL 写入更稳定,队列调优的干扰因素更少。生产环境中,建议将 Agent 模式的 --agent.wal-replay-chunk-size 从默认的 32MB 调整为 64MB,以减少 WAL 回放时的 I/O 次数。

2.2 Write-Ahead-Log (WAL):数据可靠性的基石

WAL 是 Remote Write 流程中数据持久化的第一道防线。所有采集到的样本在发送前,都会先写入 WAL 文件。这确保了即使进程崩溃,数据也不会丢失,重启后可以从 WAL 中恢复并继续发送。

WAL 写入流程: 采集器 → 样本序列 → WAL 追加写入(磁盘 fsync) → 内存中的 Series 缓存 → Remote Write 队列读取。

WAL 文件位于 data/wal/ 目录下,由一系列固定大小的段文件(默认 128MB)组成。每个段文件包含多个记录,记录类型包括:

  • Series 记录: 存储标签集合(LabelSet)与 Series ID 的映射。
  • Sample 记录: 存储时间戳与样本值。
  • Tombstone 记录: 标记已删除的 Series(用于清理)。
  • Exemplar 记录: 存储示例数据(如 Trace ID 关联)。
WAL 文件结构与写入流程 采集器 (Scraper) WAL 写入器 WAL 段文件 (128MB) 000001 / 000002 / ... fsync 持久化 Series 缓存 WAL 记录类型 • Series 记录:标签集合 → Series ID 映射(用于去重与压缩) • Sample 记录:时间戳 + 样本值(核心数据载体) • Tombstone 记录:标记已删除 Series(WAL 清理时使用) • Exemplar 记录:示例数据(Trace ID 关联,可选)
关键调优参数: WAL 的 --storage.tsdb.wal-segment-size 默认 128MB。对于高吞吐场景(每秒 > 50 万样本),建议增大至 256MB 以减少段文件切换频率,降低 I/O 抖动。但注意,增大段文件会延长崩溃恢复时的回放时间。

2.3 Shard 与 Queue:并发发送的引擎与流量控制

Remote Write 的核心发送逻辑由 Shard(分片) 和 Queue(队列) 协同完成。Shard 是并发发送的 goroutine,Queue 是待发送数据的缓冲区。理解它们的内部结构是调优的关键。

2.3.1 核心数据结构

  • Queue (队列): 一个环形缓冲区(Ring Buffer),存储待发送的样本批次。每个批次包含多个样本,由 capacity 参数控制总容量。
  • Shard (分片): 一个独立的 goroutine,负责从 Queue 中取出批次,执行 HTTP/2 请求发送到远端。Shard 数量由 min_shards 和 max_shards 动态调整。
  • Batch (批次): 发送的最小单位,由 max_samples_per_send 控制(默认 500 样本/批次)。
Shard 与 Queue 内部结构 WAL 读取器 主队列 (Ring Buffer) 容量: capacity 批次大小: max_samples_per_send 分发器 Shard 1 (Goroutine) HTTP/2 客户端 重试队列 (指数退避) Shard 2 (Goroutine) HTTP/2 客户端 重试队列 (指数退避) Shard N (Goroutine) HTTP/2 客户端 重试队列 (指数退避) 远端接收端 (Remote Write Receiver)

2.3.2 动态 Shard 调整机制

Prometheus 会根据发送延迟和队列积压情况,自动在 min_shards 和 max_shards 之间调整 Shard 数量。调整算法基于以下指标:

指标名称 PromQL 查询 调优阈值参考
队列剩余样本数 prometheus_remote_storage_queue_highest_sent_timestamp_seconds - prometheus_remote_storage_queue_samples_in 持续 > 10000 时触发扩容
发送延迟 rate(prometheus_remote_storage_samples_total[5m]) / rate(prometheus_remote_storage_succeeded_samples_total[5m]) 延迟 > 10s 时触发扩容
Shard 繁忙度 prometheus_remote_storage_shards_busy 持续 > 80% 时触发扩容
调优核心公式: 所需 Shard 数 ≈ (每秒样本数 × 平均延迟) / (max_samples_per_send × 并发因子)。例如:每秒 10 万样本,平均延迟 2s,max_samples_per_send=500,则理论 Shard 数 ≈ (100000 × 2) / (500 × 0.8) = 500 个 Shard。实际需结合网络带宽和远端处理能力调整。

2.3.3 重试与背压机制

当远端返回错误(如 429 Too Many Requests、503 Service Unavailable)时,Shard 会将失败的批次放入 重试队列,采用指数退避策略重试(初始 1s,最大 60s)。重试队列的容量由 retry_on_http_429 和 min_backoff/max_backoff 控制。

  • 背压触发条件: 当重试队列积压超过 capacity 的 50% 时,WAL 读取器会暂停读取,形成背压,防止内存溢出。
  • 数据丢失风险: 如果背压持续超过 5 分钟,WAL 段文件可能被删除(受 wal_cleanup_age 控制),导致数据丢失。这是调优中需要极力避免的状态。
避坑指南: 不要将 max_shards 设置得过大(如 > 1000),否则会导致过多的 HTTP/2 连接和 goroutine 开销,反而降低吞吐。建议从 min_shards=10、max_shards=200 开始,根据实际指标逐步调整。

2.4 架构总结:数据流全景

将上述组件串联起来,Remote Write 的完整数据流如下:

  1. 采集: Scraper 从目标拉取指标,写入 WAL。
  2. 持久化: WAL 将数据 fsync 到磁盘,确保崩溃恢复。
  3. 读取: WAL 读取器从最新段文件读取样本,送入主队列。
  4. 分片: 分发器根据 Shard 负载,将批次分配给空闲 Shard。
  5. 发送: Shard 通过 HTTP/2 发送批次到远端,失败则进入重试队列。
  6. 确认: 远端返回 2xx 后,Shard 从队列中移除该批次,并更新 WAL 的 checkpoint。