本内容是对知名性能评测博主 Anton Putra Elixir vs Go (Golang) Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准
对比 Elixir 和 Go
简介
许多人长期以来一直要求我对比 Elixir 和 Go。在本视频中,我将 Elixir 与 Go 标准库 进行对比。我甚至收到了一个包含 Elixir 实现 的 Pull Request 。因此,如果你希望我对比你喜欢的框架,可以提交 PR。
在第一个测试中,我们将从 客户端角度 测量 99% 百分位数的延迟(P99 Latency),并通过计算 每秒请求数(Requests per Second, RPS) 来衡量 吞吐量(Throughput)。此外,我们还会测量 Kubernetes 容器的 CPU 使用率和内存使用率,以及 应用的可用性(Availability)或错误率(Error Rate)。由于我们在 Kubernetes 中运行这两个应用程序,因此还需要测量 CPU 限流(CPU Throttling)。
第二个测试更加 真实,也更加 有趣。这是我首次使用 足够大的 EC2 实例 运行数据库,以确保数据库不会成为瓶颈。我稍后会解释设计思路。我们会使用 Prometheus 进行监控,测量 每个应用程序插入新记录所需的时间,以及 整体 POST 请求的持续时间。
此外,我们还会测量 数据库的 CPU 使用率,以及 每个应用创建的数据库连接池大小。
测试环境
我使用 AWS 来运行所有基准测试。应用程序运行在 m7a.large 实例 上。同时,我使用 4xlarge Graviton 实例 部署 Prometheus、Grafana 等监控组件,并用于运行 客户端负载生成器。在第二个测试中,我使用 2xlarge 实例 运行数据库。
这些 EC2 实例 的最大优势是 磁盘使用不需要额外付费,费用已包含在 EC2 价格 中。这类实例自带 两个 SSD 磁盘,总计 2.5TB 存储,非常适合 自管理分布式数据库,比如 Cassandra、Kafka,甚至 PostgreSQL。
为了 支持我的频道并支付基础设施费用,我提供 一对一咨询服务,详情请查看 视频描述。
测试设计
第一轮测试
在第一个测试中,我使用 Terraform 从零开始创建 VPC 和所有网络组件,并配置了 EKS 集群,其中包含 两个实例组(Instance Groups):
-
第一个实例组:
- 包含 2 个 m7a.large 节点(每个 2 核 CPU,8GB 内存)。
- 仅部署应用程序(每个节点运行一个应用)。
- 设置 资源限制 以匹配 VM 容量,确保 无干扰(No Noisy Neighbors)。
-
第二个实例组:
- 基于 Graviton,用于部署 Prometheus、Grafana 和 负载生成客户端(使用 Kubernetes Jobs 运行)。
应用程序接收到 GET 请求 后,会返回 硬编码的设备信息 给客户端。
第二轮测试
在第二个测试中,我创建了一个 2xlarge 实例 运行数据库,配置如下:
- 8 核 CPU,64GB 内存。
- 使用 pgTune 优化 PostgreSQL 配置。
- 将 最大连接数 设置为 1050(较高,但为了测试需要)。
应用在接收到请求后,会执行以下操作:
- 解析 JSON 负载;
- 生成 UUID 和时间戳;
- 将 记录插入数据库;
- 返回 PostgreSQL 生成的 ID。
第一轮测试
开始测试
让我们开始 第一轮测试。首先,我们关注 最重要的指标---
延迟(Latency),因为它直接影响 用户体验。在本测试中,我们使用 99% 百分位数(P99),表示 99% 的请求完成时间在该范围内,数值越低越好。
Grafana 以 微秒(μs) 显示数据,当数值达到 1,000μs 时,自动转换为毫秒(ms)。
从一开始,Go 的延迟表现明显优于 Elixir,并且这种趋势一直持续到测试结束。
吞吐量(Throughput)
在右侧,我们可以看到 吞吐量(请求数/秒)。吞吐量影响 应用所需的资源量,进而影响 基础设施成本。如果你预算有限,你会更倾向于选择 在相同计算资源下能处理更多请求的应用。
CPU 和内存使用
我们还测量了 Kubernetes 容器的 CPU 和内存使用,结果显示 Go 在这两个方面都表现更好。
当请求量达到 18,000 次/秒 时,Elixir 无法再处理更多请求,开始 落后于 Go。
CPU 限流(Throttling)
在 Kubernetes 中,CPU 限流 是影响应用性能的重要因素。在测试进行几分钟后,Elixir 的可用性下降,表明 许多请求超时或返回 500 错误。
Elixir 在本次测试中的极限远低于 Go。我认为 Elixir 这个框架 更适合与 Python Django 或 Ruby on Rails 进行对比,其性能类别相似,可能稍好一些。
让我们继续运行测试,直到 Go 也达到极限。
测试结束
Go 最高达到了 60,000 请求/秒,与其他基准测试结果相近。在此测试中,我使用了 性能更优的第三方 JSON 解析库,进一步提升了 Go 应用的整体表现。
Go 应用在无法处理更多请求时,会出现内存峰值,这是预期行为。
完整测试数据
请求数/秒
- Elixir:18,000
- Go:60,000
延迟(Latency)
- Go 显著优于 Elixir。
CPU 使用
- Elixir 可能使用了更多中间件,或某些库不如 Go 高效。
可用性(Availability)
- Elixir 可用性下降,更多请求返回 4xx 和 5xx 状态码。
内存使用
- 本次测试采用实际内存使用量(字节)而非百分比。
CPU 限流
- 超过 pod 资源限制时,cgroups 会对容器进行限流,导致性能下降。
第二轮测试
测试开始
在第二轮测试中,我使用了 更大的数据库 VM,确保 数据库不会成为瓶颈。通常,像 Redis、数据库或其他微服务 可能会成为瓶颈,因此需要 监控整个系统。
在这个测试中,我们额外测量了:
- 数据库插入延迟
- 整个 POST 请求的延迟
- PostgreSQL CPU 使用率
- 数据库连接池大小
连接池
我们在两种应用中都限制 最大连接数为 500。不同之处在于:
- Elixir 在启动时立即创建 500 个连接。
- Go 根据负载动态增加连接,直至达到 500。
测试结果
当请求量达到 9,000 次/秒 时,Elixir 的 CPU 使用率接近饱和,无法再处理更多请求。而 Go 应用的 CPU 仍有余量,最终吞吐量达到 25,000 次/秒,比 Elixir 高出一倍多。
总结
如果你可以改进 Elixir 或 Go 的实现,请提交 Pull Request,我会重新运行测试并分享优化结果。
我的频道还有其他 基准测试(Benchmark),你可能会感兴趣。感谢观看,我们下期见!