落地 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 页面中,本文将结合 Agent Daily Build 的测试数据,系统性的阐述我们的测试方法和测试结果,揭示 Agent 的业务影响和资源开销,帮助大家扫清落地 eBPF 可观测性的最后障碍。

通过分析 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 流量。

详细的测试方法和测试数据见正文,详细的总结见文末章节。

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 处理流程以后,我们希望设计一组测试例,通过他们我们希望能评估:

  1. Agent 的运行对业务性能有什么样的影响?

    • 业务的 TPS(Transactions Per Second)降低了多少?
    • 业务的 RT(Response Time)升高了多少?
    • 业务的 CPU/MEM 消耗升高了多少?
  2. Agent 自身的处理性能如何?

    • 特定压力下 Agent 的 CPU/MEM 消耗如何?
    • Agent 采集应用数据的 RPS 能力如何?
    • Agent 采集网络数据的 CPS/BPS/PPS 能力如何?

测试方法和目的

首先,为了评估 Agent 对业务性能的影响,我们希望设计典型的业务场景来进行评估,并希望能覆盖到 Agent 处理流程中的所有重要环节。我们一共设计了三个场景,如下图所示:


(通过典型业务场景评估 Agent 对业务性能的影响)

场景 A - 典型云原生微服务:
DeepFlow 面向云原生场景,我们首先找到了 Istio Bookinfo Demo。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 系统和应用性能》中给出的参考数据中 uprobe 开销大约为 tracepoint 的 14-20 倍。虽然 DeepFlow 中大量的 Probe 都在使用最高性能的 tracepoint,并辅以 kprobe,但我们还是希望能有一个场景专注于对 uprobe 性能影响的评估,看看实际业务场景下 DeepFlow Agent 的表现。因此我们编写了一个 Golang Service 的 Demo,它对外提供一个 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 的流量生成器。这个生成器会在 tcp_client 和 tcp_server 两个进程之间构建超过 100K CPS 的活跃并发连接,用以压测 Agent 中的 flow_map 模块,构造高内存压力。

场景 E - 压测 BPS:我们使用 iperf3 构造超过 20Gbps 的大流量,用以评估 Agent 对大吞吐流量的处理性能。

场景 F - 压测 PPS:我们编写了一个 UDP Flood 流量生成器。这个生成器会从 udp_flood 进程发出超过 1Mpps 的流量,用以压测 Agent 对高频 Packet Data 的处理性能。

上述四个场景,我们均会注入最高的压力,使得 Agent 的 CPU/MEM 之一达到 1C/1GB 的高水位。

接下来,我们将会对所有上述六个场景的测试方法和结果进行详细的阐述,测试过程中 DeepFlow Agent 全部使用默认配置,没有进行任何调优。自动化测试流程每天都会执行本文介绍的测试例,并将测试结果与 Git Branch、CommitID 关联,以帮助开发者评估新功能对 Agent 的性能影响。

Agent 对业务的影响


(不同业务场景下 Agent 采集的 Span 数量)

场景 A - 典型云原生微服务 Istio Bookinfo:我们使用 wrk2 来注入稳定的 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/P90 RT 分别有约 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):

TPSBookinfo CPUP50 RTP90 RT
无 Agent30052.61%69.76ms87.81ms
有 Agent30052.85%80.06ms98.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):

TPSNginx CPUP50 RTP90 RTAgent CPU
无 Agent40,00056.50%1.02ms1.68ms--
有 Agent40,00083.10%1.15ms1.90ms86.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 的性能表现为:

  • 对 Nginx P50/P90 RT 的影响降低到 7.27%
  • deepflow-agent 自身 CPU 消耗降低到 66.5%

    • 采集 80K RPS HTTP 数据消耗 66.5%,加上 Nginx CPU 增长的 25.50% CPU,总消耗为 92%
    • 即 DeepFlow Agent 浅层解析时的处理能力为 Nginx 基线能力的 1.2 倍

浅层解析时的部分测试数据如下:

TPSNginx CPUP50 RTP90 RTAgent CPU
有 Agent(浅层解析)40,00082.00%1.10ms1.80ms66.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):

TPSNginx CPUP50 RTP90 RT
无 Agent14035.22%3.56ms3.95ms
有 Agent14044.80%4.18ms4.60ms
变化幅度0+ 9.58%+ 0.62ms+ 0.65ms
变化比例0+ 27.20%+ 17.42%+ 16.46%

测试过程中我们也对业务的内存开销进行了监控,但未观察到任何影响,考虑到 DeepFlow 从原理上来讲不会增加业务进程的内存开销,因此我们没有呈现内存消耗数据。

Agent 自身处理性能

在前一个章节的场景 A-C 中,我们同时也记录了 Agent 在高负载下的资源消耗,见下表(其中场景 A 的 CPU 消耗为整机百分比,共 8 核,其他场景为单核百分比):

场景业务 TPS业务基线 CPUSpan 采集速率Agent CPUAgent 内存
A - Istio30052.61%11,1003.40%46.76 MB
B - Nginx40,00056.50%80,00086.17%15.36 MB
B - Nginx(浅层解析)40,00056.50%80,00066.50%15.36 MB
C - uprobe14035.22%2,10010.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/sAgent CPUAgent CPU(浅层解析)Agent MEM
45,00090,00098%77%15MB
40,00080,00086%67%15MB
35,00070,00075%58%15MB
30,00060,00063%50%15MB

场景 D - 使用 TCP client/server 压测 CPS:我们使用 tcp_client 产生了 122K 个 IP-Port 五元组不同的、持续活跃的 Flow,新建连接速率约为 1K/s。此时 Agent 的内存消耗已达到 1GB,具体性能数据见下表。

Agent CPUAgent 内存流量 BPS流量 PPS流量 CPS
35.00%1079.77 MB23.43Mbps40.66Kpps121,771

场景 E - 使用 iperf3 压测 BPS:我们使用 iperf3 产生了 20Gbps 的流量。这个场景下我们并没有压到 Agent 的极限,20Gbps 已经是我们能在测试虚拟机中构造出的最大流量了。具体性能数据见下表。

Agent CPUAgent 内存流量 BPS流量 PPS
10.54%124.03 MB20.08Gbps114.51Kpps

场景 F - 使用 udp_flood 压测 PPS:我们使用 udp_flood 产生了 1.28Mpps 的流量。这个场景下将 Agent CPU 压到了单核的 95%。具体性能数据见下表。

Agent CPUAgent 内存流量 BPS流量 PPS
95.86%122.97M1.04Gbps1.28Mpps

总结

通过六个场景的全方位评测,我们对 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 倍
  • 对于需要使用 eBPF uprobe 的业务场景(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:采集 20Gbps TCP Flood 流量,Agent 仅消耗单核的 10% CPU,仅消耗 124MB 内存
    • PPS:采集 1.28Mpps UDP Flood 流量,Agent 消耗单核的 96% CPU

最后,本文所有测试数据(除浅层解析外)均是在 Agent 默认配置下测得的。实际业务环境中对于每一个调用,Agent 通常会在进程、Pod 网卡、Node 网卡三个位置采集到三个 Span,你可以按需关闭某些位置的数据采集,以获得更好的性能表现。理论上当仅采集其中一份数据时,你可以获得 3 倍于本文的性能表现。我们也期待社区小伙伴的更多评测。Enjoy DeepFlow!Enjoy Zero Code Observability!

什么是 DeepFlow

DeepFlow开源项目旨在为复杂的云原生应用提供深度可观测性。DeepFlow 基于 eBPF 实现了零插桩(Zero Code)、全覆盖(Full Stack)的指标、追踪、日志采集,并通过智能标签技术实现了所有观测数据的全关联(Universal Tagging)和高效存取。使用 DeepFlow,可以让云原生应用自动具有深度可观测性,从而消除开发者不断插桩的沉重负担,并为 DevOps/SRE 团队提供从代码到基础设施的监控及诊断能力。

GitHub 地址:https://github.com/deepflowio/deepflow

访问 DeepFlow Demo,体验零插桩、全覆盖、全关联的可观测性。

原文地址,请访问DeepFlow官方博客

作者:云杉网络原文地址:https://segmentfault.com/a/1190000043853678

%s 个评论

要回复文章请先登录注册