云原生的 Java與Golang
Java曾經(jīng)著名的座右銘:"一次編寫并在任何地方運行"如今已經(jīng)過時了,我們想要運行代碼的唯一地方是在容器內(nèi)。 "及時"編譯器沒有任何意義。
由于這個原因,Java生態(tài)系統(tǒng)可能正處于其轉(zhuǎn)型之中,以便更好地適應(yīng)云。 Oracle的GraalVm允許將字節(jié)代碼編譯為Linux可執(zhí)行文件(ELF)和Rad Heat的Quarkus以及其他框架,以使其像引導(dǎo)一個反應(yīng)應(yīng)用程序一樣容易。 Quarkus還以Netty和Vertx.x為核心來構(gòu)建非常有效的響應(yīng)式Web服務(wù)。

> quarkus official performance stats
Java編譯為可執(zhí)行的二進(jìn)制文件,可在毫秒內(nèi)啟動,并且占用的內(nèi)存很小。 這可以利用Java生態(tài)系統(tǒng),甚至可以用其他JVM語言(例如Scala和Kotlin)編寫!
聽起來好得令人難以置信……
如果您不相信,可以使用在線項目生成器或通過使用maven插件在本地生成項目來玩Quarkus。
另一方面,Golang誕生于云中,當(dāng)在容器中運行時,沒有留下任何負(fù)擔(dān)。 它被認(rèn)為是云的編程語言。 從第一天開始,小型二進(jìn)制文件,快速啟動程序,較小的內(nèi)存占用量就可以了。 并且被廣泛采用。 對Java世界的嚴(yán)峻挑戰(zhàn)。
Java有機(jī)會嗎? 只有時間證明一切。 但是,出于好奇,我想將Java云原生服務(wù)與golang同類服務(wù)在性能和開發(fā)經(jīng)驗方面進(jìn)行比較。
在這篇文章中,我將強(qiáng)調(diào)兩項服務(wù)。 比較他們的CPU,RAM,延遲和正常運行時間。 這些服務(wù)將在具有相同資源分配的容器中啟動,并且Apache基準(zhǔn)測試將使他們汗流sweat背。
對于我的案例研究來說,這是一個"足夠好"的基準(zhǔn),因為我不認(rèn)為找到最佳/最差的基準(zhǔn)結(jié)果,而是比較在相同環(huán)境下執(zhí)行的兩個基準(zhǔn)。
場景
兩種服務(wù)都將連接到在另一個容器中運行的MySQL數(shù)據(jù)庫,該容器具有一個表和三行。

> the database
每個服務(wù)將獲取所有三行,將其轉(zhuǎn)換為域?qū)ο?,然后編寫JSON數(shù)組響應(yīng)。
Apache基準(zhǔn)測試將運行10K請求,并發(fā)級別為100,這是quarkus JVM版本的兩倍(還用于測試"冷" /"熱" JVM))

> the apache benchmark command
Golang服務(wù)
使用稱為gin的流行的反應(yīng)式Web框架,該框架具有出色的基準(zhǔn)。
在尋找golang非阻塞MySQL驅(qū)動程序時,我一無所獲,互聯(lián)網(wǎng)上建議同時使用go-sql-driver,這就是我要使用的。
golang樣式非常明確。 一個在你臉上的態(tài)度。 主要功能啟動服務(wù)器,配置請求處理程序,并打開數(shù)據(jù)庫連接。
構(gòu)建本機(jī)go可執(zhí)行文件

> Easy and fast build process. The only tool I had to use was the go compiler. No hustle at all.
Kotlin Cloud本機(jī)服務(wù)— Quarkus
這是一個Kotlin示例,大致遵循quarkus反應(yīng)式MySql擴(kuò)展指南。

> datasource configuration
與go版本相比,存在一些隱式東西,CDI依賴注入,使用javax注釋的聲明性路由,自動配置解析以及數(shù)據(jù)源/連接創(chuàng)建/服務(wù)器引導(dǎo)程序。 但這是使用框架的代價,它為您帶來繁重的工作,并決定了它的工作方式。 但是,它比go版本要短得多,只要我不介意黑魔法就行!
底層有一個Netty反應(yīng)式Web服務(wù)器,由Vert.x多事件循環(huán)包裝,而Vert.x反應(yīng)式MySQL驅(qū)動程序可以通過一個線程處理多個數(shù)據(jù)庫連接。
另外,我可以使用Kotlin令人驚嘆的收藏庫來折疊一個列表,其中g(shù)o版本還沒有泛型(但即將推出),也沒有豐富的標(biāo)準(zhǔn)收藏庫,我不得不手動編寫或生成它。
構(gòu)建Java本機(jī)可執(zhí)行文件

> It took 4 minutes, partly because Gradle executes the native image compilation inside a Linux Graa
基本上,我能夠弄清楚構(gòu)建本機(jī)可執(zhí)行文件的容器中發(fā)生的事情是SubstrateVM。 設(shè)計為可提前編譯的可嵌入虛擬機(jī)鏈接到我們的代碼,并作為一個單元進(jìn)行編譯。 甲骨文表示,這是驚人的,但并非沒有代價,SubstrateVM的優(yōu)化次數(shù)少于HotSpot Vm,并且垃圾回收器更簡單。
執(zhí)行此操作的編譯器稱為" Graal",它與語言無關(guān),在使用Java字節(jié)碼之前,需要先將其翻譯為中間表示形式,即Truffle語言。 這非常有趣,可以在這篇文章中找到有關(guān)Graal和Truffle的詳盡解釋。
構(gòu)建Java本機(jī)圖像看起來更加復(fù)雜,速度較慢,并且生成的二進(jìn)制文件幾乎是文件的兩倍。 但這有效! 與一個Java Uber(胖)Jar相比,35M可執(zhí)行二進(jìn)制文件實際上是什么,它可以輕松地大十倍。 35MB甚至可以放在aws lambda中。
強(qiáng)調(diào)服務(wù)
我正在使用以下設(shè)置在本地計算機(jī)上運行所有測試:

不適使用:
- MacBook Pro(15英寸,2017年)
- 2.9 GHz Intel Core i7(8核)
- 16 GB 2133 MHz LPDDR3
不適使用名為cAdvisor的工具來監(jiān)視我的容器的狀態(tài)。
場景
- quarkus jvm熱點容器
- quarkus java本機(jī)容器
- golang容器
每個都分配了以下資源
- 100MB / 0.5 CPU | 200MB / 1個CPU | 300MB / 2個CPU
我對……感興趣
- cpu / ram利用率(多核的利用率)
- cpu / ram峰值
- cpu / ram空閑
- 引導(dǎo)時間
- 響應(yīng)潛伏時間平均值/最大值
- 吞吐量(每秒請求數(shù))
現(xiàn)在,我將運行許多基準(zhǔn)測試,并為每個基準(zhǔn)收集許多數(shù)據(jù)點。 如果有太多信息,請隨時跳至摘要結(jié)尾
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
- 引導(dǎo)時間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與以前的方案相同
- 引導(dǎo)時間1.1s(NICE)

> CPU usage during bootstrap, a spike again.
第一輪壓力測試(Cold JVM)

> Good CPU utilzation

> 142 mb ram was sufficient
第2輪壓力測試(溫暖的JVM)
現(xiàn)在,讓我們看看本地圖像將如何執(zhí)行。
quarkus Java Native — 100MB / 0.5 CPU
- 引導(dǎo)時間:0.125s。 (!!!)
- 啟動時沒有CPU高峰

> cpu / ram during bootstrap
壓力測試結(jié)果

> CPU reached 0.5 limit as expected

> Good ram usage, 19MB active memory. WOW
quarkus Java Native — 200MB / 1個CPU
- 即時引導(dǎo)(0.0125s)
- 4空閑ram用法
- 在壓力下使用19種內(nèi)存
- 100%的CPU使用率
- 啟動時沒有CPU高峰
檢測結(jié)果
quarkus Java Native — 300MB / 2 CPU
沒提升。
golang — 100MB / 0.5 CPU
- 空閑CPU 0
- 閑置內(nèi)存2.3MB(不錯)
- 引導(dǎo)時間:幾分之一秒
- 啟動時沒有CPU高峰
結(jié)果有點歪斜。 由于某種原因,一小部分請求需要大約7秒鐘才能完成。
當(dāng)再次嘗試運行測試以查看偏斜結(jié)果是否能夠再現(xiàn)測試時,實際上是否已將其壓碎!
運行時錯誤:無效的內(nèi)存地址或nil指針取消引用。 嗯…可能是我做錯了什么? 似乎go-sql庫中存在錯誤。 如文檔所述,從表中讀取的代碼是100%,并且99%的時間都可以工作。 這不應(yīng)該發(fā)生。
golang — 200MB / 1個CPU
我不斷收到運行時錯誤。 可疑總是在測試結(jié)束時。 但是,go-mysql驅(qū)動程序的校正不是主要問題,因此在完成90%的請求后手動終止測試。
- 壓力下的CPU / RAM使用率

> cpu utilization during stress

> RAM usage during stress. 12.27MB, very nice.
golang — 300MB / 2個CPU
沒有明顯的改善,所有統(tǒng)計數(shù)據(jù)幾乎相同。 CPU利用率低于1.0。 我不知道為什么go不能充分利用更多的內(nèi)核,有趣的是……可能是因為該過程受IO約束,或者可能是杜松子酒需要手動配置才能更好地利用多個內(nèi)核。
摘要

> aggregated stats ( warm jvm/native image | golang )
似乎Quarkus已準(zhǔn)備好投入生產(chǎn),它允許簡單的JVM /本機(jī)發(fā)行版/開發(fā)模式,并允許在本地運行本機(jī)測試。 而且,只要您不使用反射或JNI,就可以安全地配置GraalVM。 否則,您將必須自己配置graal編譯器,并且也有針對此的現(xiàn)有解決方案。
延遲和吞吐量
golang和云原生Java均產(chǎn)生了相似的結(jié)果,盡管平均而言稍微偏愛golang服務(wù)。 但是,java本機(jī)結(jié)果更加穩(wěn)定。 Golang服務(wù)有時會在1.25µs內(nèi)做出響應(yīng),而很少在7s內(nèi)做出響應(yīng)。
"預(yù)熱"后的JVM產(chǎn)生了良好的結(jié)果,但比本機(jī)或go版本差。
CPU利用率
當(dāng)給定的內(nèi)核少于單核時,go和native-java在負(fù)載下均表現(xiàn)不佳,而在使用2個內(nèi)核啟動時,它們并沒有表現(xiàn)出明顯的改進(jìn)。 可能是因為工作負(fù)載受IO限制。 或者因為gin / Netty的默認(rèn)配置沒有考慮多個內(nèi)核。
另一方面,JVM利用了賦予它的所有內(nèi)核,并在各個方面提高了性能。
RAM使用
壓力很大,java本機(jī)為40MB,golang服務(wù)為24MB。 兩種情況都不錯,盡管golang版本使用的ram幾乎少了兩倍。
JVM在壓力下使用了140MB。 完全是官方的quarkus統(tǒng)計信息。 對于JVM來說一點都不差,但是幾乎是golang版本的6倍。
引導(dǎo)時間
golang和云原生Java均會立即啟動,而JVM版本則需要幾秒鐘(取決于分配的CPU),并在啟動時產(chǎn)生CPU峰值。
開發(fā)經(jīng)驗
這更是一個宗教問題,而不是一個實際問題。如此病態(tài),請謹(jǐn)慎回答。 Quarkus創(chuàng)建Java世界中非常熟悉的抽象(例如基于注釋的DI)。它為您啟動服務(wù)并創(chuàng)建連接池。可以使用豐富的收藏標(biāo)準(zhǔn)庫和泛型。但是,這種感覺有點像黑魔法,一旦停止工作,您會感到無助。此外,將Java代碼編譯為本地二進(jìn)制文件并不是那么簡單,您必須意識到其中的局限性和注意事項,盡管Red Hat在擴(kuò)展方面取得了很大的進(jìn)步,但并非每個Java庫都將與本地編譯兼容。 。 (預(yù)先配置為本地編譯的Java庫)。使用與本機(jī)編譯不兼容的庫(例如Guice)將需要您手動配置Graal VM。這是可能的,但并非像使用廣口瓶那樣直接。 Quarkus和Graal VM也"相對"新。因此,有許多冒險等待著。但由于是雙模式(JVM或本機(jī))。萬一本機(jī)版本停止工作,總會有一個退路,這是解決任何新出現(xiàn)問題的好方法。
另一方面,Golang僅在現(xiàn)在(存在10年后)才承認(rèn)需要泛型。 當(dāng)然,它不喜歡隱性事件的繼續(xù)。 從很多方面來說,這都是好事。 另外,盡管go社區(qū)在追趕方面確實做得很好,但是可用的工具和庫卻更少(例如,只有一個流行的阻塞MySQL驅(qū)動程序)。 另一方面,它的編譯和構(gòu)建過程非??焖?簡單。 每個golang軟件包都將為您工作,而不受Java本地平臺引入的限制。
結(jié)論
Java成為云原生,Golang并沒有像JVM那樣過度地執(zhí)行它,這是非常好的。 我相信它將來會被廣泛使用。 但是golang絕對可以打架。
因此,請謹(jǐn)慎選擇!
而且不要忘了給仙人掌澆水