2. Collector 架构与内存模型

要保障 SLI 口径数据采集的稳定性,首先需要理解 Collector 的内部架构与 JVM 内存模型。Collector 作为数据管道,其内存使用效率直接决定了在高吞吐、高并发场景下是否会触发 OOM 或频繁 GC,进而影响 SLI 数据的连续性与准确性。

2.1 Collector 组件架构:Receiver → Processor → Exporter

Collector 采用经典的管道(Pipeline)架构,数据依次经过三个核心组件:Receiver(接收器)、Processor(处理器)和 Exporter(导出器)。每个组件职责明确,且可独立配置与扩展。

组件 职责 内存敏感点
Receiver 接收来自上游(Agent、SDK、其他 Collector)的遥测数据,完成协议解析与初步校验。 接收缓冲区大小、反序列化对象池
Processor 对数据进行过滤、聚合、采样、属性修改等业务逻辑处理。 聚合窗口期数据驻留、中间对象创建速率
Exporter 将处理后的数据写入后端存储(如 Prometheus、Kafka、OTLP 后端)。 发送队列积压、重试缓冲区、序列化临时对象
调优提示: 在 SLI 口径采集场景中,Processor 的聚合操作最容易产生内存压力。例如,5 秒窗口内对 10 万条请求进行 latency 分桶聚合,会创建大量临时 Map 和数组对象,需要重点关注 Young GC 频率。
Collector 管道架构与数据流 Collector 管道架构与数据流 Agent / SDK 遥测数据源 Receiver 协议解析 缓冲队列 Processor 过滤 / 聚合 采样 / 属性修改 Exporter 序列化 发送队列 后端 ⚠ 内存压力高发区域 Receiver 缓冲区 → Processor 聚合窗口 → Exporter 积压队列 这三个环节是 OOM 和 GC 瓶颈的排查重点

2.2 JVM 堆内存与非堆内存划分

Collector 运行在 JVM 之上,其内存管理分为堆内存(Heap)和非堆内存(Non-Heap)两大区域。理解这两部分的划分,是进行内存调优的前提。

2.2.1 堆内存(Heap)

堆内存是 Java 对象分配的主要区域,Collector 处理的所有遥测数据、聚合结果、缓存对象都存储在此。堆内存内部进一步划分为:

  • 新生代(Young Generation):包含 Eden 区和两个 Survivor 区(S0/S1)。大多数临时对象(如反序列化的 Span、Metric 点)在此分配,Minor GC 频繁。
  • 老年代(Old Generation):存放长期存活的对象,如 Processor 的聚合窗口快照、Exporter 的重试队列。Major GC 或 Full GC 发生时,通常意味着老年代空间不足。

2.2.2 非堆内存(Non-Heap)

非堆内存包括 Metaspace、Code Cache、线程栈、直接内存(Direct Buffer)等。在 Collector 场景中,以下两项尤其关键:

  • Metaspace:存储类的元数据。如果 Collector 动态加载了大量 Processor/Exporter 插件,Metaspace 可能持续增长。
  • 直接内存(Direct Memory):Netty 等网络框架用于 I/O 操作的缓冲区。Receiver 和 Exporter 的异步网络通信会大量使用直接内存,若未合理限制,容易引发 OutOfMemoryError: Direct buffer memory。
内存区域 类型 Collector 典型用途 调优关注点
Eden 堆(新生代) 新到达的遥测数据对象 对象分配速率、TLAB 大小
Survivor 堆(新生代) 经过一次 Minor GC 后存活的对象 Survivor 区比例、晋升阈值
Old Gen 堆(老年代) 聚合窗口快照、长连接状态 老年代占用率、晋升速率
Metaspace 非堆 插件类定义、动态代理 MaxMetaspaceSize 限制
Direct Memory 非堆 Netty 接收/发送缓冲区 -XX:MaxDirectMemorySize
常见陷阱: 很多调优者只关注堆内存,忽略了直接内存。在 Collector 高吞吐场景下,Netty 的 Direct Buffer 可能迅速耗尽,导致 OutOfMemoryError。建议将 -XX:MaxDirectMemorySize 设置为堆内存的 1/4 到 1/3,并配合 NMT(Native Memory Tracking)监控。
JVM 内存划分与 Collector 数据驻留 JVM 内存划分与 Collector 数据驻留 堆内存 (Heap) 新生代 (Young Gen) Eden 新 Span / Metric 对象 S0 存活对象 S1 存活对象 晋升 老年代 (Old Gen) 聚合窗口快照 Exporter 重试队列 长连接状态 非堆内存 (Non-Heap) Metaspace 插件类 / 动态代理 Direct Memory Netty 接收/发送缓冲区 Code Cache / 线程栈 JIT 编译代码 / 线程上下文

2.3 GC(垃圾回收)基本原理

GC 是 JVM 自动管理堆内存的核心机制。在 Collector 场景中,理解 GC 的基本原理有助于我们选择合适的回收器并调整参数,以降低 STW(Stop-The-World)时间,保障 SLI 数据采集的实时性。

2.3.1 分代收集理论

JVM 基于“弱分代假设”将堆分为新生代和老年代:

  • 新生代:大多数对象“朝生夕死”,Minor GC 频繁但速度快。
  • 老年代:对象存活时间长,Major GC 或 Full GC 频率低但 STW 时间长。

2.3.2 常见 GC 算法

算法 原理 适用场景 Collector 推荐度
标记-复制 将存活对象复制到空闲区域,直接回收原区域 新生代(对象存活率低) ⭐⭐⭐⭐⭐
标记-清除 标记存活对象,清除未标记对象,产生碎片 老年代(CMS 回收器) ⭐⭐⭐
标记-整理 标记存活对象,向一端移动,消除碎片 老年代(G1、Parallel Old) ⭐⭐⭐⭐

2.3.3 Collector 场景下的 GC 关注点

  • Minor GC 频率:如果每秒创建的对象量过大(例如高吞吐的 Span 接收),Eden 区很快填满,导致 Minor GC 过于频繁。可通过增大 -Xmn 或调整 -XX:SurvivorRatio 缓解。
  • 晋升速率(Promotion Rate):如果大量对象在 Minor GC 后仍存活并晋升到老年代,老年代会迅速膨胀,触发 Major GC。需要检查 Processor 中是否存在长生命周期的缓存对象。
  • Full GC 停顿:Full GC 会导致 Collector 暂停处理数据,造成 SLI 数据采集断点。应尽量避免 Full GC,或使用 G1 GC 控制停顿时间。
实战建议: 对于 Collector 这类对延迟敏感的数据管道应用,推荐使用 G1 GC(Garbage-First)或 ZGC(低延迟场景)。G1 可通过 -XX:MaxGCPauseMillis=200 控制最大停顿时间,适合 5~10 秒聚合窗口的 SLI 采集场景。ZGC 停顿时间通常在 10ms 以内,但需要更大的内存和更复杂的调优。

2.4 本章小结

  • Collector 的 Receiver → Processor → Exporter 管道架构决定了内存压力的分布:缓冲区、聚合窗口、发送队列是三大瓶颈点。
  • JVM 堆内存(新生代/老年代)和非堆内存(Metaspace/Direct Memory)需要分别监控和调优,不能只关注堆。
  • GC 调优的核心目标是控制 Minor GC 频率、晋升速率和 Full GC 停顿,推荐使用 G1 或 ZGC 回收器。