本内容是对知名性能评测博主 Anton Putra Go (Golang) vs Java: Performance Benchmark vs Java: Performance Benchmark") 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准
在本视频中,我们将比较 Go 和 Java。
我们将基于 Golang 的 Fiber 框架和 Java 的 Spring Boot 创建几个简单的应用程序,并使用 Prometheus 将它们部署到 Kubernetes。我们将从 Nginx Ingress 控制器收集延迟和流量信息。
为了收集基本的容器指标,例如 CPU 使用率,我们将部署 cAdvisor 作为一个守护进程集(DaemonSet)。您可以监控 CPU 使用率,既可以看作是分配给容器限制的百分比,也可以是实际使用的 CPU 核心数,并将请求(requests)和限制(limits)以线的形式绘制出来。在 Kubernetes 中,由于使用了 cgroups,测量 CPU 节流(throttling)至关重要,因为它会极大地影响应用程序的性能。
同样的情况也适用于内存。我们可以将其可视化为百分比,或者使用实际使用量,并标注请求(requests)和限制(limits)。监控 Nginx Ingress Pod 的 CPU 使用率也至关重要,至少需要关注这一点,因为它可能成为您应用程序的瓶颈,并显著增加延迟。
我们还将使用原生的 Prometheus 客户端来检测(instrument)我们应用程序的部分代码。例如,在这个仪表板中,我们将测量 S3 调用的请求持续时间和 MongoDB 插入操作的耗时。
由于我们将使用一个基于开源 MinIO 项目的自托管 S3 解决方案,我认为用 Prometheus 对其进行监控也是一个好主意。我们还将在 Kubernetes 中部署 MongoDB,并使用 Prometheus exporter 对其进行监控。
这些技术不仅对基准测试有用,对于日常运维也很有帮助。您可以在我的 GitHub 仓库中找到源代码。
首先,我们将使用 Terraform 创建 VPC 和 EKS。然后,为了在 VPC 内暴露我们的应用程序,我们将使用 Helm chart 和 Terraform 部署一个私有的 Nginx Ingress 控制器。接着,将 Go 语言和 Java 应用程序部署到 Kubernetes。
对于第一个测试,我们简单地使用 k6 负载测试工具来并排比较 Fiber 和 Spring Boot 应用程序。
对于第二个更贴近现实的测试,在每次请求中,我们将从 S3 存储桶下载一张图片,并将最后修改日期保存到 MongoDB 数据库。
现在,让我们来看一下代码。首先,您可以在 AWS 环境中找到用于创建所有网络组件和 EKS 的 Terraform 代码。然后,我们有 Prometheus 和其他监控组件。例如,MinIO 能够开箱即用地生成 Prometheus 指标。而要监控 MongoDB,我们需要单独部署 Prometheus exporter。
对于第一个测试,我们只需向客户端返回 10 个设备信息。Java 的实现也一样。在这里,我额外加入了一个 Prometheus 计数器变量,以防您想统计这个端点被调用了多少次。
对于第二个测试,在 Go 语言中,我们有一个 getImage
Fiber 处理器(Handler),它使用 download
函数从 S3 存储桶拉取 S3 图片,并使用 save
函数将最后修改日期插入到 MongoDB。为了共享 S3 客户端和 MongoDB 连接池,我们创建了一个自定义的处理器,并添加了 session
和 client
属性。然后,在初始化处理器时,我们调用辅助方法来建立与 S3 和 MongoDB 的连接。您不必共享 S3 客户端,但每次访问 S3 时,它都会重新进行身份验证,这也会消耗时间。
谈到 Prometheus 指标,首先,我们需要在一个结构体(struct)中声明它们。然后创建一个 newMetrics
函数来初始化它们。在这个测试中,我使用了 summary
类型,这对于单个副本来说是没问题的,而且您不必预先设定时间间隔桶(interval buckets)。但是,如果您计划水平扩展此应用程序,您将无法聚合 summary
类型;请改用 histogram
类型来记录观测值。您可以这样做来记录观测值:简单地在函数调用之前和之后记录时间,或者,您当然可以使用中间件模式来包装这个函数,这取决于您。
对于 Java,您遵循相同的原则。声明指标,在这个例子中,我有 S3 和 MongoDB 的持续时间指标,并使用它们来记录观测值。对于 Java,您需要添加一些依赖项,以允许您使用 Prometheus 指标。要暴露 Prometheus 端点,您可以使用 application.yaml
配置,或者只是在代码中使用 setProperty
手动启用它。
最后,让我们看一下第一个测试场景。我们将从 1 个虚拟用户开始,在 5 分钟内缓慢扩展到 100 个用户。然后立即将用户数增加到 500 个,并保持 5 分钟。再过 5 分钟后,将用户扩展到 3000 个,直到其中一个应用程序失败。
第二个测试类似,我们只是减少了客户端数量,并使用了不同的端点。
我还必须提到,Java 现在具有容器支持,可以直接从 cgroup 获取限制。然而,有些人仍然建议手动设置最小和最大堆大小(Min and Max Heap size),而且那些 Java 的最大和最小内存百分比(Java Max and Min RAM percentages)非常令人困惑。
让我们开始运行第一个测试。正如您可能预期的那样,在这个简单的测试中,Java 的 CPU 使用率和内存使用率远高于 Go。
当我们达到大约每秒 600 次请求时,Java 根本无法处理它们并拒绝了请求。这在接下来的测试中会有所改变。
在这个包含详细 CPU 使用率的仪表板中,您可以看到 Java 进程受到了相当严重的节流(throttled)。
这是内存使用情况,在整个测试期间,Java 和 Go 的内存使用都保持平稳。
每次使用 Ingress 时,您必须至少监控 Ingress 控制器的 CPU 使用率。如果它们没有足够的资源,可能会导致延迟大幅增加,特别是当您使用 Nginx Ingress 的默认 Helm chart 时。
在测试结束时,您可以找到 Java 和 Go 的 P95 和 P90 延迟。
让我们运行第二个测试,即下载图片并保存最后修改日期。在开始时,您可以看到与第一个测试相同的模式:Java 的 CPU 使用率更高。但当请求达到每秒 20 次时,差异显著缩小。到测试结束时,当我们增加用户数量时,Go 语言的 CPU 现在变得更高了,这让我感到惊讶,并且延迟也急剧上升。
需要说明的是,这些测试仅仅代表了真实世界的场景,它们在比较语言本身方面可能不是很准确,因为我们使用了大量的外部库。但是,当您在生产环境中运行应用程序时,它们可以向您展示实际的差异。从 DevOps 的角度来看,这比斐波那契(Fibonacci)测试重要得多。
如果您发现任何错误,请告诉我,以便我可以在下一个视频中修复它们。感谢您的观看,我们下个视频再见。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。