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

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

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

> the apache benchmark command
Golang服務(wù)
使用稱為gin的流行的反應(yīng)式Web框架,該框架具有出色的基準(zhǔn)。
在尋找golang非阻塞MySQL驅(qū)動(dòng)程序時(shí),我一無所獲,互聯(lián)網(wǎng)上建議同時(shí)使用go-sql-driver,這就是我要使用的。
golang樣式非常明確。 一個(gè)在你臉上的態(tài)度。 主要功能啟動(dòng)服務(wù)器,配置請(qǐng)求處理程序,并打開數(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
這是一個(gè)Kotlin示例,大致遵循quarkus反應(yīng)式MySql擴(kuò)展指南。

> datasource configuration
與go版本相比,存在一些隱式東西,CDI依賴注入,使用javax注釋的聲明性路由,自動(dòng)配置解析以及數(shù)據(jù)源/連接創(chuàng)建/服務(wù)器引導(dǎo)程序。 但這是使用框架的代價(jià),它為您帶來繁重的工作,并決定了它的工作方式。 但是,它比go版本要短得多,只要我不介意黑魔法就行!
底層有一個(gè)Netty反應(yīng)式Web服務(wù)器,由Vert.x多事件循環(huán)包裝,而Vert.x反應(yīng)式MySQL驅(qū)動(dòng)程序可以通過一個(gè)線程處理多個(gè)數(shù)據(jù)庫連接。
另外,我可以使用Kotlin令人驚嘆的收藏庫來折疊一個(gè)列表,其中g(shù)o版本還沒有泛型(但即將推出),也沒有豐富的標(biāo)準(zhǔn)收藏庫,我不得不手動(dòng)編寫或生成它。
構(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ī)鏈接到我們的代碼,并作為一個(gè)單元進(jìn)行編譯。 甲骨文表示,這是驚人的,但并非沒有代價(jià),SubstrateVM的優(yōu)化次數(shù)少于HotSpot Vm,并且垃圾回收器更簡單。
執(zhí)行此操作的編譯器稱為" Graal",它與語言無關(guān),在使用Java字節(jié)碼之前,需要先將其翻譯為中間表示形式,即Truffle語言。 這非常有趣,可以在這篇文章中找到有關(guān)Graal和Truffle的詳盡解釋。
構(gòu)建Java本機(jī)圖像看起來更加復(fù)雜,速度較慢,并且生成的二進(jìn)制文件幾乎是文件的兩倍。 但這有效! 與一個(gè)Java Uber(胖)Jar相比,35M可執(zhí)行二進(jìn)制文件實(shí)際上是什么,它可以輕松地大十倍。 35MB甚至可以放在aws lambda中。
強(qiáng)調(diào)服務(wù)
我正在使用以下設(shè)置在本地計(jì)算機(jī)上運(yùn)行所有測(cè)試:

不適使用:
- MacBook Pro(15英寸,2017年)
- 2.9 GHz Intel Core i7(8核)
- 16 GB 2133 MHz LPDDR3
不適使用名為cAdvisor的工具來監(jiān)視我的容器的狀態(tài)。
場(chǎng)景
- quarkus jvm熱點(diǎn)容器
- quarkus java本機(jī)容器
- golang容器
每個(gè)都分配了以下資源
- 100MB / 0.5 CPU | 200MB / 1個(gè)CPU | 300MB / 2個(gè)CPU
我對(duì)……感興趣
- cpu / ram利用率(多核的利用率)
- cpu / ram峰值
- cpu / ram空閑
- 引導(dǎo)時(shí)間
- 響應(yīng)潛伏時(shí)間平均值/最大值
- 吞吐量(每秒請(qǐng)求數(shù))
現(xiàn)在,我將運(yùn)行許多基準(zhǔn)測(cè)試,并為每個(gè)基準(zhǔn)收集許多數(shù)據(jù)點(diǎn)。 如果有太多信息,請(qǐng)隨時(shí)跳至摘要結(jié)尾
github repo以及該實(shí)驗(yàn)的所有代碼都可以在這里找到
quarkus jvm熱點(diǎn)— 100MB / 0.5 CPU
- 閑置CPU使用率0.25%
- 空閑ram使用情況66MB
- 自舉時(shí)間6s

> CPU usage during bootstrap. ( a spike , probably jit + launching JVM )
第一輪壓力測(cè)試(Cold JVM)
令人驚訝的是,沒有失敗的請(qǐng)求。

> CPU usage during stress.

> RAM launched from 60 to almost 100 MB (limit) and stayed there.
第2輪壓力測(cè)試(溫暖的JVM)
quarkus jvm熱點(diǎn)— 200MB / 1個(gè)CPU
- 閑置CPU使用率0.13%
- 空閑ram使用情況66MB
- 引導(dǎo)時(shí)間3s

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

> CPU / RAM usage under stress

> Surprisingly the JVM did not eat all the allocated 200MB and 140MB was sufficient
第2輪壓力測(cè)試(溫暖的JVM)
quarkus jvm熱點(diǎn)— 300MB / 2 CPU
- 空閑cpu / ram與以前的方案相同
- 引導(dǎo)時(shí)間1.1s(NICE)

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

> Good CPU utilzation

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

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

> CPU reached 0.5 limit as expected

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

> cpu utilization during stress

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

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