雲原生Java與Golang比較 -lgor

banq發表於2020-07-07

Java曾經著名的座右銘:“一次編寫並在任何地方執行”如今已經過時了,我們要執行程式碼的唯一位置是在容器內。“即時”編譯器沒有任何意義。(banq注:即時啟動執行變成主流目標)
因此,Java生態系統可能正處於其轉型之中,以便變得更適合雲端計算。Oracle的GraalVm允許將位元組碼編譯為Linux可執行檔案(ELF)和Rad Heat的Quarkus以及其他框架,以期類似響應式應用一樣能即時啟動,Quarkus還以NettyVertx.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
package main

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

type Fruit struct {
    Id  int `json:"id"`
    Name string `json:"name"`
}

var con *sql.DB

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

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

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

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

在尋找golang非阻塞MySQL驅動程式時,我什麼也沒找到,網際網路上一致建議使用go-sql-driver,因此我將使用它。
golang樣式非常明確。主要功能啟動伺服器,配置請求處理程式,並開啟資料庫連線。

Kotlin Cloud本機服務— Quarkus

package org.acme

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

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


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


這是一個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峰值。

 

相關文章