天天看點

重塑雲上的 Java 語言

音樂無國界,但是音樂人有國界。

雲原生亦如此。雖沒有限定的程式設計語言,但應用所使用的程式設計語言已經決定了應用部署運作的行為。

Java 誕生于20年前,擁有大量優秀的企業級架構,踐行 OOP 理念,更多展現的是嚴謹以及在長時間運作條件下的穩定性和高性能。反觀如今,在要求快速疊代傳遞的雲場景下,語言的簡單性似乎成了首要的要求,而傳統的 Java 語言顯得有一些過于重量了。

本文由阿裡巴巴 JVM 團隊技術專家郁磊(花名:梁希)分享 JVM 團隊是如何面對和處理集團巨大的業務規模和複雜的業務場景的。

ElasticHeap

Java 常因為耗資源而受诟病,其中最顯著一點就是 Heap 對記憶體的占用,即便沒有請求在處理也沒有對象配置設定,程序仍然會保留完整的堆記憶體空間,保障 GC 進行配置設定記憶體和操作記憶體的快速靈活。

AJDK ZenGC/ElasticHeap 雙十一全面支援核心鍊路上百應用和數十萬執行個體。

重塑雲上的 Java 語言

JDK12 開始支援固定時間的觸發 concurrent mark 并在 remark 中收縮 Java 堆歸還記憶體的功能,然而并未解決在 stw 中增加暫停時間的問題,是以無法在每次 young GC 時做記憶體歸還。 ElasticHeap 在并發異步線程中完成記憶體處理反複 map/unmap 以及 page fault 的開銷,是以任意一次 young GC 都可以靈活的及時歸還記憶體,或重新恢複記憶體使用。

ElasticHeap 阿裡巴巴實戰

ElasticHeap場景1:可預測的流量高峰

重塑雲上的 Java 語言
重塑雲上的 Java 語言

ElasticHeap 場景 2 :單機運作多個 Java 執行個體

重塑雲上的 Java 語言

多個 Java 執行個體接受的流量任務較為随機,峰值不會重疊,在閑時可以有效降低多個執行個體整體的記憶體占用,提高部署密度。

雙11驗證核心交易系統使用 ElasticHeap 進行低功耗模式運作,大幅降低 WSS(Working Set Size) 規模的執行個體。

重塑雲上的 Java 語言

靜态編譯

很多雲上的新應用不約而同地選擇了 Go 語言,很大的原因是 Go 應用對運作時沒有依賴,靜态編譯的程式啟動速度快,也不需要通過 JIT 來預熱。在阿裡有大量 Java 代碼的前提下,我們是如何為 Java 注入這方面的能力的呢?

Java 靜态編譯技術是一種激進的 AOT 技術,通過單獨的編譯階段将 Java 程式編譯為本地代碼,在運作時無需傳統 Java 虛拟機和運作時環境,隻需作業系統類庫支援即可。其工作基本原理如下圖所示。靜态編譯技術實作了 Java 語言與原生 native 程式的“合體”,将原本的 Java 程式編譯成為了一個自舉的具有 Java 行為的原生 native 程式,由此兼有 Java 程式和原生 native 程式的優點。

JVM 團隊與 SOFAStack 團隊密切合作,在中間件應用上率先實作靜态編譯的落地。将一個應用的啟動速度從 60 秒優化到 3.8 秒,雙十一期間靜态編譯的應用運作穩定,沒有故障, GC 停頓時間在 100 毫秒,在業務允許範圍之内,記憶體占用和 RT 與傳統 Java 應用持平。

重塑雲上的 Java 語言

綜上所述,靜态編譯的應用在穩定性、資源占用、RT 響應等各方面名額與傳統 Java 應用基本持平的狀況下,将啟動時間降低了 2000% 。

Wisp2

當你用時下最酷炫的 Vert.X 開發一個簡單的 Web 服務,準備體驗一下最強的性能, QA 同學拿來一台 1C 2G 的容器讓你壓一下,你卻發現你怎麼也拼不過别人 Go 應用。研究之後發現,原來協程模型在這樣的少核心的情況下性能要好很多。是時代變了, Java 落伍了?

AJDK Wisp2 回答了這個問題: Java 同樣可以擁有高性能的協程。今年是 Wisp2 大規模上線的第一年, Wisp2 具有如下特點:

在整個Java runtime中支援了協程排程,線程(比如 Socket.getInputStream().read() )阻塞會變成更輕量的協程切換。

完全相容 Thread API ,在開啟 Wisp2 的 JDK 中,Thread.start() 實際建立的是一個協程(輕量級線程),可以類比 Go 隻提供協程關鍵字 go 而沒有暴露線程接口;我們同樣隻提供建立協程的方式,應用可以透明切換到協程。

支援 work stealing ,排程政策特别适合 web 場景,在高壓力下排程開銷極小。

在今年雙十一, Wisp 支援了上百應用,十萬級容器,其中 90% 的容器已經更新到 Wisp2 。

重塑雲上的 Java 語言

我們可以看到峰值附近, Wisp2 機器的 CPU 要低 7%( Wisp1 更低,Wisp2的取向是 RT ,是以 CPU 會高一些)左右,這主要是輕量級排程所節省的 sys CPU 。 0 點的 CPU 是相等的,這也說明一點: Wisp2 解決的是排程開銷,當 CPU 低,排程沒有壓力時是看不出差距的。

重塑雲上的 Java 語言

從 RT 角度看, Wisp2 機器的 RT 要低 20% 左右, RT 減少明顯的一個原因是這批機器的 CPU 壓力很大,協程的排程優勢更容易展現出來。這樣的優勢可以幫助系統摸高到更高的水位,整體地提高使用率而擔心 RT 過高導緻系統雪崩。

FDO

雙十一正零點相對後面幾分鐘會有一個明顯的 CPU 峰值,根據資料分析,主要原因是雙十一零點觸發了 JIT 編譯。 舉個例子,程式裡有邏輯:

if (is1111(LocalDate.now())) {

branch1

} else {

branch2

}

假設預熱時一直在走 branch2 ,那麼 JIT 有理由相信後續基本也都會走 branch2 ,而不會對 branch1編譯。在零點時,我們進入 branch1 ,此時就需要觸發退優化重新編譯方法。我們來看 AJDK 如何通過 profiling 解決這個問題。

退優化原理及其危害

JDK 運作代碼的時候,采用分層編譯的方式對 Java 方法進行動态編譯。在最高等級(峰值性能最好)的編譯中,出于性能的考慮,編譯的時候會根據收集的資訊做一些比較樂觀的假設,一旦這些假設條件不滿足了,就會出現退優化的現象。比如某個熱點方法中某段代碼僅會在雙十一中執行,那麼在預熱過程中這段代碼不會被編譯,雙十一到來時這段代碼一旦被執行,就會觸發整個方法的退優化。

發生退優化有兩個方面的負面影響,一是需要運作的方法由高效率的編譯執行變成了解釋執行,運作速度降低百倍以上;二是流量高峰期退優化的方法會很快被重新編譯,編譯線程會消耗 CPU 。是以在雙十一這種流量短時間劇增且與預熱流量不太一樣的場景下,退優化的危害會特别明顯。

通過 FDO 減少退優化

FDO 是 feedback directed optimization 的縮寫,即參考以往 JVM 運作時的編譯資訊,指導本次運作時進行更好的編譯。具體的,我們采用了兩個層面的方法來減少退優化。

将每次運作時的退優化資訊記錄到檔案中,下次運作時讀取這個檔案,在決定是否做樂觀假設的時候參考檔案中的資訊做判斷,進而減少退優化的機率。

資訊顯示出現最多的退優化與 if-else 相關,占總數量的一半以上。我們提供了一個方法根據以往出現 if-else 退優化的資訊,關閉某個路徑上所有相關的樂觀假設。

雙十一中 FDO 的效果

FDO 今年雙十一上線,目标解決兩個問題:

1、雙十一 0 點流量高峰和退優化/編譯高峰疊加造成的 CPU 使用率脈沖過高。

2、預熱效率低,壓測經過前長時間預熱後,增大流量時仍然伴随着大量的編譯及退優化。

針對第一個問題,我們收集了雙十一高峰第一分鐘的退優化/C2 編譯次數以及 CPU 資料。

可見開啟 FDO 後高峰期 C2 編譯數目減少約 45% ,退優化數目減少約 70% 。

CPU 資料上,高峰期第一分鐘内開啟 FDO 後 CPU 由約 67.5 降低到 63.1 ,降低約 7.0% 。

重塑雲上的 Java 語言

第二個目标可以通過壓測第一分鐘的 CPU 資料驗證。

開啟 FDO ,壓測第一分鐘 CPU 使用率由 66.19 降低到 60.33% ,降低約 10% 。

Grace

ZProfiler 一直是全集團排查 Java 應用各類問題的利器,而 Grace 作為其平台化的版本,對其實施了一系列的優化,從原來的單機版本到現在的 Master/Worker 架構,同時引入了任務排隊機制,在高壓力情況下對使用者的任務進行排隊進而解決 Worker 不堪重負的問題。在可維護性、拓展性、以及使用者體驗上得到了質的提升,為後續工具平台的上雲、開源事項打下了夯實的基礎。

目前已經內建了 Heap Dump 功能,在繼承 ZProfiler 功能的基礎上做了一定的優化,提升了解析引擎的版本,支援更全面的 OQL 文法等等。

重塑雲上的 Java 語言

JDK11

JDK8 作為一個經典版本,正被大規模使用,雖然從 JDK6 和 7 遷移上來有一定的陣痛,但是更新後普遍的回報是:“真香”。

OpenJDK 8的下一個穩定版本是 OpenJDK 11 。JVM 團隊自然會在這個方向上積極跟進,目前 AJDK11 支援了 AJDK8 的 Wisp2 、多租戶特性。本次雙十一的部分叢集已經上線到 JDK11 ,表現穩定。

更新 JDK11 是否會和更新 JDK8 一樣給我們帶來同樣的的驚喜呢?在 JDK11 上我們可以體驗到最新的 ZGC 。

ZGC

JDK11 引入了一個重要特性: ZGC 記憶體垃圾回收器。這個垃圾回收器号稱能夠在幾十 GB 至若幹 TB 的堆上把暫停時間保持在 10ms 以内。許多 Java 開發者苦于過去的垃圾回收器的暫停時間帶來延遲, ZGC 短暫停的特性未來無疑會成為 Java 開發者的新寵。

目前 ZGC 在 OpenJDK 中仍然處于實驗特性,而且 JDK11 尚未在産業界完全普及, JDK11 隻支援 Linux 上的 ZGC( MacOS 和 Windows 的 ZGC 預計在 2020 年 3 月釋出的 JDK14 版本才會支援),許多 Java 開發者仍然隻能垂涎欲滴,處于觀望狀态。

向來敢于吃螃蟹的我們豈能望而卻步?阿裡 JVM 團隊和資料庫團隊已經開始讓資料庫應用運作在 ZGC 上,并根據運作的效果對 ZGC 進行了相應的改進工作,包括 ZGC 的頁緩存機制優化、ZGC的觸發時機優化等等。

從 9 月開始,兩個團隊推動線上資料庫應用在 ZGC 上運作,目前已經穩定運作兩個月,并順利通過雙十一大考。線上回報的效果可喜可賀:

1、 JVM 暫停時間保持在官方的 10ms 以内;

2、 ZGC 大大改善了線上運作叢集的平均 RT 與毛刺名額。

小結

從上述的功能特性可以看到 AJDK 已經從一個傳統的 Managed Runtime 脫胎換骨。今後 AJDK 将繼續緻力于提高雲上的應用的開發體驗,通過底層的創新為上層應用提供更多的可能。

在 Dragonwell 上使用 AJDK 功能

上述的這些經過雙十一考驗的功能都将随着 Dragonwell 陸續開源和傳遞到廣大使用者手中,敬請關注。

Alibaba Dragonwell 8 是一款免費的 OpenJDK 發行版。它提供長期支援,包括性能增強和安全修複。Alibaba Dragonwell 8 目前支援 X86-64/Linux 平台,在資料中心大規模 Java 應用部署情況下, 可以大幅度提高穩定性、效率以及性能。Alibaba Dragonwell 8 是 OpenJDK 的下遊( friendly fork ),使用了和 OpenJDK 一樣的 license。Alibaba Dragonwell 8 與 Java SE 标準相容,使用者可以使用 Alibaba Dragonwell 8 開發和運作 Java 應用程式。此次開源的 Alibaba Dragonwell 8 是阿裡巴巴内部 OpenJDK 定制版 AJDK 的開源版本, AJDK 為線上電商,金融,物流做了結合業務場景的優化,運作在超大規模的,1,000,000+ 伺服器的阿裡巴巴資料中心。

近期我們正在緊密籌備 Alibaba Dragonwell 11 的 release 。 Dragonwell 11 是基于 OpenJDK 11 的 Dragonwell 發行版本,擁有更多特性,可以更多地為雲上場景賦能,子產品化更加清晰,并将獲得長期的支援,是以推薦大家關注和适時更新。