Java微服务可以和Go一样快吗?

开发 前端
业界普遍认为Java是"老的","慢的"和"无聊的"。 Go是"快速","新"和"酷"。 但是我们想知道这些特性是否得到实际性能数据的保证或支持。

我们想进行实验,看看Java微服务是否可以像Go微服务一样快速运行。 业界普遍认为Java是"老的","慢的"和"无聊的"。 Go是"快速","新"和"酷"。 但是我们想知道这些特性是否得到实际性能数据的保证或支持。

[[351764]]

我们想要一个公平的测试,所以我们创建了一个非常简单的微服务,没有外部依赖项(例如数据库),并且代码路径非常短(仅处理字符串)。 我们确实包含了指标和日志记录,因为它们似乎总是包含在任何实际的微服务中。 我们使用了小型轻量级的框架(Helidon for Java和Go-Kit for Go),并且还尝试了Java的纯JAX-RS。 我们尝试了不同版本的Java和不同的JVM。 我们对堆大小和垃圾收集器进行了一些基本调整。 我们在测试运行之前对微服务进行了预热。

Java的历史

Java由Sun Microsystems开发,后来被Oracle收购。 其1.0版本于1996年发布,最新版本是2020年的Java15。主要设计目标是Java虚拟机和字节码的可移植性以及带有垃圾回收的内存管理。 它仍然是最流行的语言之一(根据StackOverflow和TIOBE之类的来源),它是在开源中开发的。

让我们谈谈" Java问题"。

Java微服务可以和Go一样快吗?

多年来,Java有许多不同的垃圾收集算法,包括串行,并行,并发标记/清除,G1和新的ZGC垃圾收集器。 现代垃圾收集器旨在最大程度地减少垃圾收集"停止世界"暂停的时间。

Oracle实验室开发了一种新的名为GraalVM的Java虚拟机,该Java虚拟机用Java编写,具有新的编译器和一些令人兴奋的新功能,例如能够将Java字节码转换为无需Java VM即可运行的本机映像。

Go的历史

Go由Google的Robert Griesemer,Rob Pike和Ken Thomson创建。

Go受C,Python,Javascript和C的影响。 它被设计为用于高性能网络和多处理的最佳语言。

在我们演讲时,StackOverflow有27,872个被" Go"标记的问题,而Java则为1,702,730。

Go是一种静态类型的编译语言。

Go是许多CNCF项目的首选语言,例如Kubernetes,Istio,Prometheus和Grafana都(大部分)用Go编写。

它旨在具有快速的构建时间和快速的执行。

Go(与Java相比)有什么好处-根据我的经验,这是我的个人看法:

  • 容易实现功能模式,例如合成,纯函数,不可变状态。
  • 样板代码少得多(但仍然太多)。
  • 它仍然处于生命周期的早期,因此它不具有向后兼容的沉重负担-他们仍然可以打破现状来改进它。
  • 它可以编译成本地静态链接的二进制文件-无虚拟机层-二进制文件具有运行程序所需的一切,这对于" FROM scratch"容器非常有用。
  • 它具有体积小,启动快和执行快的特点。
  • 没有OOP,继承,泛型,断言,指针算术。
  • 括号较少,例如
  • 没有循环依赖性,没有未使用的变量或导入,没有隐式类型转换的强制。

那么,Go的"问题"是什么?

  • 工具生态系统还不成熟,尤其是依赖管理-有多种选择,没有一个是完美的,特别是对于非开源开发而言;
  • 建立具有新/更新依赖关系的代码非常慢(例如Maven著名的"下载Internet"问题。
  • 导入会将代码绑定到存储库,这使代码在噩梦中移动。
  • IDE非常适合编程,文档查找,自动完成等。
  • 指针!
  • 没有Java风格的try / catch异常(如果err!= nil的使用频率太高,您最终会写出来),没有功能风格的原语,例如列表,映射函数等。
  • 由于它尚不可用,您通常会最终实现一些基本算法。 最近,我写了一些代码,由sloe进行了两个字符串(列表)的比较,并进行了转换。 用一种功能语言,我本可以使用诸如map这样的内置函数来做到这一点。
  • 没有动态链接! (您问"谁在乎?"。)如果您要使用带有"感染"静态链接代码的GPL许可证的代码,这可能是一个真正的问题。
  • 调节执行或垃圾收集,配置文件执行或优化算法的旋钮并不多-Java具有数百种垃圾收集调整选项,而Go具有一个-启用或禁用。

负载测试方法

我们使用JMeter进行负载测试。 测试多次调用服务,并收集有关响应时间,吞吐量(每秒事务)和内存使用情况的数据。 对于Go,我们收集常驻集大小;对于Java,我们跟踪本机内存。

在许多测试中,我们将JMeter与被测应用程序在同一台计算机上运行。 如果我们在另一台机器上运行JMeter,结果似乎没有任何干扰或差异,因此可以简化设置。 当我们以后将应用程序部署到Kubernetes中时,JMeter在集群外部的远程计算机上运行。

在进行测量之前,我们使用了1,000次服务调用来预热了应用程序。

应用程序本身的源代码以及负载测试的定义都在以下GitHub存储库中:

https://github.com/markxnelson/go-java-go

第一轮测试

在第一轮中,我们在"小型"计算机上进行了测试,在这种情况下,该计算机是2.5GHz双核Intel Core i7笔记本电脑,具有16GB RAM,运行macOS。

结果如下:

Java微服务可以和Go一样快吗?

我们宣布Go成为第一轮的获胜者!

以下是我们根据这些结果得出的观察结果:

  • 日志记录似乎是主要的性能问题,尤其是java.util.logging。 因此,我们在进行日志记录和不进行日志记录的情况下都进行了测试。 我们还注意到,日志记录是Go应用程序性能好的重要因素。
  • Java版本具有显著的更大的内存占用空间,即使对于如此小的简单应用程序也是如此
  • 预热对JVM产生了很大的影响-我们知道JVM在运行时会进行优化,因此这很有意义
  • 在此测试中,我们正在比较不同的执行模型-Go应用程序被编译为本地可执行二进制文件,而Java应用程序被编译为字节代码,然后在虚拟机上运行。

GraalVM本机映像

GraalVM具有本机映像功能,可让您采用Java应用程序并将其本质上编译为本机可执行代码。

该可执行文件包括应用程序类,其依赖项中的类,运行时库类以及JDK中的静态链接本机代码。

这是再次添加GraalVM本机图像测试(使用GraalVM EE 20.1.1-JDK 11构建的本地图像)的第一轮结果:

Java微服务可以和Go一样快吗?

在这种情况下,与在JVM上运行应用程序相比,使用GraalVM本机映像没有看到吞吐量或响应时间的任何显着改善,但是内存占用空间较小。

以下是一些测试的响应时间的图表:

Java微服务可以和Go一样快吗?

> Response time graphs for round one

请注意,在所有这三种Java变体中,第一个请求的响应时间要长得多(请在与左轴相对的右上方寻找那条蓝线)。

第二轮

接下来,我们决定在更大的计算机上运行测试。

与第一轮一样,我们使用了100个线程,每个线程10,000个循环,10秒的启动时间以及相同版本的Go,Java,Helidon和GraalVM。

结果如下:

Java微服务可以和Go一样快吗?

我们宣布GraalVM本机映像是第二轮的赢家!

以下是这些测试的响应时间图:

Java微服务可以和Go一样快吗?

> Response times for test runs with logging enabled but no warmup

Java微服务可以和Go一样快吗?

> Response times for test runs with no logging and no warmup

Java微服务可以和Go一样快吗?

> Response times for test runs with warmup but no logging

第二轮的一些观察:

  • 在此测试中,Java变体的性能要好得多,并且在不使用日志记录的情况下,其性能要比Go好很多
  • Java似乎更有能力使用硬件提供的多个内核和执行线程(与Go相比)–这是有一定道理的,因为Go旨在作为一种系统和网络编程语言,并且它是一种较年轻的语言,因此
  • 有趣的是,Java是在多核处理器不常见的时候设计的,而Go是在多核处理器不是通用的时候设计的。
  • 特别是,似乎Java日志记录已成功卸载到其他线程/内核,并且对性能的影响要小得多。
  • 这一轮的最佳性能来自GraalVM本机映像,平均响应时间为0.25毫秒,每秒处理82,426个事务,而Go的最佳结果为1.59毫秒和39,227 tps,但这是以增加两个数量级的内存为代价的用法!
  • Java变体的响应时间似乎更加一致,但是出现了更多的峰值-我们认为这意味着Go在做更多,更小的垃圾回收

第三轮-Kubernetes

在第三轮中,我们决定在Kubernetes集群中运行应用程序—您可能会说,这是微服务的更自然的运行时环境。

在这一轮中,我们使用了具有三个工作节点的Kubernetes 1.16.8集群,每个工作节点都有两个内核(每个都有两个执行线程),14GB的RAM和Oracle Linux 7.8。 在某些测试中,我们为每个变体运行了一个Pod,在其他测试中运行了100个Pod。

应用程序访问是通过Traefik入口控制器进行的,其中JMeter在Kubernetes集群外部运行,以进行某些测试,对于其他测试,我们使用ClusterIP并在集群中运行JMeter。

与之前的测试一样,我们使用了100个线程,每个线程10,000个循环,以及10秒的启动时间。

以下是每个变体的容器大小:

  • 继续11.6MB
  • Java / Helidon 1.41GB
  • Java / Helidon JLinked 150MB
  • 本机图像25.2MB

结果如下:

Java微服务可以和Go一样快吗?

以下是一些响应时间表:

Java微服务可以和Go一样快吗?

> Response times from Kubernetes tests

在这一轮中,我们观察到Go有时会更快,而GraalVM本地映像有时会更快,但是两者之间的差异很小(通常小于5%)

那我们学到了什么?

我们对所有这些测试和结果进行了反思,以下是一些结论:

  • Kubernetes似乎没有迅速扩展
  • Java似乎比Go更擅长使用所有可用的内核/线程-我们发现Java测试期间CPU利用率更高
  • 在具有更多内核和内存的机器上,Java性能更好;在较小/功能较弱的机器上,Go性能更好。
  • Go的性能总体上更加一致-可能是由于Java的垃圾回收
  • 在"生产规模"的计算机上,Java的运行速度与Go一样快,甚至更快
  • 日志记录似乎是我们在Go和Java中遇到的主要瓶颈
  • Java的现代版本以及诸如Helidon之类的新框架在消除/减少Java的一些众所周知且长期存在的问题(例如冗长程度,GC性能,启动时间等)的痛苦方面取得了长足的进步。

接下来是什么?

这是一个非常有趣的练习,我们打算继续努力,特别是:

  • 我们想通过Kubernetes自动扩展做更多的工作-我们可能需要更复杂的微服务或更高的负载才能看到性能上的差异
  • 我们希望研究更复杂的微服务,多种服务以及电路中断等模式,并观察网络如何影响性能以及如何调整微服务网络
  • 还想看一下日志记录问题,看看该怎么做才能消除瓶颈
  • 我们想看一下目标代码并比较正在执行的实际指令,看看是否可以在代码路径中做一些进一步的优化。
  • 我们想知道JMeter是否可以在不成为瓶颈的情况下产生足够的负载,但是我们的测试表明这根本不是一个因素,它可以轻松跟上Go和Java实现。
  • 想要对容器启动时间,内存占用量等进行更详细的测量。

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2021-04-12 10:20:20

Java微服务Go

2020-11-17 15:31:23

Java微服务Go

2021-01-14 09:55:21

Java微服务Go

2020-09-10 13:55:53

VScodego开发工具

2021-08-27 06:41:34

Docker ContainerdRun&Exec

2020-01-09 17:03:29

人工智能技术算法

2020-03-02 10:56:41

办公电脑疫情

2021-01-14 16:00:52

VRVR电影3D电影

2014-03-17 10:20:30

数据目录服务

2024-01-05 07:36:54

人工智能创造力模型

2021-10-17 22:02:27

显卡内存装机

2020-02-24 17:58:54

RSA大会网络安全

2021-12-23 15:11:46

Web 3.0元宇宙Metaverse

2021-12-22 07:31:18

RedisNoSQL数据库

2011-03-14 16:51:24

2022-07-28 14:46:01

人工智能机器人计算机科学

2021-05-12 08:46:09

电脑电脑网速技巧

2022-09-26 08:06:24

Go语言

2021-04-19 10:08:01

云计算机房数据中心

2012-03-07 17:24:10

戴尔咨询
点赞
收藏

51CTO技术栈公众号