掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
Peter Nagy 和我在 2020 年 8 月的甲骨文 Groundbreakers Tour 2020 LATAM 大會上發(fā)表一篇論文,題為《Go Java, Go!》。我們在本文中提出一個(gè)問題:“Java 微服務(wù)能像 Go 一樣快嗎?”為此,我們創(chuàng)建了一系列微服務(wù)并進(jìn)行了基準(zhǔn)測試,并在會議上展示了我們的成果。但其中還有不少可以探索的空間,因此我們決定將在本文中進(jìn)一步探討。

成都創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營銷、網(wǎng)站重做改版、陵城網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5場景定制、電子商務(wù)商城網(wǎng)站建設(shè)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為陵城等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
1. 背景介紹
我們希望通過實(shí)驗(yàn)了解 Java 微服務(wù)在運(yùn)行速度上能否達(dá)到 Go 微服務(wù)的水平。目前,軟件行業(yè)普遍認(rèn)為 Java 已經(jīng)過于陳舊、緩慢且無聊。而 Go 則成了快速、嶄新以及酷炫的代名詞。真是這樣嗎?我們想從數(shù)據(jù)的角度看看這樣的印象是否站得住腳。
我們希望建立一個(gè)公平的測試,因此創(chuàng)建了一項(xiàng)非常簡單的微服務(wù),其中不含外部依賴項(xiàng)(例如數(shù)據(jù)庫),而且代碼路徑非常短(僅處理字符串)。我們在其中包含有指標(biāo)及日志記錄,因?yàn)樗坪跻磺形⒎?wù)都或多或少包含這些內(nèi)容。另外,我們使用了小型、輕量化的框架(Helidon for Java 以及 Go-Kit for Go),兩袖清風(fēng)嘗試了 Java 的純 JAX-RS。我們也嘗試了不同版本的 Java 與不同 JVM。我們對堆大小及垃圾收集機(jī)制做出基本調(diào)整,并在測試運(yùn)行前對微服務(wù)進(jìn)行了預(yù)熱。
2. Java 的發(fā)展歷史
Java 由 Sun Microsystems 公司開發(fā),后被甲骨文所收購。其 1.0 版本發(fā)布于 1996 年,目前的最新版本是 2020 年的 Java 15。Java 當(dāng)前的主要設(shè)計(jì)目標(biāo),在于實(shí)現(xiàn) Java 虛擬機(jī)及字節(jié)碼的可移植性,外加帶有垃圾回收的內(nèi)存管理機(jī)制。時(shí)至今日,Java 作為一種開源語言仍是全球最受歡迎的語言選項(xiàng)之一(根據(jù) StackOverflow 及 TIOBE 等來源)。
下面來聊聊“Java 問題”。人們對于它速度緩慢的印象其實(shí)更多是種固有觀念,而不再適應(yīng)當(dāng)下的事實(shí)。如今的 Java 甚至擁有不少性能敏感區(qū),包括存儲對象數(shù)據(jù)堆、用于管理堆的垃圾收集器,外加準(zhǔn)時(shí)化(JIT)編譯器。
多年以來,Java 曾先后使用多種不同的垃圾收集算法,包括串行、并行、并發(fā)標(biāo)記 / 清除、G1 以及最新的 ZGC 垃圾收集器。現(xiàn)代垃圾收集器旨在盡可能減少垃圾收集造成的暫停時(shí)長。
甲骨文實(shí)驗(yàn)室開發(fā)出一款名為 GraalVM 的 Java 虛擬機(jī),其使用 Java 編寫而成,具有新的編譯器外加一系列令人興奮的新功能,包括可以將 Java 字節(jié)碼轉(zhuǎn)換為無需 Java 虛擬機(jī)即可運(yùn)行的原生鏡像等。
3. Go 的發(fā)展歷史
Go 語言由谷歌的 Robert Griesemer、Rob Pike 以及 Ken Thomson 開發(fā)而成。他們幾位也是 UNIX、B、C、Plan9 以及 UNIX 視窗系統(tǒng)等項(xiàng)目的主要貢獻(xiàn)者。作為一種開源語言,Go 的 1.0 版本發(fā)布于 2012 年,2020 年最新版本為 1.15。Go 語言的本體、采用速度以及工具生態(tài)系統(tǒng)的發(fā)展都相當(dāng)迅猛。
Go 語言受到 C、Python、JavaScript 以及 C++ 的影響,已經(jīng)成為一種理想的高性能網(wǎng)絡(luò)與多處理語言。
截至我們發(fā)布主題演講時(shí),StackOverflow 上共有 27872 個(gè)帶有“Go”標(biāo)簽的問題,Java 則為 1702730 個(gè)。
Go 是一種靜態(tài)類型的編譯語言,其語法類似于 C,且擁有內(nèi)存安全、垃圾回收、結(jié)構(gòu)化類型以及 CSP 樣式并發(fā)(通信順序過程)等功能特性。Go 還使用名為 goroutine 的輕量級進(jìn)程(并非操作系統(tǒng)線程),外加各進(jìn)程間用于通信的通道(類型化,F(xiàn)IFO)。Go 語言不提供競態(tài)條件保護(hù)。
Go 是眾多 CNCF 項(xiàng)目的首選語言,例如 Kubernetes、Istio、Prometheus 以及 Grafana 等皆是由 Go 語言編寫而成(或者大部分是)。
Go 語言在設(shè)計(jì)上強(qiáng)調(diào)快速構(gòu)建與快速執(zhí)行。到底是兩個(gè)空格還是四個(gè)空格?Go 語言表示不用麻煩,無所謂。
與 Java 相比,我將個(gè)人體會到的 Go 語言優(yōu)勢整理如下:
但 Go 當(dāng)然也不完美。與 Java 相比,我認(rèn)為 Go 存在以下問題:
4. 負(fù)載測試方法
我們使用 JMeter 進(jìn)行負(fù)載測試。測試多次調(diào)用服務(wù),并收集關(guān)于響應(yīng)時(shí)間、吞吐量(每秒事務(wù))以及內(nèi)存使用情況的數(shù)據(jù)。在 Go 方面,我們主要收集常駐集大小,Java 方面則主要跟蹤原生內(nèi)存。
在多項(xiàng)測試中,我們都將 JMeter 與被測應(yīng)用程序放置在同一臺計(jì)算機(jī)上運(yùn)行。經(jīng)過對比,我們發(fā)現(xiàn)在其他機(jī)器上運(yùn)行 JMeter 幾乎不會對結(jié)果造成任何影響。后續(xù)在將應(yīng)用程序部署到 Kubernetes 中時(shí),我們會考慮將 JMeter 運(yùn)行在集群之外的遠(yuǎn)程計(jì)算機(jī)之上。
在進(jìn)行測試之前,我們使用 1000 項(xiàng)服務(wù)調(diào)用對應(yīng)用程序進(jìn)行了預(yù)熱。
應(yīng)用程序本體的源代碼以及負(fù)載測試定義請參見 GitHub repo:
https://github.com/markxnelson/go-java-go
5. 首輪測試
在第一輪測試中,我們在小型機(jī)器上運(yùn)行測試,搭載了 2.5 GHz 雙核英特爾酷睿 i7 的筆記本電腦,具有 16 GB 內(nèi)存并運(yùn)行 MacOS。我們運(yùn)行了 100 個(gè)線程,每個(gè)線程 10000 個(gè)循環(huán),再額外加個(gè) 10 秒的啟動時(shí)間。Java 應(yīng)用程序運(yùn)行在 JDK 11 與 Helidon 2.0.1 之上。Go 應(yīng)用程序則使用 Go 1.13.3 進(jìn)行編譯。
測試結(jié)果如下:
| 應(yīng)用程序 | 日志記錄 | 預(yù)熱 | 平均響應(yīng)時(shí)間(毫秒) | 事務(wù) /秒 | 內(nèi)存(RSS)(開始/結(jié)束) |
|---|---|---|---|---|---|
| Golang | 是 | 否 | 5.79 | 15330.60 | 5160KB / 15188KB |
| Golang | 否 | 否 | 4.18 | 20364.11 | 5164KB / 15144KB |
| Golang | 否 | 是 | 3.97 | 21333.33 | 10120KB / 15216KB |
| Java (Helidon) | 是 | 否 | 12.13 | 8168.15 | 296376KB / 427064KB; 提交 = 169629KB +15976KB (NMT); 保留 =1445329KB +5148KB (NMT) |
| Java (Helidon) | 否 | 否 | 5.13 | 17332.82 | 282228KB / 430264KB; 保留 =1444264KB +6280KB; 提交 =166632KB +15884KB |
| Java (Helidon) | 否 | 是 | 4.84 | 18273.18 | 401228KB / 444556KB |
我們宣布,Go 成為首輪測試的獲勝者!
以下為根據(jù)這些結(jié)果得出的觀察結(jié)論:
6. GraalVM 原生鏡像
GraalVM 提供原生鏡像功能,使您能夠使用 Java 應(yīng)用程序并在實(shí)質(zhì)上將其編譯為原生可執(zhí)行代碼。根據(jù) GraalVM 項(xiàng)目網(wǎng)站的介紹:
該可執(zhí)行文件包含應(yīng)用程序類、依賴項(xiàng)中的類、運(yùn)行時(shí)庫類以及 JDK 中的靜態(tài)鏈接原生代碼。其并非運(yùn)行在 Java 虛擬機(jī)之上,而是包含必要組件,例如來自不同運(yùn)行時(shí)系統(tǒng)(也被稱為「基層虛擬機(jī)」)的內(nèi)存管理、線程調(diào)度等功能?;鶎犹摂M機(jī)代表的是各運(yùn)行時(shí)組件(例如反優(yōu)化器、垃圾收集器、線程調(diào)度等)。
在添加 GraalVM 原生鏡像(原生鏡像由 GraalVM EE 20.1.1——JDK 11 構(gòu)建而成)之后,首輪測試結(jié)果如下:
| 應(yīng)用程序 | 日志記錄 | 預(yù)熱 | 平均響應(yīng)時(shí)間(毫秒) | 事務(wù) /秒 | 內(nèi)存(RSS)(開始/結(jié)束) |
|---|---|---|---|---|---|
| Golang | 是 | 否 | 5.79 | 15330.60 | 5160KB / 15188KB |
| Golang | 否 | 否 | 4.18 | 20364.11 | 5164KB / 15144KB |
| Golang | 否 | 是 | 3.97 | 21333.33 | 10120KB / 15216KB |
| Java (Helidon) | 是 | 否 | 12.13 | 8168.15 | 296376KB / 427064KB; 提交 = 169629KB +15976KB (NMT); 保留 =1445329KB +5148KB (NMT) |
| Java (Helidon) | 否 | 否 | 5.13 | 17332.82 | 282228KB / 430264KB; 保留 =1444264KB +6280KB; 提交 =166632KB +15884KB |
| Java (Helidon) | 否 | 是 | 4.84 | 18273.18 | 401228KB / 444556KB |
| Native Image | 是 | 否 | 12.01 | 7748.27 | 18256KB / 347204KB |
| Native Image | 否 | 否 | 5.59 | 15753.24 | 169765KB / 347100KB |
| Native Image | 否 | 是 | 5.22 | 17837.19 | 127436KB / 347132KB |
在這種情況下,與運(yùn)行在 JVM 上的應(yīng)用程序相比,我們發(fā)現(xiàn)使用 GraalVM 原生鏡像并不會在吞吐量或者響應(yīng)時(shí)間等層面帶來任何實(shí)質(zhì)性的改善,但內(nèi)存占用量確實(shí)有所減少。
以下是測試期間的響應(yīng)時(shí)間圖表:
首輪響應(yīng)時(shí)間圖
請注意,在所有三種 Java 變體當(dāng)中,第一批請求的響應(yīng)時(shí)間要長得多(藍(lán)線相較于左軸的高度)而且在各項(xiàng)測試中,我們還看到一些峰值,其可能是由垃圾收集或優(yōu)化所引起。
7. 第二輪測試
接下來,我們決定在更大的計(jì)算機(jī)上運(yùn)行測試。在本輪中,我們使用臺具有 36 個(gè)核心(每核心雙線程)、256 GB 內(nèi)存的計(jì)算機(jī),并配合 Oracle Linux 7.8 操作系統(tǒng)。
與第一輪一樣,我們?nèi)匀皇褂?100 個(gè)線程、每線程 10000 個(gè)循環(huán),10 秒啟動時(shí)間以及相同版本的 Go、Java、Helidon 以及 GraalVM。
下面來看結(jié)果:
| 應(yīng)用程序 | 日志記錄 | 預(yù)熱 | 平均響應(yīng)時(shí)間(毫秒) | 事務(wù) /秒 | 內(nèi)存(RSS)(開始/結(jié)束) |
|---|---|---|---|---|---|
| 原生鏡像 | 是 | 否 | 5.61 | 14273.48 | 28256KB / 1508600KB |
| 原生鏡像 | 否 | 否 | 0.25 | 82047.92 | 29368KB / 1506428KB |
| 原生鏡像 | 否 | 是 | 0.25 | 82426.64 | 1293216KB / 1502724KB |
| Golang | 是 | 否 | 4.72 | 18540.49 | 132334KB / 72433KB |
| Golang | 否 | 否 | 1.69 | 37949.22 | 12864KB / 70716KB |
| Golang | 否 | 是 | 1.59 | 39227.99 | 16764KB / 76996KB |
| Java (Helidon) | 是 | 否 | 7.38 | 11216.42 | 318545KB / 529848KB |
| Java (Helidon) | 否 | 否 | 0.40 | 74827.90 | 307672KB / 489568KB |
| Java (Helidon) | 否 | 是 | 0.38 | 76306.75 | 398156KB / 480460KB |
我們宣布,GraalVM 原生鏡像成為第二輪測試的贏家!
下面來看本輪測試的響應(yīng)時(shí)間圖:
啟用日志記錄,但未經(jīng)預(yù)熱的測試運(yùn)行響應(yīng)時(shí)間。
不使用日志記錄也未經(jīng)預(yù)熱的測試運(yùn)行響應(yīng)時(shí)間。
經(jīng)過預(yù)熱,但未使用日志記錄的測試運(yùn)行響應(yīng)時(shí)間
第二輪的觀察結(jié)果:
8. 第三輪測試:Kubernetes
在第三輪中,我們決定在 Kubernetes 集群上運(yùn)行應(yīng)用程序,借此模擬更為自然的微服務(wù)運(yùn)行時(shí)環(huán)境。
在本輪中,我們使用包含三個(gè)工作節(jié)點(diǎn)的 Kubernets 1.16.8 集群,每個(gè)工作節(jié)點(diǎn)中包含兩個(gè)核心(各對應(yīng)兩個(gè)線程)、14 GB 內(nèi)存以及 Oracle Linux 7.8。在某些測試中,我們在變體上運(yùn)行一個(gè) Pod;在其他一些測試中,我們則運(yùn)行一百個(gè) Pod。
應(yīng)用程序訪問通過 Traefik 入口控制器實(shí)現(xiàn),其中 JMeter 運(yùn)行在 Kubernetes 集群之外。在某些測試中,我們也會嘗試使用 ClusterIP 并在集群內(nèi)運(yùn)行 JMeter。
與之前的測試一樣,我們使用 100 個(gè)線程、每線程 10000 個(gè)循環(huán),外加 10 秒啟動時(shí)間。
以下是各個(gè)變體的容器大小:
以下為本輪測試結(jié)果:
響應(yīng)時(shí)間圖表:
Kubernetes 測試中的響應(yīng)時(shí)間
在本輪中,可以看到 Go 有時(shí)更快,而 GraalVM 原生鏡像也經(jīng)常取得領(lǐng)先,但二者的差異很?。ㄒ话愕陀?5%)。
9. 測試結(jié)論
縱觀幾輪測試與結(jié)果,我們得出了以下結(jié)論:
10. 未來展望

我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流