落地 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日 蓝鲸 x DeepFlow 可观测性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 处理流程

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 能力如何?

02 | 测试方法和目的

首先,为了评估 Agent 对业务性能的影响,我们希望设计典型的业务场景来进行评估,并希望能覆盖到 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 自身的处理性能

评估 Agent 自身的处理性能

场景 B - 压测 RPS:我们复用了 Nginx Demo,向他注入极限 RPS 压力,并评估 Agent 在消耗一个逻辑核的场景下能处理多大 RPS 的 HTTP 流量。

场景 D - 压测 CPS:我们编写了一个高并发 Flow 的流量生成器[5]。这个生成器会在 tcp_clienttcp_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 数量

不同业务场景下 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/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 零插桩分布式追踪 - 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 的性能表现为:

  • 对 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 倍

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

  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 倍

  • 对于需要使用 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!

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/

马斯克宣布 Twitter 将改名为 X,并更换 Logo 力不从心,React 核心开发者 Dan Abramov 宣布从 Meta 离职 关于 MyBatis-Flex 抄袭 MyBatis-Plus 的澄清 OpenAI 正式上线安卓版 ChatGPT ChatGPT for Android 将于下周上线,现在开始预注册 Arc 浏览器正式发布 1.0,声称是 Chrome 的替代品 马斯克“零元购”,强夺 @x 推特账号 VS Code 优化名称混淆压缩,将内置 JS 减小 20%! 新型高速 JavaScript 运行时 Bun 0.7 正式发布 谷歌薪资数据泄露,软件工程师基本年薪高达 71.8 万美元
{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/3681970/blog/9698278