Java曾經著名的座右銘:"一次編寫並在任何地方運行"如今已經過時了,我們想要運行代碼的唯一地方是在容器內。 "及時"編譯器沒有任何意義。

由於這個原因,Java生態系統可能正處於其轉型之中,以便更好地適應雲。 Oracle的GraalVm允許將字節代碼編譯爲Linux可執行文件(ELF)和Rad Heat的Quarkus以及其他框架,以使其像引導一個反應應用程序一樣容易。 Quarkus還以Netty和Vertx.x爲核心來構建非常有效的響應式Web服務。

> quarkus official performance stats

Java編譯爲可執行的二進制文件,可在毫秒內啓動,並且佔用的內存很小。 這可以利用Java生態系統,甚至可以用其他JVM語言(例如Scala和Kotlin)編寫!

聽起來好得令人難以置信……

如果您不相信,可以使用在線項目生成器或通過使用maven插件在本地生成項目來玩Quarkus。

另一方面,Golang誕生於雲中,當在容器中運行時,沒有留下任何負擔。 它被認爲是雲的編程語言。 從第一天開始,小型二進制文件,快速啓動程序,較小的內存佔用量就可以了。 並且被廣泛採用。 對Java世界的嚴峻挑戰。

Java有機會嗎? 只有時間證明一切。 但是,出於好奇,我想將Java雲原生服務與golang同類服務在性能和開發經驗方面進行比較。

在這篇文章中,我將強調兩項服務。 比較他們的CPU,RAM,延遲和正常運行時間。 這些服務將在具有相同資源分配的容器中啓動,並且Apache基準測試將使他們汗流sweat背。

對於我的案例研究來說,這是一個"足夠好"的基準,因爲我不認爲找到最佳/最差的基準結果,而是比較在相同環境下執行的兩個基準。

場景

兩種服務都將連接到在另一個容器中運行的MySQL數據庫,該容器具有一個表和三行。

> the database

每個服務將獲取所有三行,將其轉換爲域對象,然後編寫JSON數組響應。

Apache基準測試將運行10K請求,併發級別爲100,這是quarkus JVM版本的兩倍(還用於測試"冷" /"熱" JVM))

> the apache benchmark command

Golang服務

使用稱爲gin的流行的反應式Web框架,該框架具有出色的基準。

在尋找golang非阻塞MySQL驅動程序時,我一無所獲,互聯網上建議同時使用go-sql-driver,這就是我要使用的。

golang樣式非常明確。 一個在你臉上的態度。 主要功能啓動服務器,配置請求處理程序,並打開數據庫連接。

構建本機go可執行文件

> Easy and fast build process. The only tool I had to use was the go compiler. No hustle at all.

Kotlin Cloud本機服務— Quarkus

這是一個Kotlin示例,大致遵循quarkus反應式MySql擴展指南。

> datasource configuration

與go版本相比,存在一些隱式東西,CDI依賴注入,使用javax註釋的聲明性路由,自動配置解析以及數據源/連接創建/服務器引導程序。 但這是使用框架的代價,它爲您帶來繁重的工作,並決定了它的工作方式。 但是,它比go版本要短得多,只要我不介意黑魔法就行!

底層有一個Netty反應式Web服務器,由Vert.x多事件循環包裝,而Vert.x反應式MySQL驅動程序可以通過一個線程處理多個數據庫連接。

另外,我可以使用Kotlin令人驚歎的收藏庫來摺疊一個列表,其中go版本還沒有泛型(但即將推出),也沒有豐富的標準收藏庫,我不得不手動編寫或生成它。

構建Java本機可執行文件

> It took 4 minutes, partly because Gradle executes the native image compilation inside a Linux Graa

基本上,我能夠弄清楚構建本機可執行文件的容器中發生的事情是SubstrateVM。 設計爲可提前編譯的可嵌入虛擬機鏈接到我們的代碼,並作爲一個單元進行編譯。 甲骨文表示,這是驚人的,但並非沒有代價,SubstrateVM的優化次數少於HotSpot Vm,並且垃圾回收器更簡單。

執行此操作的編譯器稱爲" Graal",它與語言無關,在使用Java字節碼之前,需要先將其翻譯爲中間表示形式,即Truffle語言。 這非常有趣,可以在這篇文章中找到有關Graal和Truffle的詳盡解釋。

構建Java本機圖像看起來更加複雜,速度較慢,並且生成的二進制文件幾乎是文件的兩倍。 但這有效! 與一個Java Uber(胖)Jar相比,35M可執行二進制文件實際上是什麼,它可以輕鬆地大十倍。 35MB甚至可以放在aws lambda中。

強調服務

我正在使用以下設置在本地計算機上運行所有測試:

不適使用:

  • MacBook Pro(15英寸,2017年)
  • 2.9 GHz Intel Core i7(8核)
  • 16 GB 2133 MHz LPDDR3

不適使用名爲cAdvisor的工具來監視我的容器的狀態。

場景

  • quarkus jvm熱點容器
  • quarkus java本機容器
  • golang容器

每個都分配了以下資源

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

我對……感興趣

  • cpu / ram利用率(多核的利用率)
  • cpu / ram峯值
  • cpu / ram空閒
  • 引導時間
  • 響應潛伏時間平均值/最大值
  • 吞吐量(每秒請求數)

現在,我將運行許多基準測試,併爲每個基準收集許多數據點。 如果有太多信息,請隨時跳至摘要結尾

github repo以及該實驗的所有代碼都可以在這裏找到

quarkus jvm熱點— 100MB / 0.5 CPU

  • 閒置CPU使用率0.25%
  • 空閒ram使用情況66MB
  • 自舉時間6s
> CPU usage during bootstrap. ( a spike , probably jit + launching JVM )

第一輪壓力測試(Cold JVM)

令人驚訝的是,沒有失敗的請求。

> CPU usage during stress.
> RAM launched from 60 to almost 100 MB (limit) and stayed there.

第2輪壓力測試(溫暖的JVM)

quarkus jvm熱點— 200MB / 1個CPU

  • 閒置CPU使用率0.13%
  • 空閒ram使用情況66MB
  • 引導時間3s
> CPU usage during bootstrap. ( a spike again )

第一輪壓力測試(Cold JVM)

> CPU / RAM usage under stress
> Surprisingly the JVM did not eat all the allocated 200MB and 140MB was sufficient

第2輪壓力測試(溫暖的JVM)

quarkus jvm熱點— 300MB / 2 CPU

  • 空閒cpu / ram與以前的方案相同
  • 引導時間1.1s(NICE)
> CPU usage during bootstrap, a spike again.

第一輪壓力測試(Cold JVM)

> Good CPU utilzation
> 142 mb ram was sufficient

第2輪壓力測試(溫暖的JVM)

現在,讓我們看看本地圖像將如何執行。

quarkus Java Native — 100MB / 0.5 CPU

  • 引導時間:0.125s。 (!!!)
  • 啓動時沒有CPU高峯
> cpu / ram during bootstrap

壓力測試結果

> CPU reached 0.5 limit as expected
> Good ram usage, 19MB active memory. WOW

quarkus Java Native — 200MB / 1個CPU

  • 即時引導(0.0125s)
  • 4空閒ram用法
  • 在壓力下使用19種內存
  • 100%的CPU使用率
  • 啓動時沒有CPU高峯

檢測結果

quarkus Java Native — 300MB / 2 CPU

沒提升。

golang — 100MB / 0.5 CPU

  • 空閒CPU 0
  • 閒置內存2.3MB(不錯)
  • 引導時間:幾分之一秒
  • 啓動時沒有CPU高峯

結果有點歪斜。 由於某種原因,一小部分請求需要大約7秒鐘才能完成。

當再次嘗試運行測試以查看偏斜結果是否能夠再現測試時,實際上是否已將其壓碎!

運行時錯誤:無效的內存地址或nil指針取消引用。 嗯…可能是我做錯了什麼? 似乎go-sql庫中存在錯誤。 如文檔所述,從表中讀取的代碼是100%,並且99%的時間都可以工作。 這不應該發生。

golang — 200MB / 1個CPU

我不斷收到運行時錯誤。 可疑總是在測試結束時。 但是,go-mysql驅動程序的校正不是主要問題,因此在完成90%的請求後手動終止測試。

  • 壓力下的CPU / RAM使用率
> cpu utilization during stress
> RAM usage during stress. 12.27MB, very nice.

golang — 300MB / 2個CPU

沒有明顯的改善,所有統計數據幾乎相同。 CPU利用率低於1.0。 我不知道爲什麼go不能充分利用更多的內核,有趣的是……可能是因爲該過程受IO約束,或者可能是杜松子酒需要手動配置才能更好地利用多個內核。

摘要

> aggregated stats ( warm jvm/native image | golang )

似乎Quarkus已準備好投入生產,它允許簡單的JVM /本機發行版/開發模式,並允許在本地運行本機測試。 而且,只要您不使用反射或JNI,就可以安全地配置GraalVM。 否則,您將必須自己配置graal編譯器,並且也有針對此的現有解決方案。

延遲和吞吐量

golang和雲原生Java均產生了相似的結果,儘管平均而言稍微偏愛golang服務。 但是,java本機結果更加穩定。 Golang服務有時會在1.25µs內做出響應,而很少在7s內做出響應。

"預熱"後的JVM產生了良好的結果,但比本機或go版本差。

CPU利用率

當給定的內核少於單核時,go和native-java在負載下均表現不佳,而在使用2個內核啓動時,它們並沒有表現出明顯的改進。 可能是因爲工作負載受IO限制。 或者因爲gin / Netty的默認配置沒有考慮多個內核。

另一方面,JVM利用了賦予它的所有內核,並在各個方面提高了性能。

RAM使用

壓力很大,java本機爲40MB,golang服務爲24MB。 兩種情況都不錯,儘管golang版本使用的ram幾乎少了兩倍。

JVM在壓力下使用了140MB。 完全是官方的quarkus統計信息。 對於JVM來說一點都不差,但是幾乎是golang版本的6倍。

引導時間

golang和雲原生Java均會立即啓動,而JVM版本則需要幾秒鐘(取決於分配的CPU),並在啓動時產生CPU峯值。

開發經驗

這更是一個宗教問題,而不是一個實際問題。如此病態,請謹慎回答。 Quarkus創建Java世界中非常熟悉的抽象(例如基於註釋的DI)。它爲您啓動服務並創建連接池。可以使用豐富的收藏標準庫和泛型。但是,這種感覺有點像黑魔法,一旦停止工作,您會感到無助。此外,將Java代碼編譯爲本地二進制文件並不是那麼簡單,您必須意識到其中的侷限性和注意事項,儘管Red Hat在擴展方面取得了很大的進步,但並非每個Java庫都將與本地編譯兼容。 。 (預先配置爲本地編譯的Java庫)。使用與本機編譯不兼容的庫(例如Guice)將需要您手動配置Graal VM。這是可能的,但並非像使用廣口瓶那樣直接。 Quarkus和Graal VM也"相對"新。因此,有許多冒險等待着。但由於是雙模式(JVM或本機)。萬一本機版本停止工作,總會有一個退路,這是解決任何新出現問題的好方法。

另一方面,Golang僅在現在(存在10年後)才承認需要泛型。 當然,它不喜歡隱性事件的繼續。 從很多方面來說,這都是好事。 另外,儘管go社區在追趕方面確實做得很好,但是可用的工具和庫卻更少(例如,只有一個流行的阻塞MySQL驅動程序)。 另一方面,它的編譯和構建過程非常快速/簡單。 每個golang軟件包都將爲您工作,而不受Java本地平臺引入的限制。

結論

Java成爲雲原生,Golang並沒有像JVM那樣過度地執行它,這是非常好的。 我相信它將來會被廣泛使用。 但是golang絕對可以打架。

因此,請謹慎選擇!

而且不要忘了給仙人掌澆水

相關文章