• 當前位置:聯(lián)升科技 > 技術(shù)資訊 >

    Java 微服務(wù)能像 Go 一樣快嗎?

    2020-11-17    作者:Mark Nelson    來(lái)源:架構頭條    閱讀: 次
    Peter Nagy 和我在 2020 年 8 月的甲骨文 Groundbreakers Tour 2020 LATAM 大會(huì )上發(fā)表一篇論文,題為《Go Java, Go!》。我們在本文中提出一個(gè)問(wèn)題:“Java 微服務(wù)能像 Go 一樣快嗎?”為此,我們創(chuàng )建了一系列微服務(wù)并進(jìn)行了基準測試,并在會(huì )議上展示了我們的成果。但其中還有不少可以探索的空間,因此我們決定將在本文中進(jìn)一步探討。
     1. 背景介紹
    我們希望通過(guò)實(shí)驗了解 Java 微服務(wù)在運行速度上能否達到 Go 微服務(wù)的水平。目前,軟件行業(yè)普遍認為 Java 已經(jīng)過(guò)于陳舊、緩慢且無(wú)聊。而 Go 則成了快速、嶄新以及酷炫的代名詞。真是這樣嗎?我們想從數據的角度看看這樣的印象是否站得住腳。
    我們希望建立一個(gè)公平的測試,因此創(chuàng )建了一項非常簡(jiǎn)單的微服務(wù),其中不含外部依賴(lài)項(例如數據庫),而且代碼路徑非常短(僅處理字符串)。我們在其中包含有指標及日志記錄,因為似乎一切微服務(wù)都或多或少包含這些內容。另外,我們使用了小型、輕量化的框架(Helidon for Java 以及 Go-Kit for Go),兩袖清風(fēng)嘗試了 Java 的純 JAX-RS。我們也嘗試了不同版本的 Java 與不同 JVM。我們對堆大小及垃圾收集機制做出基本調整,并在測試運行前對微服務(wù)進(jìn)行了預熱。
     2. Java 的發(fā)展歷史
    Java 由 Sun Microsystems 公司開(kāi)發(fā),后被甲骨文所收購。其 1.0 版本發(fā)布于 1996 年,目前的最新版本是 2020 年的 Java 15。Java 當前的主要設計目標,在于實(shí)現 Java 虛擬機及字節碼的可移植性,外加帶有垃圾回收的內存管理機制。時(shí)至今日,Java 作為一種開(kāi)源語(yǔ)言仍是全球最受歡迎的語(yǔ)言選項之一(根據 StackOverflow 及 TIOBE 等來(lái)源)。
    下面來(lái)聊聊“Java 問(wèn)題”。人們對于它速度緩慢的印象其實(shí)更多是種固有觀(guān)念,而不再適應當下的事實(shí)。如今的 Java 甚至擁有不少性能敏感區,包括存儲對象數據堆、用于管理堆的垃圾收集器,外加準時(shí)化(JIT)編譯器。
    多年以來(lái),Java 曾先后使用多種不同的垃圾收集算法,包括串行、并行、并發(fā)標記 / 清除、G1 以及最新的 ZGC 垃圾收集器?,F代垃圾收集器旨在盡可能減少垃圾收集造成的暫停時(shí)長(cháng)。
    甲骨文實(shí)驗室開(kāi)發(fā)出一款名為 GraalVM 的 Java 虛擬機,其使用 Java 編寫(xiě)而成,具有新的編譯器外加一系列令人興奮的新功能,包括可以將 Java 字節碼轉換為無(wú)需 Java 虛擬機即可運行的原生鏡像等。
     3. Go 的發(fā)展歷史
    Go 語(yǔ)言由谷歌的 Robert Griesemer、Rob Pike 以及 Ken Thomson 開(kāi)發(fā)而成。他們幾位也是 UNIX、B、C、Plan9 以及 UNIX 視窗系統等項目的主要貢獻者。作為一種開(kāi)源語(yǔ)言,Go 的 1.0 版本發(fā)布于 2012 年,2020 年最新版本為 1.15。Go 語(yǔ)言的本體、采用速度以及工具生態(tài)系統的發(fā)展都相當迅猛。
    Go 語(yǔ)言受到 C、Python、JavaScript 以及 C++ 的影響,已經(jīng)成為一種理想的高性能網(wǎng)絡(luò )與多處理語(yǔ)言。
    截至我們發(fā)布主題演講時(shí),StackOverflow 上共有 27872 個(gè)帶有“Go”標簽的問(wèn)題,Java 則為 1702730 個(gè)。
    Go 是一種靜態(tài)類(lèi)型的編譯語(yǔ)言,其語(yǔ)法類(lèi)似于 C,且擁有內存安全、垃圾回收、結構化類(lèi)型以及 CSP 樣式并發(fā)(通信順序過(guò)程)等功能特性。Go 還使用名為 goroutine 的輕量級進(jìn)程(并非操作系統線(xiàn)程),外加各進(jìn)程間用于通信的通道(類(lèi)型化,FIFO)。Go 語(yǔ)言不提供競態(tài)條件保護。
    Go 是眾多 CNCF 項目的首選語(yǔ)言,例如 Kubernetes、Istio、Prometheus 以及 Grafana 等皆是由 Go 語(yǔ)言編寫(xiě)而成(或者大部分是)。
    Go 語(yǔ)言在設計上強調快速構建與快速執行。到底是兩個(gè)空格還是四個(gè)空格?Go 語(yǔ)言表示不用麻煩,無(wú)所謂。
    與 Java 相比,我將個(gè)人體會(huì )到的 Go 語(yǔ)言?xún)?yōu)勢整理如下:
    更易于實(shí)現函數模式,例如復合、純函數、不可變狀態(tài)等。
    樣板代碼少得多(但客觀(guān)上仍然太多)。
    Go 語(yǔ)言仍處于生命周期早期,因此沒(méi)什么向下兼容壓力——改進(jìn)道路較為平坦。
    Go 代碼可編譯為原生靜態(tài)鏈接的二進(jìn)制文件——無(wú)虛擬機層——二進(jìn)制文件中包含程序運行所需要的一切,因此更適合“從零開(kāi)始”的容器。
    體積更小、啟動(dòng)速度快、執行速度快。
    無(wú) OOP、繼承、泛型、斷言、指針算術(shù)。
    括號較少,例如可以實(shí)現為 if x > 3 { whatever }
    強制執行,沒(méi)有循環(huán)依賴(lài)性,不存在未使用的變量或導入,沒(méi)有隱式類(lèi)型轉換。
    但 Go 當然也不完美。與 Java 相比,我認為 Go 存在以下問(wèn)題:
    工具生態(tài)系統還不成熟,特別是依賴(lài)項管理方面雖有多種選擇,但還都不完美。在非開(kāi)源開(kāi)發(fā)方面,Go 模塊在依賴(lài)項管理上優(yōu)勢明顯,但由于存在某些兼容性問(wèn)題,其采用率仍不算特別高。
    構建具有新的 / 更新依賴(lài)項的代碼時(shí)非常緩慢(例如 Maven 著(zhù)稱(chēng)的「下載互聯(lián)網(wǎng)」問(wèn)題)。
    導入會(huì )將代碼綁定至 repo,導致代碼移動(dòng)非常困難。
    IDE 非常適合編程、文檔查找與自動(dòng)補全等功能,但卻難以進(jìn)行調試及概要分析等。
    指針!我以為二十一世紀之前就可以告別這東西了,但 Go 里面還有!好在至少已經(jīng)沒(méi)有指針算法了。
    沒(méi)有 Java 那樣的 try/catch 異常(最終總是要用到 if err != nil),也沒(méi)有列表、映射函數等函數風(fēng)格的原語(yǔ)。
    某些基本算法仍然缺失,所以用戶(hù)往往只能自行編寫(xiě)。最近我就編寫(xiě)了一些代碼,用 sloe 對兩個(gè)字符串(列表)進(jìn)行比較以及轉換。在函數語(yǔ)言中,我們完全可以使用 map 等內置算法完成。
    沒(méi)有動(dòng)態(tài)鏈接!如果要在靜態(tài)鏈接代碼當中使用 GPL 等許可,就會(huì )很不方便。
    用于調整執行、垃圾收集、概要分析或者優(yōu)化算法的選項很少。Java 擁有數百種垃圾收集調整選項,相比之下,Go 只有一項。
     4. 負載測試方法
    我們使用 JMeter 進(jìn)行負載測試。測試多次調用服務(wù),并收集關(guān)于響應時(shí)間、吞吐量(每秒事務(wù))以及內存使用情況的數據。在 Go 方面,我們主要收集常駐集大小,Java 方面則主要跟蹤原生內存。
    在多項測試中,我們都將 JMeter 與被測應用程序放置在同一臺計算機上運行。經(jīng)過(guò)對比,我們發(fā)現在其他機器上運行 JMeter 幾乎不會(huì )對結果造成任何影響。后續在將應用程序部署到 Kubernetes 中時(shí),我們會(huì )考慮將 JMeter 運行在集群之外的遠程計算機之上。
    在進(jìn)行測試之前,我們使用 1000 項服務(wù)調用對應用程序進(jìn)行了預熱。
    應用程序本體的源代碼以及負載測試定義請參見(jiàn) GitHub repo:
    https://github.com/markxnelson/go-java-go
     5. 首輪測試
    在第一輪測試中,我們在小型機器上運行測試,搭載了 2.5 GHz 雙核英特爾酷睿 i7 的筆記本電腦,具有 16 GB 內存并運行 MacOS。我們運行了 100 個(gè)線(xiàn)程,每個(gè)線(xiàn)程 10000 個(gè)循環(huán),再額外加個(gè) 10 秒的啟動(dòng)時(shí)間。Java 應用程序運行在 JDK 11 與 Helidon 2.0.1 之上。Go 應用程序則使用 Go 1.13.3 進(jìn)行編譯。
    測試結果如下:
    應用程序 日志記錄 預熱 平均響應時(shí)間(毫秒) 事務(wù) /秒 內存(RSS)(開(kāi)始/結束)
    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 成為首輪測試的獲勝者!
    以下為根據這些結果得出的觀(guān)察結論:
    日志記錄似乎是影響性能的主要問(wèn)題,特別是 java.util.logging。因此,我們在啟用與禁用日志記錄兩種條件下進(jìn)行了測試。我們還注意到,Go 應用程序性能主要受到日志記錄的影響。
    即使對于如此簡(jiǎn)單的小型應用程序,Java 版本的內存占用量也明顯更大。
    預熱對 JVM 產(chǎn)生了很大影響——我們知道 JVM 在運行過(guò)程中會(huì )進(jìn)行優(yōu)化,因此預熱對 Java 應用程序特別重要。
    在此測試中,我們還比較了不同的執行模型——Go 應用程序被編譯為原生可執行二進(jìn)制文件,而 Java 應用程序被編譯為字節碼,而后虛擬機上運行。我們還決定引入 GraalVM 原生鏡像,保證 Java 應用程序的執行環(huán)境更接近 Go 應用程序。
     6. GraalVM 原生鏡像
    GraalVM 提供原生鏡像功能,使您能夠使用 Java 應用程序并在實(shí)質(zhì)上將其編譯為原生可執行代碼。根據 GraalVM 項目網(wǎng)站的介紹:
    該可執行文件包含應用程序類(lèi)、依賴(lài)項中的類(lèi)、運行時(shí)庫類(lèi)以及 JDK 中的靜態(tài)鏈接原生代碼。其并非運行在 Java 虛擬機之上,而是包含必要組件,例如來(lái)自不同運行時(shí)系統(也被稱(chēng)為「基層虛擬機」)的內存管理、線(xiàn)程調度等功能?;鶎犹摂M機代表的是各運行時(shí)組件(例如反優(yōu)化器、垃圾收集器、線(xiàn)程調度等)。
    在添加 GraalVM 原生鏡像(原生鏡像由 GraalVM EE 20.1.1——JDK 11 構建而成)之后,首輪測試結果如下:
    應用程序 日志記錄 預熱 平均響應時(shí)間(毫秒) 事務(wù) /秒 內存(RSS)(開(kāi)始/結束)
    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
    在這種情況下,與運行在 JVM 上的應用程序相比,我們發(fā)現使用 GraalVM 原生鏡像并不會(huì )在吞吐量或者響應時(shí)間等層面帶來(lái)任何實(shí)質(zhì)性的改善,但內存占用量確實(shí)有所減少。
    以下是測試期間的響應時(shí)間圖表:

    響應時(shí)間圖
    請注意,在所有三種 Java 變體當中,第一批請求的響應時(shí)間要長(cháng)得多(藍線(xiàn)相較于左軸的高度)而且在各項測試中,我們還看到一些峰值,其可能是由垃圾收集或優(yōu)化所引起。
     7. 第二輪測試
    接下來(lái),我們決定在更大的計算機上運行測試。在本輪中,我們使用臺具有 36 個(gè)核心(每核心雙線(xiàn)程)、256 GB 內存的計算機,并配合 Oracle Linux 7.8 操作系統。
    與第一輪一樣,我們仍然使用 100 個(gè)線(xiàn)程、每線(xiàn)程 10000 個(gè)循環(huán),10 秒啟動(dòng)時(shí)間以及相同版本的 Go、Java、Helidon 以及 GraalVM。
    下面來(lái)看結果:
    應用程序 日志記錄 預熱 平均響應時(shí)間(毫秒) 事務(wù) /秒 內存(RSS)(開(kāi)始/結束)
    原生鏡像 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)家!
    下面來(lái)看本輪測試的響應時(shí)間圖:

    日志記錄,但未經(jīng)預熱的測試運行響應時(shí)間

    用日志記錄也未經(jīng)預熱的測試運行響應時(shí)間

    預熱,但未使用日志記錄的測試運行響應時(shí)間
    第二輪的觀(guān)察結果:
    Java 變體在本輪測試中的性能表現大幅提升,而且在不使用日志記錄的情況下性能遠優(yōu)于 Go。
    與 Go 相比,Java 似乎更擅長(cháng)使用硬件上的多個(gè)核心與執行線(xiàn)程——這是因為 Go 本身主要作為系統及網(wǎng)絡(luò )編程語(yǔ)言存在,而且發(fā)展周期相對較短,因此在成熟度及優(yōu)化水平上不及 Java 也很正常。
    有趣的是,Java 誕生之時(shí)多核心處理器并不常見(jiàn),而 Go 誕生時(shí)多核處理器已經(jīng)成為行業(yè)標準。
    具體來(lái)看,Java 似乎成功將日志記錄移交給其他線(xiàn)程 / 核心,因此極大減弱了其對性能的影響。
    本輪最佳性能來(lái)自 GraalVM 原生鏡像,其平均響應時(shí)間為 0.25 毫秒,每秒可執行 82426 項事務(wù);Go 的最佳結果為 1.59 毫秒外加每秒 39227 項事務(wù),而其內存占用量比前者高出兩個(gè)數量級!
    GraalVM 原生鏡像變體的速度要比運行在 JVM 上的同一應用程序快 30% 到 40%。
    Java 變體的響應時(shí)間更為穩定,但出現的峰值更多——我們猜測這是因為 Go 會(huì )把垃圾回收分成更多更小的批次來(lái)執行。
     8. 第三輪測試:Kubernetes
    在第三輪中,我們決定在 Kubernetes 集群上運行應用程序,借此模擬更為自然的微服務(wù)運行時(shí)環(huán)境。
    在本輪中,我們使用包含三個(gè)工作節點(diǎn)的 Kubernets 1.16.8 集群,每個(gè)工作節點(diǎn)中包含兩個(gè)核心(各對應兩個(gè)線(xiàn)程)、14 GB 內存以及 Oracle Linux 7.8。在某些測試中,我們在變體上運行一個(gè) Pod;在其他一些測試中,我們則運行一百個(gè) Pod。
    應用程序訪(fǎng)問(wèn)通過(guò) Traefik 入口控制器實(shí)現,其中 JMeter 運行在 Kubernetes 集群之外。在某些測試中,我們也會(huì )嘗試使用 ClusterIP 并在集群內運行 JMeter。
    與之前的測試一樣,我們使用 100 個(gè)線(xiàn)程、每線(xiàn)程 10000 個(gè)循環(huán),外加 10 秒啟動(dòng)時(shí)間。
    以下是各個(gè)變體的容器大?。?/div>
    Go 11.6MB
    Java/Helidon 1.41GB
    Java/Helidon JLinked 150MB
    原生鏡像 25.2MB
    以下為本輪測試結果:
    響應時(shí)間圖表:

    Kubernetes 測試中的響應時(shí)間
    在本輪中,可以看到 Go 有時(shí)更快,而 GraalVM 原生鏡像也經(jīng)常取得領(lǐng)先,但二者的差異很?。ㄒ话愕陀?5%)。
     9. 測試結論
    縱觀(guān)幾輪測試與結果,我們得出了以下結論:
    6
    Kubernetes 似乎沒(méi)有快速橫向擴展。
    Java 似乎比 Go 更關(guān)于利用全部可用核心 / 線(xiàn)程,我們發(fā)現 Java 測試期間 CPU 的利用率更高。
    在核心及內存容量更高的計算機上,Java 性能更好;在較小 / 性能較弱的計算機上,Go 性能更好。
    Go 的性能總體上更加一致,這可能是由于 Java 中的垃圾回收機制所致。
    在“生產(chǎn)規模”計算機上,Java 的運行速度與 Go 基本相當、甚至更快一點(diǎn)。
    日志記錄似乎成為 Go 及 Java 中的主要性能瓶頸。
    Java 的現代版本以及 Helidon 等新型框架在消除 / 減輕 Java 長(cháng)期存在的某些重大問(wèn)題(例如冗長(cháng)、GC 性能、啟動(dòng)時(shí)間等)擁有良好的表現。
     10. 未來(lái)展望
    經(jīng)過(guò)這輪有趣的測試,我們打算繼續探索,特別是:
    我們打算通過(guò) Kubernetes 自動(dòng)擴展做更多工作,包括引入更復雜的微服務(wù)或更高的負載以凸顯出性能上的差異。
    我們希望研究更復雜的微服務(wù)、多種服務(wù)類(lèi)型以及模式,觀(guān)察網(wǎng)絡(luò )如何影響性能,以及應如何對微服務(wù)網(wǎng)絡(luò )進(jìn)行調優(yōu)。
    我們還打算深挖日志記錄問(wèn)題,了解解決此瓶頸的方法。
    我們希望查看目標代碼并比較當前正在執行的實(shí)際指令,看看能否在代碼路徑中做出進(jìn)一步優(yōu)化。
    我們希望了解 JMeter 能否在不成為瓶頸的同時(shí)產(chǎn)生足夠多的負載,但此次測試結果表明 JMeter 并不構成影響,而是能夠輕松跟上 Go 與 Java 實(shí)現的運行步伐。
    我們打算對容器啟動(dòng)時(shí)間、內存占用量等指標做出更詳細的測量。


    相關(guān)文章

    我們很樂(lè )意傾聽(tīng)您的聲音!
    即刻與我們取得聯(lián)絡(luò )
    成為日后肩并肩合作的伙伴。

    聯(lián)系我們

    13387904606

    地址:新余市仙女湖區仙女湖大道萬(wàn)商紅A2棟

    手機:13755589003
    QQ:122322500
    微信號:13755589003

    江西新余網(wǎng)站設計_小程序制作_OA系統開(kāi)發(fā)_企業(yè)ERP管理系統_app開(kāi)發(fā)-新余聯(lián)升網(wǎng)絡(luò )科技有限公司 贛ICP備19013599號-1   贛公網(wǎng)安備 36050202000267號   

    微信二維碼
    色噜噜狠狠一区二区三区果冻|欧美亚洲日本国产一区|国产精品无码在线观看|午夜视频在线观看一区|日韩少妇一区二区无码|伊人亚洲日韩欧美一区二区|国产在线码观看清码视频