数据采集方案:从 Agent 到 eBPF

做全链路监控,第一个绕不开的问题就是:数据怎么采?

我入行头两年,市面上几乎全是 Agent 方案。后来微服务多了,容器化了,传统 Agent 开始吃力。再后来 eBPF 冒出来,有人形容它是“内核里的时光机”。这章咱们就掰开揉碎聊聊这三种主流方案。

1. 采集方案的三个核心矛盾

数据采集永远面临三个难题:

  • 全面 vs 性能 – 想多拿信息,就会吃掉 CPU 和内存
  • 实时 vs 侵入 – 埋点越深,对业务代码的干扰越大
  • 细粒度 vs 运维量 – 每台机器装 Agent 不难,难的是升级、配置、心跳

你在选方案时,其实就是在这三个矛盾之间找平衡点。我见过不少团队开始迷信“全自动无侵入”,结果到了生产环境才发现日志丢了关键的上下文。——嗯,后面会细说。

2. 基于 Agent 的主动式采集

Agent 是最早被大规模采用的模式。在每个业务节点上部署一个常驻进程,它负责收集指标、日志、调用链,然后推送到后端。

我个人的习惯:如果团队能管好 Agent 的版本和配置,优先用它。原因很简单 – 你可以精准控制采集什么、采多少、怎么采样。

常见的 Agent 包括:

  • Java Agent(如 SkyWalking、Pinpoint) – 通过字节码增强注入拦截点
  • PHP扩展 / Nginx Module – 直接嵌入运行时
  • Sidecar 模式(如 Istio 的 Envoy) – 每个 Pod 里放一个代理

举个栗子,我在一个电商项目中用 Java Agent 做全链路追踪。当时接口调用的响应时间分布不规律,加了 Agent 后发现是一个第三方 SDK 里有个同步锁住了。这种问题,没有 Agent 的字节码级追踪很难定位。

避坑指南:我曾经在一套老系统上直接升级 Agent 版本,结果新版拦截了某个不兼容的类加载器,导致服务启动失败。从那以后,Agent 的灰度上线和回滚方案一定先写进预案。

Agent 方案优缺点
优点缺点
控制精细,采样维度自由部署、升级、配置管理复杂
支持复杂场景(如数据库调用解析)对业务代码有侵入(字节码 / SDK)
性能开销相对可控(可调采样率)语言绑定,每个运行时需独立 Agent

3. 无侵入采集 – 旁路监听

无侵入,说白了就是不动你业务代码,也不开额外进程。常见的实现方式很“暴力”:

  • 流量劫持 / 端口镜像 – 在交换机或中间件层面复制流量
  • 日志文件异步读取 – tail -f 类的监听模式
  • 系统调用 hook – 通过 LD_PRELOAD 或内核模块

你想想看,无侵入最大的诱惑是什么?完全不需要业务配合。但代价也很明显:你拿不到业务语义。比如你知道 CPU 高了,但不知道是哪个用户请求造成的;你抓到了慢 SQL,但不知道对应的 trace ID。

我在项目中遇到过一个案例:用旁路抓包分析 HTTP 延迟,发现某路径每次都有 500ms 的空白期。最后排查是因为反向代理里有个 half-close 的 bug。无侵入能发现现象,但定位根因往往还需要 Agent 数据来补上下文。

无侵入最适合做基础设施层面的监控,比如网络丢包、TCP 重传、磁盘 IO 延迟。这些指标对业务无感知,但又直接影响全链路质量。

4. eBPF – 内核里的瑞士军刀

eBPF 这几年火得不行。它允许你在 Linux 内核里跑沙箱程序,安全、高效、几乎无侵入。全链路监控里 eBPF 能做什么?

  • 捕获所有系统调用 – 知道每个进程在干什么
  • 追踪 socket 操作 – 把网络包和进程、线程关联起来
  • 监控文件 IO / 信号量 – 无需改任何业务代码

一个我常用的 eBPF 例子:用 bcc 工具里的 tcpconnect 抓新连接,可以一秒查出是哪个进程在频繁建连。这在排查连接池泄漏时简直是神器。

# 一个简化版的 eBPF 程序片段 – 统计每个进程的 write 系统调用次数
#include <linux/bpf.h>
#include <bpf_helpers.h>

SEC("tracepoint/syscalls/sys_enter_write")
int count_write(struct trace_event_raw_sys_enter *ctx) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    // 这里可以写 map 计数逻辑
    return 0;
}

但是注意,eBPF 不是银弹。它只能看到内核态的视角,应用层的上层数据(如 HTTP Header、Trace ID)需要用户态配合才能关联。我踩过的一个坑:用 eBPF 抓网络包,发现延迟很高,但不知道是哪个业务接口。后来加了用户态 sidecar 把 span id 注入到 socket 的 cgroup,才打通了上下游。

警告:eBPF 对内核版本有强依赖。老内核(4.x 以下)基本不能用,新内核也有特性差异。如果你想大规模上 eBPF 做监控,最好统一内核版本,或使用 CO-RE(Compile Once – Run Everywhere)的方式。

5. 三种方案的选型对比

下面这张表是我自己做项目时的参考清单:

维度Agent无侵入eBPF
部署成本高(每节点部署+配置)中(依赖网络或日志基础设施)中(需内核环境,无用户态进程)
业务耦合高(需 SDK 或字节码)低极低(只看内核态)
数据语义丰富(trace ID、请求参数)薄(原始流量/包)中等(进程级、系统调用级)
性能开销适中(受采样率影响)中等(流量复制放大)极低(内核事件驱动)
适用场景调用链追踪、业务慢请求网络监控、大盘统计内核问题、安全审计、延迟分析

我的建议:三种方案完全可以共存。比如用 eBPF 监控网络和系统健康,用 Agent 做业务调用链,再搭配无侵入的旁路做流量验证。全链路溯源不是单选题,而是组合拳。

避坑指南(再次强调):我曾经在一个压测环境里同时启用 eBPF 监控和 Java Agent,结果 Agent 自己产生的网络活动也被 eBPF 采集到,造成数据重复。一定要规划好“采集边界”,避免自己打自己。

6. 一张图看清三种方案的定位

下面这张 SVGG 示意图,梳理了从数据源到采集方式的逻辑关系:

数据采集方案对比 应用进程 / 容器 操作系统内核 网络流量 / 中间件 Agent(字节码/SDK) 无侵入(旁路/Log) eBPF(内核钩子) 全链路数据总线 Trace / Log / Metric 不同来源可以对应不同采集方式,实际部署时常混合使用 例如 eBPF 看系统层,Agent 看业务层,无侵入做流量校验

图中左边是数据源头,中间是三种采集方式,右边汇聚到全链路数据总线。你可以看到,不同源头通常天然适合不同的采集方式。比如内核数据用 eBPF,应用层用 Agent,网络层用无侵入。

7. 一点实践心得

说了这么多理论,最后补个真实的感受。我负责过一条日均千万调用的电商链路监控。开始只上了 Java Agent,能看 trace,但出了网络抖动问题完全看不见底层。后来加了 eBPF 抓延迟高的 syscall,立马发现是某个 Pod 的 cgroup CPU 限制导致了调度延迟。

所以我的结论很明确:别纠结哪个方案最好,先搞清楚你的全链路里最痛的点在哪。如果频繁出现“查询慢但看不出 SQL 慢”的情况,大概率是网络/内核层在搞鬼,eBPF 上场。如果是接口超时但业务日志正常,Agent 的调用链是首选。至于无侵入,我最喜欢拿它来做监控对账——用旁路流量和 Agent 数据交叉验证,有没有遗漏的数据段一目了然。

建议你在小范围做一次“三种方案同跑对比”,用相同的场景(比如一个模拟慢请求)看看每种方案收集到的数据完整度、开销、以及问题定位耗时。这个实验做一次,团队就能统一选型共识了。

好了,这一章就到这。记住,数据采集不是终点,它只是通往问题溯源的第一座桥。桥稳了,后面的事才谈得上。