本内容是对知名性能评测博主 Anton Putra Node.js vs Go (Golang): Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准
在本篇内容中,我们将比较 Node.js 和 Golang。我会使用标准库在 Golang 中创建一个 Web 应用程序;而在 Node.js 中,我会使用非常常见的 Web 框架 ---
Express。
为了运行这些测试,我将两个应用程序都部署到了 AWS 上一个面向生产环境的 Kubernetes 集群中,并使用最新一代的 EC2 实例作为 Kubernetes 节点。
我们首先会测量 CPU 使用率、内存使用率、应用程序可用性、Kubernetes 的 CPU 限流情况以及网络压力。我们还会追踪每个应用程序接收和发送的字节数。这一数值可能会有很大差异,取决于请求头的数量以及应用程序是否启用了 KeepAlive(KeepAlive 允许一个 TCP 连接处理多个 HTTP 请求)。
第二个测试介绍
在第二个测试中,我引入了持久化层,因为大多数应用程序都需要以某种方式存储状态。例如,一个博客网站需要存储文章,一个电商网站需要存储库存,甚至一个待办事项列表也需要存储任务。
最常见的方法之一是使用数据库。在本次基准测试中,我们使用的是 PostgreSQL 关系型数据库。
除了前面提到的指标外,我们还会测量每个应用程序将数据插入数据库所需的延迟,并追踪连接池的状态以及每个应用程序如何扩展连接池。
作为一名 DevOps 工程师,我依赖开发者的输入来在生产环境中运行和优化应用程序。我欢迎任何改进应用的建议,甚至更好的是 Pull Request。你可以在视频描述中找到我 GitHub 仓库的链接。
第一个测试
好了,现在我来部署这两个应用程序到 Kubernetes。你会注意到,Node.js 使用的 CPU 稍微多一些,而内存使用基本相同。好了,现在开始第一次性能测试。

我使用了一个 Kubernetes Job 和 20 个副本来为每个应用程序生成负载。整个测试可能持续了三到四个小时,但我会将其压缩为几分钟展示。我从每个 Pod 启动一个客户端开始,然后不断增加客户端数量,直到两个应用程序都开始失败。
另外,每个阶段之间我设置了 60 秒的间隔,客户端超时时间设置为 1 秒。当达到超时阈值时,你会在可用性图表中看到下降。

从一开始你就可以注意到,Node.js 使用更多的 CPU 来处理请求,且延迟明显高于 Go。我认为它的性能与我在之前视频中测试过的 Python Django 框架类似。除此之外,Node.js 使用了更多的内存,它默认还会发送更多的头信息,这也是你会看到它传输的数据更多的原因。
当我们达到每秒 9,000 个请求时,Node.js 的 CPU 使用率达到 60%,并陷入卡顿状态。如果你知道 Node.js 出现这种情况的原因,或者以前遇到过这种问题,请告诉我。在接下来的测试中,Node.js 的 CPU 使用率将维持在 60% 左右,而请求的延迟会持续增加。我知道你可以使用 cluster 模式,但我个人认为直接增加副本数量对 Node.js 更合适。

由于我使用的是与之前测试完全相同的设置,我们知道 Golang 可以处理大约 70,000 到 80,000 个请求。由于 Golang 的标准库没有任何速率限制机制,它会持续缓存所有请求,直到内存使用率达到 100%,然后被 Kubernetes 的 OOM(内存溢出)机制杀死。
在测试结束时,Node.js 也开始性能下降,很多请求开始超时,你可以在可用性图表中看到这一点。顺便说一句,我在 Node.js 中使用了 async 函数,并设置了 NODE_ENV=production
。
测试结果:Golang 可以处理大约 70,000 个请求,而 Node.js 只能处理大约 9,000 个请求。
现在让我打开整个测试周期中的每个图表:
- 首先是每秒请求数图;

- 然后是 P99 客户端延迟图;

Node.js 开始变慢前的延迟图;

- 接下来是 CPU 使用图;

- 内存使用图;

- 可用性图;

- CPU 限流图;

- 最后是网络压力图。


好了,这就是第一个测试的全部内容。如果你有任何改进测试的建议,请告诉我。
第二个测试
现在我们进行第二个测试。在本测试中,我们向每个应用程序发送一个包含 JSON 负载的 POST 请求。应用程序会为设备生成一个 UUID,然后将其保存到数据库中。我为两个应用程序都设置了最大连接池大小为 20。
我使用一个开源的 Postgres Docker 镜像来运行一些数据库迁移操作。例如,我会创建一个用户,清除该用户的所有空闲连接,并创建一个表。然后我使用 init 容器在每次应用程序部署时运行这些迁移脚本。

现在开始测试。我为 Node.js 使用了最快的 Postgres 驱动之一,因此在测试初期,插入数据的延迟基本相同。整体延迟也非常接近。但当然,CPU 和内存使用差异很大。

你还可以注意到,连接池很快就达到了最大连接数 ---
每个应用程序 20 个连接。

当请求量达到每秒约 4,000 时,你会再次看到 Node.js 的 CPU 使用率达到 60%,并开始性能下降,延迟上升。看起来这就是 Node.js 在本测试中的最大处理能力。

继续测试,直到 Golang 开始失败。当请求量达到每秒约 7,000 时,你可以看到它开始卡顿,只有内存使用率继续上升。如果我们继续测试,它也会达到内存限制,并被 Kubernetes 杀死。

好了,现在让我打开整个测试周期中的每个图表:
- 首先是每秒请求数图;

- 客户端延迟图;


- 数据库插入延迟图;

- 连接池大小图;

- CPU 使用率图;

- 内存使用图;

- 可用性图;

- 最后是 CPU 限流图
