云原生时代,Java还是Go?

开发 后端 云原生
Java曾经著名的座右铭:"一次编写,到处运行",已经很过时了,因为现在我们只想在容器里运行代码。在容器里,一个 "Just in time "的编译器意义不大。

Java曾经著名的座右铭:"一次编写,到处运行",已经很过时了,因为现在我们只想在容器里运行代码。在容器里,一个 "Just in time "的编译器意义不大。

出于这个原因,可能为了更好地适应云计算,Java生态系统正处于转型之中。Oracle 的GraalVm允许将字节码编译成Linux可执行文件(ELF),而Rad Heat的Quarkus以及其他框架,则立志让响应式服务这件事变得更简单。Quarkus以Netty和Vertx.x为核心,可以用来构建非常高效的响应式Web服务。

 

Java编译成可执行二进制文件,以毫秒级的速度启动,内存占用很小。这样就可以利用Java生态系统,甚至可以用其他JVM语言(如Scala和Kotlin)编写。你可以用online项目生成器玩玩Quarkus,或者用maven插件在本地生成一个项目。

而Golang则是为云而生的,在容器中运行时,没有遗留负担。它被认为是云端的编程语言。生成的二进制可执行文件很小,快速启动,内存占用也很小,而且这是从Go诞生之初就具备的特性。Golang的流行对 Java 世界形成了严峻的挑战。

Java有机会吗,也许只有时间才会告诉我们最终答案。然而,出于好奇,我想从性能和开发体验方面比较一下 Java 和 Golang 的云原生服务。

在这篇文章中,我将使用两种语言来写同样的服务。比较它们的CPU使用率、RAM、延迟和运行速度。这些服务将在容器中启动,资源分配相同,使用ab来测试。

对于我的案例来说,这是一个 "足够好 "的基准,因为我不假设找到最好/最差的基准结果,而是在同一环境下执行运行两个基准测试进行比较。

场景

这两个服务将连接到在另一个容器中运行的MySQL数据库,有一个表和三行数据。

 

每一个服务都会获取所有记录,将它们转化为对象,然后输出JSON数组。

ab将发出10K请求,并发级别为100,quarkus JVM版本运行两次(用于测试 "冷"/"暖 "JVM)。

 

Go语言版本

Go语言版本使用gin框架。

  1. # the service 
  2. package main 
  3.  
  4. import ( 
  5.     "database/sql" 
  6.     "fmt" 
  7.     "github.com/gin-gonic/gin" 
  8.     _ "github.com/go-sql-driver/mysql" 
  9.     "net/http" 
  10.  
  11. type Fruit struct { 
  12.     Id  int `json:"id"
  13.     Name string `json:"name"
  14.  
  15. var con *sql.DB 
  16.  
  17. func init(){ 
  18.   //opening a mysql connection pool with another container 
  19.    db, err := sql.Open("mysql""root:password@tcp(host.docker.internal:3306)/payments"
  20.    if err != nil { 
  21.        panic("failed to open a mysql connection"
  22.    } 
  23.    con = db 
  24.  
  25. func main() { 
  26.     r := gin.Default() 
  27.     r.GET("/fruits", fruits) 
  28.     r.Run() //server up on 8080 
  29.  
  30. // THE REQUEST HANDLER 
  31. func fruits(c *gin.Context) { 
  32.     fruits := getFruits() 
  33.     c.JSON(http.StatusOK, fruits) 
  34.  
  35. func getFruits() []Fruit { 
  36.     rows, _ := con.Query("SELECT * FROM fruits"
  37.     fruits := []Fruit{} 
  38.     for rows.Next() { 
  39.         var r Fruit 
  40.         rows.Scan(&r.Id, &r.Name
  41.         fruits = append(fruits, r) 
  42.     } 
  43.     return fruits 

Golang的MySQL驱动的使用go-sql-driver。golang的代码风格是非常明确的。一种一切都在眼前态度。主函数启动服务器,配置请求处理程序,打开DB连接。

编译本地可执行文件

 

Kotlin版本

  1. package org.acme 
  2. import io.vertx.core.json.JsonArray 
  3. import io.vertx.core.json.JsonObject 
  4. import io.vertx.mutiny.mysqlclient.MySQLPool 
  5. import io.vertx.mutiny.sqlclient.Row 
  6. import io.vertx.mutiny.sqlclient.RowSet 
  7. import java.util.concurrent.CompletionStage 
  8. import javax.inject.Inject 
  9. import javax.ws.rs.GET 
  10. import javax.ws.rs.Path 
  11. import javax.ws.rs.Produces 
  12. import javax.ws.rs.core.MediaType 
  13.  
  14. @Path("/fruits"
  15. class FruitResource { 
  16.     @field:Inject 
  17.     lateinit var client: MySQLPool 
  18.  
  19.  
  20.     @GET 
  21.     @Produces(MediaType.APPLICATION_JSON) 
  22.     fun listFruits(): CompletionStage<JsonArray> { 
  23.         return client.query("SELECT * FROM fruits").execute() 
  24.                 .map { rows: RowSet<Row> -> 
  25.                     rows.fold(JsonArray()) { array, row -> 
  26.                         array.add(JsonObject() 
  27.                                 .put("id", row.getLong("id")) 
  28.                                 .put("name", row.getString("name"))) 
  29.                     } 
  30.                 }.subscribeAsCompletionStage() 
  31.     } 

数据库连接使用Quarkus React Mysql 扩展。

 

与Go版本相比,代码有很大不同,比如CDI依赖注入,使用javax注释的声明式路由,自动配置解析,以及数据源/连接创建/服务器引导。这是使用框架的代价,它为你完成了繁重的工作,并决定了做事方式。不过,它比go版本代码要简短很多。

这里使用Netty响应式web服务器,由Vert.x多事件循环包装,还有一个Vert.x响应式MySQL驱动,这样可以用一个线程处理多个DB连接。

另外,我可以使用Kotlin的集合库的fold函数,这种函数还没有通用的Go版本。

编译Java版本的可执行文件

 

我已经弄清楚构建过程中发生了什么,其核心是SubstrateVM。它被设计在AOT过程中的可嵌入虚拟机,它会链接到我们的代码,并作为一个单元进行编译。然而根据Oracle的说法,SubstrateVM的优化比HotSpot Vm少,垃圾收集器也比较简单。

该AOT编译器被称为 "Graal",它是语言不相关的。java字节码需要被翻译成一种中间表示法(Truffle语言)。这在这篇文章【1】中可以找到关于Graal和Truffle的相关论述。

构建一个 Java 本地可执行文件看起来更复杂,编译得更慢,它产生的二进制文件几乎是Go版本两倍大小。然而一个35M的可执行二进制文件和Java FatJar相比,还是小D多了。35MB甚至可以让你使用aws lambda运行。

压力测试

我在本机运行所有测试,设置如下。

  • MacBook Pro(15英寸,2017年
  • 2.9 GHz英特尔酷睿i7(8个核心)。
  • 16 GB 2133 MHz LPDDR3

使用cAdvisor的工具来监控容器的统计数据。

场景

  • Quarkus JVM hotspot
  • Quarkus Java native
  • Golang

上述的每种情况都在以下三种配置上测试

  • 100MB / 0.5 CPU | 200MB / 1 CPU | 300MB / 2 CPU

我主要关注:

  • cpu/ram利用率(多核的利用率)
  • cpu/ram峰值
  • cpu/ram空余
  • 启动时间
  • 响应延迟avg/max
  • 吞吐量(每秒请求数)

测试结果

 

看起来Quarkus已经为生产环境做好准备了,它允许简单的JVM/原生发布/开发 模式,并允许在本地运行原生测试。只要你不使用反射或JNI,根据GraalVM的配置就是可行的。否则,你将不得不自己配置graal编译器,然而现在也有解决方案。

延迟和吞吐量

Golang 和原生 Java 的测试结果比较接近,虽然平均来说 Golang 版本的测试结果略好一些。不过,Java Native版本的测试结果更稳定。Golang服务有时在1.25μs内完成响应,也有一部分需要7s才能完成。

"预热 "后的JVM版本结果也不差,但比Native或Go版本稍逊一筹。

CPU利用率

使用0.5核的时候,Go和native-java在负载下似乎都表现不佳,而用2核启动时,也没有明显改善。这可能是因为工作负载的瓶颈是IO。或者是因为gin/Netty的默认配置没有考虑到多核的问题。

而JVM版本则利用了所有给定的核心,并在各个维度上提升了性能。

内存使用率

在压力下,Java native 使用40MB,Golang 使用24MB。两种情况下都还不错,虽然Golang版本使用的内存几乎少了一倍。

JVM使用了140MB。和Quarkus官方的统计完全一样。对于JVM来说还不错,但比Golang版本多了近6倍。

启动时间

Golang和cloud-native java都能立即启动,然而JVM版本需要几秒钟(取决于分配的CPU),并且在启动时产生CPU峰值。如果配置不当,会导致k8s HPA发飙,并增加pods。

开发体验

这与其说是一个实际问题,不如说是一个宗教问题。Quarkus 使用了在 Java 世界中很常见的抽象(比如基于注解的DI)。它为你启动服务并创建连接池。它可以使用丰富的集合标准库和generics。然而,这可能感觉有点像黑魔法,一旦有些组件不工作,你会感觉很无助。此外,将 Java 代码编译成原生二进制并不是那么简单,有一些限制和注意事项是你必须知道的,并非每个Java库都能兼容原生编译。一旦使用一个不兼容的库(比如Guice),你就需要自己配置Graal VM。

Quarkus 和 Graal VM "相对 "较新。所以可能会有一些问题。但由于双模式(JVM或原生)。在原生版本的某些组件停止工作的情况下,总是有一个后备方案,这对任何新问题来说都是很好的变通方法。

另一方面,Golang 在成立10年后才承认它需要generics。而且它肯定不喜欢框架使用很多魔法操作。这在很多方面既是好事也是坏事。此外,尽管 Go 社区做的非常好,然而可用的工具和库还是相对较少。然而它的编译和构建过程更快/更简单。而且兼容每个Golang的包,没有java-native平台带来的限制。

结论

Java已经为云原生做好了准备,Golang并没有大幅度领先。相信未来Cloud Native Java会被大规模使用。

 

原文地址:https://medium.com/swlh/cloud-native-java-vs-golang-2a72c0531b05

本文转载自微信公众号「高可用架构」,可以通过以下二维码关注。转载本文请联系Igor Domrev 公众号。 

 

责任编辑:武晓燕 来源: 高可用架构
相关推荐

2019-09-20 13:37:50

Java云原生Docker

2021-08-09 11:43:02

容器云原生安全

2022-06-22 09:24:30

云原生Go 语言

2020-08-28 08:29:40

云原生微服务编程

2023-08-28 16:08:12

2019-07-04 17:28:04

腾讯云云原生开源

2020-12-01 17:44:15

华为云Go语言云原生

2020-10-21 10:04:56

云原生应用架构

2021-03-23 11:09:36

云计算

2022-11-30 18:38:50

2022-10-27 18:03:04

GogRPC云原生

2022-05-26 11:50:15

云原生云安全

2021-08-23 13:50:46

云原生PaaS

2021-12-08 12:03:09

金融科技云原生

2023-11-30 16:42:21

2022-01-14 07:17:39

阿里云云原生经济

2012-09-26 10:59:52

大数据云计算云服务

2024-01-03 15:09:21

云原生Go语言
点赞
收藏

51CTO技术栈公众号