转载

云原生Java与Golang比较 -lgor

Java曾经著名的座右铭:“一次编写并在任何地方运行”如今已经过时了,我们要运行代码的唯一位置是在容器内。“即时”编译器没有任何意义。(banq注:即时启动运行变成主流目标)

因此,Java生态系统可能正处于其转型之中,以便变得更适合云计算。Oracle的 GraalVm 允许将字节码编译为Linux可执行文件( ELF )和Rad Heat的 Quarkus 以及其他框架,以期类似响应式应用一样能即时启动,Quarkus还以 Netty 和 Vertx.x 为核心来构建非常有效的响应式Web服务。

Quarkus将Java编译为可执行的二进制文件,可在毫秒内启动,并且占用的内存很小。这就可以利用Java生态系统,甚至可以用其他JVM语言(例如Scala和Kotlin)编写!如果您不相信,可以使用 在线项目生成器 或通过使用 maven plugin 在本地生成项目来玩Quarkus 。

另一方面,Golang诞生于云中,当在容器中运行时,没有留下任何负担。它被认为是云的编程语言。从第一天开始,小型二进制文件,快速启动程序,较小的内存占用量就可以了。它被广泛采用。对Java世界的严峻挑战。

Java有机会吗?只有时间证明一切。但是,出于好奇,我想在性能和开发经验方面将Java云原生服务与等效的golang进行比较。

在这篇文章中,我将强调两项服务。比较他们的CPU,RAM,延迟和正常运行时间。这些服务将在具有相同资源分配的容器中启动,并且 Apache基准测试 将使他们满载。

场景

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

每个服务将获取所有三行,将其转换为域对象,然后编写JSON数组响应。

Apache基准测试将运行10K请求,并发级别为100,是quarkus JVM版本的两倍(还可以测试“冷” /“热” JVM)。

Golang服务

使用称为 gin 的流行的反应式Web框架,该框架具有出色的基准。

# the service
<b>package</b> main

<b>import</b> (
    <font>"database/sql"</font><font>
    </font><font>"fmt"</font><font>
    </font><font>"github.com/gin-gonic/gin"</font><font>
    _ </font><font>"github.com/go-sql-driver/mysql"</font><font>
    </font><font>"net/http"</font><font>
)

type Fruit struct {
    Id  <b>int</b> `json:</font><font>"id"</font><font>`
    Name string `json:</font><font>"name"</font><font>`
}

<b>var</b> con *sql.DB

func init(){
  </font><font><i>//opening a mysql connection pool with another container </i></font><font>
   db, err := sql.Open(</font><font>"mysql"</font><font>, </font><font>"root:password@tcp(host.docker.internal:3306)/payments"</font><font>)
   <b>if</b> err != nil {
       panic(</font><font>"failed to open a mysql connection"</font><font>)
   }
   con = db
}

func main() {
    r := gin.Default()
    r.GET(</font><font>"/fruits"</font><font>, fruits)
    r.Run() </font><font><i>//server up on 8080</i></font><font>
}

</font><font><i>// THE REQUEST HANDLER</i></font><font>
func fruits(c *gin.Context) {
    fruits := getFruits()
    c.JSON(http.StatusOK, fruits)
}

func getFruits() []Fruit {
    rows, _ := con.Query(</font><font>"SELECT * FROM fruits"</font><font>)
    fruits := []Fruit{}
    <b>for</b> rows.Next() {
        <b>var</b> r Fruit
        rows.Scan(&r.Id, &r.Name)
        fruits = append(fruits, r)
    }
    <b>return</b> fruits
}
</font>

在寻找golang非阻塞MySQL驱动程序时,我什么也没找到,互联网上一致建议使用 go-sql-driver ,因此我将使用它。

golang样式非常明确。主要功能启动服务器,配置请求处理程序,并打开数据库连接。

Kotlin Cloud本机服务— Quarkus

<b>package</b> org.acme

<b>import</b> io.vertx.core.json.JsonArray
<b>import</b> io.vertx.core.json.JsonObject
<b>import</b> io.vertx.mutiny.mysqlclient.MySQLPool
<b>import</b> io.vertx.mutiny.sqlclient.Row
<b>import</b> io.vertx.mutiny.sqlclient.RowSet
<b>import</b> java.util.concurrent.CompletionStage
<b>import</b> javax.inject.Inject
<b>import</b> javax.ws.rs.GET
<b>import</b> javax.ws.rs.Path
<b>import</b> javax.ws.rs.Produces
<b>import</b> javax.ws.rs.core.MediaType

@Path(<font>"/fruits"</font><font>)
<b>class</b> FruitResource {
    @field:Inject
    lateinit <b>var</b> client: MySQLPool


    @GET
    @Produces(MediaType.APPLICATION_JSON)
    fun listFruits(): CompletionStage<JsonArray> {
        <b>return</b> client.query(</font><font>"SELECT * FROM fruits"</font><font>).execute()
                .map { rows: RowSet<Row> ->
                    rows.fold(JsonArray()) { array, row ->
                        array.add(JsonObject()
                                .put(</font><font>"id"</font><font>, row.getLong(</font><font>"id"</font><font>))
                                .put(</font><font>"name"</font><font>, row.getString(</font><font>"name"</font><font>)))
                    }
                }.subscribeAsCompletionStage()
    }
}
</font>

这是一个Kotlin示例,大致遵循 quarkus反应式MySql 扩展指南。

与go版本相比,存在一些隐式东西,CDI依赖注入,使用javax注释的声明性路由,自动配置解析以及数据源/连接创建/服务器引导程序。但这是使用框架的代价,它为您带来了沉重的负担,并决定了其工作方式。但是,它比go版本要短得多,只要我不介意黑魔法就行!

在后台,有一个Netty反应式Web服务器,它由Vert.x多事件循环包装,还具有一个Vert.x反应式MySQL驱动程序,可以通过一个线程处理多个数据库连接。

另外,我可以使用Kotlin令人惊叹的集合库fold折叠到一个列表,其中go版本还没有泛型(但即将推出),也没有丰富的标准集合库,我不得不手动编写或生成它。

基本上,我能够弄清楚构建本机可执行文件的容器中发生的事情是SubstrateVM。设计为可提前编译的可嵌入虚拟机链接到我们的代码,并作为一个单元进行编译。甲骨文认为,这是惊人的,但并非没有代价,SubstrateVM的优化次数少于 HotSpot Vm, 并且垃圾回收器更简单。

执行此操作的编译器称为“ Graal”,它与语言无关,在使用Java字节码之前,需要将其转换为中间表示形式,即Truffle语言。这非常有趣,可以在这篇 文章中 找到有关Graal和Truffle的出色解释。

构建Java本机镜像看起来更复杂,更慢,并且生成的二进制文件几乎是其两倍大小。但这有效!与一个Java Uber(胖)Jar相比,35M可执行二进制文件实际上是什么,它可以轻松地大十倍。35MB甚至可以放在aws lambda中。

.....

测试结果

延迟和吞吐量比较结果:golang和云原生Java均产生了相似的结果,尽管平均而言稍微偏爱golang服务。但是,java本机结果更加稳定。Golang服务有时会在1.25µs内做出响应,而很少在7s内做出响应。

“预热”后的JVM产生了良好的结果,但比本机或go版本差。

在CPU利用上,当给定的内核少于单核时,go和native-java在负载下均表现不佳,而在使用2个内核启动时,它们并没有表现出明显的改进。可能是因为工作负载受IO限制。或者因为gin / Netty的默认配置没有考虑多个内核。另一方面,JVM利用了赋予它的所有内核,并在各个方面提高了性能。

在内存使用上,压力很大情况下,java本机为40MB,golang服务为24MB。两种情况都不错,尽管golang版本使用的ram几乎少了两倍。JVM在压力下使用了140MB。完全是官方的quarkus统计信息。对于JVM来说一点都不差,但是几乎是golang版本的6倍。

引导时间:golang和云原生Java均会立即启动,而JVM版本则需要几秒钟(取决于分配的CPU),并在启动时产生CPU峰值。

原文  https://www.jdon.com/54557
正文到此结束
Loading...