数据采集方案:从 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 的灰度上线和回滚方案一定先写进预案。
| 优点 | 缺点 |
|---|---|
| 控制精细,采样维度自由 | 部署、升级、配置管理复杂 |
| 支持复杂场景(如数据库调用解析) | 对业务代码有侵入(字节码 / 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 示意图,梳理了从数据源到采集方式的逻辑关系:
图中左边是数据源头,中间是三种采集方式,右边汇聚到全链路数据总线。你可以看到,不同源头通常天然适合不同的采集方式。比如内核数据用 eBPF,应用层用 Agent,网络层用无侵入。
7. 一点实践心得
说了这么多理论,最后补个真实的感受。我负责过一条日均千万调用的电商链路监控。开始只上了 Java Agent,能看 trace,但出了网络抖动问题完全看不见底层。后来加了 eBPF 抓延迟高的 syscall,立马发现是某个 Pod 的 cgroup CPU 限制导致了调度延迟。
所以我的结论很明确:别纠结哪个方案最好,先搞清楚你的全链路里最痛的点在哪。如果频繁出现“查询慢但看不出 SQL 慢”的情况,大概率是网络/内核层在搞鬼,eBPF 上场。如果是接口超时但业务日志正常,Agent 的调用链是首选。至于无侵入,我最喜欢拿它来做监控对账——用旁路流量和 Agent 数据交叉验证,有没有遗漏的数据段一目了然。
建议你在小范围做一次“三种方案同跑对比”,用相同的场景(比如一个模拟慢请求)看看每种方案收集到的数据完整度、开销、以及问题定位耗时。这个实验做一次,团队就能统一选型共识了。
好了,这一章就到这。记住,数据采集不是终点,它只是通往问题溯源的第一座桥。桥稳了,后面的事才谈得上。