落地 eBPF 可观测性之 DeepFlow Agent 性能揭秘 精华
DeepFlow 基于 eBPF 实现了零插桩(Zero Code)的云原生应用可观测性,能够在不改代码、不改启动参数、不重启进程的前提下实现分布式追踪。这是一种全新的技术手段,因此不少用户在选型和落地 DeepFlow 的过程中会对它的性能开销存在疑问。到底 Agent 的运行会对业务造成什么样的影响?而 Agent 自身的资源开销又如何?这些问题我们在 SIGCOMM 2023 论文《Network-Centric Distributed Tracing with DeepFlow: Troubleshooting Your Microservices in Zero Code》中都有体系化的回答,论文将于九月份正式公开。在此之前,为了尽快帮助大家扫清落地 eBPF 可观测性的最后障碍,最近我们也将 DeepFlow Agent 的自动化测试结果放到了线上 Demo 页面中[1],本文将结合 Agent Daily Build 的测试数据,系统性的阐述我们的测试方法和测试结果,揭示 Agent 的业务影响和资源开销,帮助大家扫清落地 eBPF 可观测性的最后障碍。
欢迎报名 6 月 10 日 可观测性 Meetup 将首次向公众披露论文细节,并畅谈 DeepFlow 社区发展、用户案例、以及未来迭代计划。
通过分析 Agent 的处理流程,我们设计了六个场景进行全方位的评测。测试结果表明 Agent 对典型高负载生产业务的 TPS 无任何影响、CPU 增长仅 0.46%、平均单个调用的 RT 增长小于 1ms。Agent 对 HTTP 流量的处理性能不低于 Nginx,通过 eBPF uprobe 采集 Golang 协程信息的性能为 Pixie 方案的 2.5 倍。Agent 在 1 核 1GB 内存限制下可采集 90K RPS HTTP 流量,或 122K CPS 并发的 TCP 流量,或 20+Gbps(未到极限) TCP Flood 流量,或 1.28Mpps UDP Flood 流量。详细的测试方法和测试数据见正文,详细的总结见文末章节。
01 | Agent 处理流程
为了确定性能测试的方法,我们首先需要对 Agent 的处理流程有一定了解,做到有的放矢。如下图所示,Agent 主要从三个 eBPF 接口获取数据,自下而上依次是:
- 通过 AF_PACKET 结合 BPF 过滤程序,获取应用的网络包数据,用于采集 NET Span
- 通过 kprobe 和 tracepoint 接口,获取应用的系统调用数据,用于采集 SYS Span,以及每个调用生命周期内的慢文件 IO 事件
- 通过 uprobe 接口,获取应用程序的函数调用数据,用于实现 Golang 等协程语言的零插桩分布式追踪,也用于采集 HTTP2/HTTPS 协议的 SYS Span
DeepFlow Agent 处理流程
当获取到 Packet/Socket/Function 数据之后,DeepFlow Agent 需要解析数据中的应用协议,获取 Span,并关联、聚合形成 Trace、Metrics、Logs 数据。特别的,对于 Packet 数据,Agent 还需要基于包聚合生成 TCP/UDP Flow,用于生成流日志,并计算网络层的吞吐、时延、异常等性能指标。
理解 Agent 处理流程以后,我们希望设计一组测试例,通过他们我们希望能评估:
- Agent 的运行对业务性能有什么样的影响?
- 业务的 TPS(Transactions Per Second)降低了多少?
- 业务的 RT(Response Time)升高了多少?
- 业务的 CPU/MEM 消耗升高了多少?
- Agent 自身的处理性能如何?
- 特定压力下 Agent 的 CPU/MEM 消耗如何?
- Agent 采集应用数据的 RPS 能力如何?
- Agent 采集网络数据的 CPS/BPS/PPS 能力如何?
02 | 测试方法和目的
首先,为了评估 Agent 对业务性能的影响,我们希望设计典型的业务场景来进行评估,并希望能覆盖到 Agent 处理流程中的所有重要环节。我们一共设计了三个场景,如下图所示:
通过典型业务场景评估 Agent 对业务性能的影响
场景 A - 典型云原生微服务:DeepFlow 面向云原生场景,我们首先找到了 Istio Bookinfo Demo[2]。Istio 是一种流行的服务网格解决方案,在 GitHub 上拥有 32.9K Star。这个 Demo 的应用拓扑见上图,我们可以看到它由 Python、Java、Ruby、Node.JS 实现的四个微服务组成,每个微服务所在的 Pod 中运行着 Envoy 代理。这个 Demo 中的一个事务对应着访问 4 个微服务的 4 个调用。特别值得我们关注的是,由于 Envoy 的存在实际调用链深度会被拉长两倍。
场景 B - 极高性能极简业务:除了常规业务微服务以外,DeepFlow 还能采集并追踪基础设施服务。因此我们计划测试一个极致高性能、极简业务逻辑的中间件服务。我们选择了 Nginx,我们知道它以性能强悍著称,它用 C 语言实现,而且我们在测试中让他只是简单的回复一个默认静态页。我们相信这个 Nginx Demo 自身的性能表现远超过任何一个实际的生产环境,我们希望使用这个 Demo 来说明 DeepFlow Agent 的采集对极端高性能的中间件影响如何。
场景 C - 用 uprobe 追踪 Golang:我们知道 eBPF 中 uprobe 的性能要明显低于 kprobe/tracepoint,Brendan Gregg 在他的著作《BPF 之巅 - 洞悉 Linux 系统和应用性能》[3]中给出的参考数据中 uprobe 开销大约为 tracepoint 的 14 倍(DeepFlow 没有使用 uretprobe,它的开销是 tracepoint 的 20 倍)。虽然 DeepFlow 中大量的 Probe 都在使用最高性能的 tracepoint,并辅以 kprobe,但我们还是希望能有一个场景专注于对 uprobe 性能影响的评估,看看实际业务场景下 DeepFlow Agent 的表现。因此我们编写了一个 Golang Service 的 Demo[4],它对外提供一个 API,并会在 API 实现逻辑中调用上游的 Redis、MySQL、Nginx 三个服务。DeepFlow Agent 使用 uprobe 来 Hook Golang 服务的 runtime.execute 函数调用,用以跟踪协程的创建,因此这个服务每次响应 API 的 4 个调用都会触发一次 Hook。
所有上述三个场景,我们均会分别测试停用 deepflow-agent
(基线)、运行 deepflow-agent
两种情况,通过对比得出 Agent 对业务性能的影响。另外,我们也会注入不同 TPS 的压力,直至达到业务极限处理能力,以评估不同压力下的影响是否存在差异。
另一方面,对于 Agent 自身处理性能的评估,我们会记录场景 A-C 中 deepflow-agent 进程的 CPU/MEM 开销。除此之外,我们也希望设计一些更极端的场景,用来评估 Agent 在资源受限情况下的 RPS/CPS/BPS/PPS 极限处理能力。
评估 Agent 自身的处理性能
场景 B - 压测 RPS:我们复用了 Nginx Demo,向他注入极限 RPS 压力,并评估 Agent 在消耗一个逻辑核的场景下能处理多大 RPS 的 HTTP 流量。
场景 D - 压测 CPS:我们编写了一个高并发 Flow 的流量生成器[5]。这个生成器会在 tcp_client
和 tcp_server
两个进程之间构建超过 100K CPS 的活跃并发连接,用以压测 Agent 中的 flow_map 模块,构造高内存压力。
场景 E - 压测 BPS:我们使用 iperf3
构造超过 20Gbps 的大流量,用以评估 Agent 对大吞吐流量的处理性能。
场景 F - 压测 PPS:我们编写了一个 UDP Flood 流量生成器[6]。这个生成器会从 udp_flood
进程发出超过 1Mpps 的流量,用以压测 Agent 对高频 Packet Data 的处理性能。
上述四个场景,我们均会注入最高的压力,使得 Agent 的 CPU/MEM 之一达到 1C/1GB 的高水位。
接下来,我们将会对所有上述六个场景的测试方法和结果进行详细的阐述,测试过程中 DeepFlow Agent 全部使用默认配置,没有进行任何调优。自动化测试流程每天都会执行本文介绍的测试例,并将测试结果与 Git Branch、CommitID 关联,以帮助开发者评估新功能对 Agent 的性能影响。
03 | Agent 对业务的影响
不同业务场景下 Agent 采集的 Span 数量
场景 A - 典型云原生微服务 Istio Bookinfo:我们使用 wrk2[7] 来注入稳定的 TPS 负载,wrk2 会直接请求 Productpage 服务。所有的服务(包括 wrk2)部署在一个 8C16GB 的 K8s 节点上(CentOS 7、Kernel 4.19),我们在该节点上部署 deepflow-agent Daemonset 来对所有调用进行采集,测试过程中限制了 deepflow-agent 资源消耗为 1C768MB。
基线场景下我们为 Bookinfo 注入了消耗整机 52.61% CPU(相当于 4.2 个逻辑核)的高负载请求,可以看到这是一个非常繁忙的业务场景,为了达到该负载我们甚至特意增加了其中两个瓶颈服务的副本数:将 Productpage 调整为 4 副本、将 Details 调整为 2 副本。在这样的高负载下:
- 运行 deepflow-agent 前后,TPS 没有任何影响,均为 300/s
- 运行 deepflow-agent 前后,CPU 仅有 0.46% 的增长,几乎感知不到,可以认为是统计误差
- P50/P90RT 分别有约 10ms 的增长,增长比例约为 12%
- 对于每个事务,DeepFlow 采集了 37 个 Span,因此 300 TPS 负载下的Span 采集速率为 11,100/s
- 每个事务包括 26 个 SYS Span(eBPF)和 11 个 NET Span(cBPF)
- 每个事物共采集了 13 个 Server-side SYS Span,因此平均每个调用仅引入了 0.8ms 的 RT 增长
DeepFlow 通过 eBPF 采集这些 Span,基于创新的关联算法实现了零插桩的分布式追踪,Span 之间的关联信息也是由 Agent 实时计算得到的:
DeepFlow 零插桩分布式追踪 - Istio Bookinfo
该场景下的部分测试数据如下(更多测试数据请查看 DeepFlow 在线 Demo 中的 Grafana Dashboard):
TPS | Bookinfo CPU | P50 RT | P90 RT | |
无 Agent | 300 | 52.61% | 69.76ms | 87.81ms |
有 Agent | 300 | 52.85% | 80.06ms | 98.56ms |
变化幅度 | 0 | + 0.24% | + 10.30ms | + 10.75ms |
变化比例 | 0 | + 0.46% | + 14.76% | + 12.24% |
场景 B - 极高性能极简业务 Nginx:我们依然使用 wrk2 来注入稳定的 TPS 负载,wrk2 会直接请求 Nginx 提供的 Default Page 服务。为了减少其他业务的干扰,我们将 Nginx 和 wrk2 部署在两个单独的虚拟机上(8C16GB、CentOS 7、Kernel 4.19),将 Nginx 的 worker 数量固定为 1 个。我们在 Nginx 所在虚拟机上部署了 deepflow-agent,测试过程中限制了 deepflow-agent 资源消耗为 1C768MB。
基线场景下我们为 Nginx 注入了消耗单核 56.50% CPU 的高负载请求,可以看到这是一个非常繁忙的业务场景。在这样的极端高性能和高负载下:
- 运行 deepflow-agent 前后,TPS 没有任何影响,均为 40,000/s
- 运行 deepflow-agent 前后,Nginx CPU 增长了 26.60%,另外 deepflow-agent 消耗了 86.17% CPU
- 每次调用,deepflow-agent 会采集两个 Span,即Span 采集速率为 80,000/s
- 这意味着 deepflow-agent 处理两倍调用消耗的 CPU = 86.17+26.6 = 112.77 = 2 倍 Nginx 基线状态下的 CPU 消耗
- 也就是说,deepflow-agent 的处理性能与 Nginx 相同
- P50/P90 RT 分别仅有 0.13ms/0.22ms 的增长,增长比例约为 12%
该场景下的部分测试数据如下(更多测试数据请查看 DeepFlow 在线 Demo 中的 Grafana Dashboard):
TPS | Nginx CPU | P50 RT | P90 RT | Agent CPU | |
无 Agent | 40,000 | 56.50% | 1.02ms | 1.68ms | -- |
有 Agent | 40,000 | 83.10% | 1.15ms | 1.90ms | 86.17% |
变化幅度 | 0 | + 26.60% | + 0.13ms | + 0.22ms | -- |
变化比例 | 0 | + 47.08% | + 12.75% | + 13.10% | -- |
上述测试结果是在 Agent 默认配置下测得的。实际上由于 Agent 在全栈路径上不同位置采集到的 Span 中 request_domain、request_resource、response_result 等字段不会有变化,因此我们可以开启 Agent 的浅层解析采集模式,针对 HTTP 流量仅解析首行判断协议类型和响应码。所有调用日志均开启浅层解析后,Agent 的性能表现为:
- 对 NginxP50/P90 RT 的影响降低到 7.27%
- deepflow-agent自身 CPU 消耗降低到 66.5%
- 采集 80K RPS HTTP 数据消耗 66.5%,加上 Nginx CPU 增长的 25.50% CPU,总消耗为 92%
- 即 DeepFlowAgent 浅层解析时的处理能力为 Nginx 基线能力的 1.2 倍
浅层解析时的部分测试数据如下:
TPS | Nginx CPU | P50 RT | P90 RT | Agent CPU | |
有 Agent(浅层解析) | 40,000 | 82.00% | 1.10ms | 1.80ms | 66.5% |
变化幅度 | 0 | + 25.50% | + 0.08ms | + 0.12ms | -- |
变化比例 | 0 | + 45.13% | + 7.27% | + 7.27% | -- |
场景 C - 用 uprobe 追踪 Golang:我们使用 wrk2 来注入稳定的 TPS 负载,wrk2 会直接请求 go-server 提供的服务。我们将 wrk2 和所有服务部署在一个虚拟机上(8C16GB、CentOS 7、Kernel 4.19),除了 wrk2 以外所有服务均使用 docker-compose 部署在 container 中。同样这台虚拟机上也部署了 deepflow-agent。测试过程中限制了 deepflow-agent 资源消耗为 1C768MB。
DeepFlow 中通过 eBPF uprobe 跟踪 Golang 协程的创建来实现零插桩的分布式追踪。为了无差异的让 uprobe 覆盖到每一个调用(HTTP、MySQL、Redis),本 Demo 中我们没有注入 HTTP2/HTTPS 流量。
基线场景下我们为 go-server 注入了消耗单核 35.22% CPU 的请求负载。在这样的负载下:
- 运行 deepflow-agent 前后,TPS 没有任何影响,均为 140/s
- 每个事务采集了 15 个 Span,因此 140 TPS 负载下的Span 采集速率为 2100/s
- 相比其他场景,运行 deepflow-agent 后 CPU 和 RT 增长略大
- 运行 deepflow-agent 前后,业务 CPU 增长了 9.58%,增长比例为 27.20%
- 运行 deepflow-agent 前后,业务 P50/P90 RT 分别有约 0.6ms 的增长,增长比例约为 17%
我们看到 uprobe 的性能开销确实要略高于前两个场景(仅使用了 tracepoint/kprobe/AF_PACKET)。实际上每一次 uprobe Hook 触发会引起两次用户态和内核态间的上下文切换,每个事务中我们 Hook 的 golang 函数触发了 4 次,因此会导致每个事务中增加 8 次上下文切换。
但值得提到的是,DeepFlow Hook 的 Golang 函数是经过深思熟虑的。我们使用 runtime.execute
来跟踪协程和线程之间的关系,这是我们目前能找到的性能影响最低的途经。作为对比 Pixie Hook 了 runtime.casgstatus 函数(未用于分布式追踪,仅用来获取协程 ID),它的调用频率会明显高于 runtime.execute。在对比测试中我们发现,如果使用 Pixie 的方案,在当前场景下将会造成业务服务高达 67% 的 CPU 增长、高达 45% 的 RT 增长,对业务的影响是 DeepFlow 的 2.5 倍。
该场景下的部分测试数据如下(更多测试数据请查看 DeepFlow 在线 Demo 中的 Grafana Dashboard):
TPS | Go-server CPU | P50 RT | P90 RT | |
无 Agent | 140 | 35.22% | 3.56ms | 3.95ms |
有 Agent | 140 | 44.80% | 4.18ms | 4.60ms |
变化幅度 | 0 | + 9.58% | + 0.62ms | + 0.65ms |
变化比例 | 0 | + 27.20% | + 17.42% | + 16.46% |
测试过程中我们也对业务的内存开销进行了监控,但未观察到任何影响,考虑到 DeepFlow 从原理上来讲不会增加业务进程的内存开销,因此我们没有呈现内存消耗数据。
04 | Agent 自身处理性能
在前一个章节的场景 A-C 中,我们同时也记录了 Agent 在高负载下的资源消耗,见下表(其中场景 A 的 CPU 消耗为整机百分比,共 8 核,其他场景为单核百分比):
场景 | 业务 TPS | 业务基线 CPU | Span 采集速率 | Agent CPU | Agent 内存 |
A - Istio | 300 | 52.61% | 11,100 | 3.40% | 46.76 MB |
B - Nginx | 40,000 | 56.50% | 80,000 | 86.17% | 15.36 MB |
B - Nginx(浅层解析) | 40,000 | 56.50% | 80,000 | 66.50% | 15.36 MB |
C - uprobe | 140 | 35.22% | 2,100 | 10.20% | 43.26 MB |
从上表可以看到:
- 场景 A:Agent 自身的 CPU 消耗仅为整机的 3.4%(相当于单核的 27%),相比业务消耗微乎其微
- 场景 B:Agent 运行引发的额外 CPU 消耗与 Nginx 的基线 CPU 消耗相当(归一化至处理同样 RPS 数据之后),浅层解析下 Agent 的处理能力是 Nginx 的 1.2 倍
- 场景 C:Agent 自身的 CPU 消耗仅为单核的 10.20%
- 各个场景下的内存开销极低
除了评估 Agent 在业务高负载情况下的资源消耗,我们也通过如下四个场景来评估 Agent 在 1C1G 资源限制下的极限处理能力。
场景 B - 使用 Nginx 压测 RPS:我们通过 wrk2 注入了极端的 45K/s TPS 压力,此时 Nginx 的基线 CPU 消耗已高达 62.39%,可以看到这是一个非常极端的场景。在这样的场景下我们得到了 Agent 在 1C 限制下的 HTTP 流量极限采集能力 —— 90K/s。
该场景下的部分测试数据如下(更多测试数据请查看 DeepFlow 在线 Demo 中的 Grafana Dashboard):
TPS | 采集 Span/s | Agent CPU | Agent CPU(浅层解析) | Agent MEM |
45,000 | 90,000 | 98% | 77% | 15MB |
40,000 | 80,000 | 86% | 67% | 15MB |
35,000 | 70,000 | 75% | 58% | 15MB |
30,000 | 60,000 | 63% | 50% | 15MB |
场景 D - 使用 TCP client/server 压测 CPS:我们使用 tcp_client 产生了 122K 个 IP-Port 五元组不同的、持续活跃的 Flow,新建连接速率约为 1K/s。此时 Agent 的内存消耗已达到 1GB,具体性能数据见下表。
Agent CPU | Agent 内存 | 流量 BPS | 流量 PPS | 流量 CPS |
35.00% | 1079.77 MB | 23.43Mbps | 40.66Kpps | 121,771 |
场景 E - 使用 iperf3 压测 BPS:我们使用 iperf3 产生了 20Gbps 的流量。这个场景下我们并没有压到 Agent 的极限,20Gbps 已经是我们能在测试虚拟机中构造出的最大流量了。具体性能数据见下表。
Agent CPU | Agent 内存 | 流量 BPS | 流量 PPS |
10.54% | 124.03M | 20.08Gbps | 114.51Kpps |
场景 F - 使用 udp_flood 压测 PPS:我们使用 udp_flood 产生了 1.28Mpps 的流量。这个场景下将 Agent CPU 压到了单核的 95%。具体性能数据见下表。
Agent CPU | Agent 内存 | 流量 BPS | 流量 PPS |
95.86% | 122.97M | 1.04Gbps | 1.28Mpps |
05 | 总结
通过六个场景的全方位评测,我们对 Agent 的性能有了完整的了解,希望能够帮助大家尽快落地基于 eBPF 的可观测性。简要结论总结如下:
- 对于一个典型的微服务架构的云原生业务,在注入整机 52% 高负载的压力下:
- Agent 对业务TPS 没有任何影响
- Agent 使得业务 P50 RT 仅增加了 10.30ms(+14.76%),平均每个调用的 RT 仅增加 0.8ms
- Agent 使得业务CPU 仅增长 0.24%(+0.46%),几乎感知不到
- Agent自身仅消耗 3.4% CPU、47 MB 内存
- 对于一个极致性能的极简业务(Nginx Default Page),在注入单核 56% 高负载的压力下:
- Agent 对业务TPS 没有任何影响
- Agent 使得业务P50 RT 仅增加了 0.13ms(+12.75%)
- 浅层解析时,业务P50 RT 仅增加 0.08ms (+7.27%)
- Agent 的处理性能等于 Nginx,处理等量 HTTP 调用的资源开销与 Nginx 相同
- 浅层解析时,Agent 处理性能可高达 Nginx 的 1.2 倍
- 对于需要使用 eBPFuprobe的业务场景(Golang 协程跟踪),在注入单核 35% 负载的压力下:
- Agent 对业务TPS 没有任何影响
- Agent 使得业务P50 RT 仅增加了 0.62ms(+17.42%)
- Agent 使得业务CPU 增长了 9.58%(+27.20%),主要由 uprobe 的内核态-用户态上下文切换引入
- 对比:此场景下DeepFlow 的性能为 Pixie 方案的 2.5 倍
- 在资源受限为 1核 CPU 1GB 内存的情况下,Agent 的极限处理性能如下
- RPS:HTTP 采集性能90K RPS,此时 Agent 消耗单核的98% CPU
- CPS:采集并发 122K CPS、新建 1K CPS 的 TCP Flood 流量,Agent 消耗1GB 内存
- BPS:采集20GbpsTCP Flood 流量,Agent 仅消耗单核的 10% CPU,仅消耗 124MB 内存
- PPS:采集1.28MppsUDP Flood 流量,Agent 消耗单核的96% CPU
最后,本文所有测试数据(除浅层解析外)均是在 Agent 默认配置下测得的。实际业务环境中对于每一个调用,Agent 通常会在进程、Pod 网卡、Node 网卡三个位置采集到三个 Span,你可以按需关闭某些位置的数据采集,以获得更好的性能表现。理论上当仅采集其中一份数据时,你可以获得 3 倍于本文的性能表现。我们也期待社区小伙伴的更多评测。Enjoy DeepFlow!Enjoy Zero Code Observability!
06 | 什么是 DeepFlow
DeepFlow[8] 开源项目旨在为复杂的云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了零插桩(Zero Code)、全覆盖(Full Stack)的指标、追踪、日志采集,并通过智能标签技术实现了所有观测数据的全关联(Universal Tagging)和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。
GitHub 地址:https://github.com/deepflowio/deepflow
访问 DeepFlow Demo[9],体验零插桩、全覆盖、全关联的可观测性。
参考资料
[1] 线上 Demo 页面中: https://ce-demo.deepflow.yunshan.net/d/Agent_Performance_Analysis/agent-performance-analysis
[2] Istio Bookinfo Demo: https://istio.io/latest/docs/examples/bookinfo/#deploying-the-application
[3] Brendan Gregg 在他的著作《BPF 之巅 - 洞悉 Linux 系统和应用性能》: https://www.brendangregg.com/bpf-performance-tools-book.html
[4] Golang Service 的 Demo: https://github.com/nrjatyunshan/go-server-sample
[5] 高并发 Flow 的流量生成器: https://github.com/deepflowio/traffic-generators/tree/main/1-high-connection
[6] UDP Flood 流量生成器: https://github.com/deepflowio/traffic-generators/tree/main/2-udp-flood
[7] wrk2: https://github.com/giltene/wrk2
[8] DeepFlow: https://github.com/deepflowio/deepflow
[9] DeepFlow Demo: https://deepflow.io/docs/zh/install/overview/
前排了解一下
不错,看得出性能很优秀