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 频率。
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)监控。
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 回收器。