LinkedIn的Java 11遷移之旅

banq發表於2022-06-17

LinkedIn在2018年底開始研究Java 11,當時,Java 9、10和11在社群中還不是超級流行。作為一個軼事,在2019年底的Oracle Code One會議上,一些會議詢問與會者他們的產品是否在使用Java 9或更高版本,其中只有約20%的人表示他們在使用;也很少有大公司採用Java 11。

在LinkedIn,應用程式使用四種主要型別的框架之一。Jetty、Play、Samza或Hadoop。其中,只有Jetty的版本(在LinkedIn內部)與Java 11相容,所以這篇部落格將主要介紹我們的Jetty應用程式的遷移情況。值得慶幸的是,僅Jetty應用就佔了1000多個微服務和60%以上的生產份額。

準備工作可以分為三部分:升級構建框架,做效能測試,以及自動化遷移。

構建框架
準備工作的第一步是讓我們的程式碼用Java 11構建。在LinkedIn,我們使用的是多版本策略,所以每個應用程式和庫都是單獨構建的,並有自己的版本庫。這意味著超過2000個倉庫(應用程式+庫)需要將它們的構建改變為Java 11。

對於我們的構建系統,我們需要升級到Gradle 5或更高版本,以便與Java 11的升級相容。值得慶幸的是,我們的構建工具團隊很清楚這一要求,並且已經開始工作,將構建系統遷移到Gradle 5。

在看到有可能讓我們的應用程式在Java 11下執行後,我們的團隊迅速過渡到早期採用者測試。

效能測試
我們挑選了20個應用程式作為早期採用者,用Java 11對它們進行測試,主要是使用G1垃圾收集器,因為我們80%的應用程式都使用了G1(剩下的20%大部分是CMS)。我們的選擇標準優先考慮具有較大堆尺寸、較高主機數和較高QPS的應用程式,有狀態和無狀態的應用程式都被選中。

總的來說,結果是積極的,沒有應用程式在升級到Java 11後出現效能下降。最好的案例顯示效能提高(在延遲和吞吐量方面)高達200%。這些效能提升主要體現在G1、Shenandoah和ZGC上。CMS的效能沒有變化。聽說JVM效能提高是一回事,但在我們廣泛的應用中瞭解和見證它的第一手資料則完全是另一回事。

自動化
除了改變2000多個儲存庫的構建過程之外,還需要改變1000多個應用程式的執行時間,這對一個很小的工作組來說是一個很高的目標。值得慶幸的是,自動化在這裡真正拯救了我們 我們能夠將遷移到Java 11所需的許多變化自動化。雖然這並沒有給我們帶來100%的成功率,但將任何比例的資源庫遷移到Java 11的自動化都會使工作量變得更容易接受。

在對我們的基礎設施做了一些小改動後,我們有可能改變資源庫的構建系統以使用Java 11。然後我們能夠在測試環境中觸發大規模的Java 11構建,以找出需要解決的問題。毫無疑問,這是我們在Java 11遷移過程中最重要的功能和學習之一。這種測試讓我們發現了大量的邊緣案例以及幾個主要的挑戰。以下是我們發現的一些主要挑戰。

JDK交叉相容問題
第一個也是最緊迫的問題是Java 8和Java 11之間的交叉相容問題。我們意識到,為公司完成這次升級需要多年時間,這意味著我們將處於過渡狀態,JDK 8和JDK 11都將使用一段時間。LinkedIn執行的是多版本原始碼控制,這意味著我們需要確保每個版本庫都能同時適用於Java 8和Java 11的上游。我們稱之為交叉相容而不是向後相容(因為位元組碼級別)的原因是,我們也發現了一些案例,程式碼可以在Java 8上編譯,但在Java 11上卻無法正常執行。這些情況包括刪除JavaEE庫,改變預設的類載入器型別,以及Java 11中更嚴格的類cast。

我們發現有太多的問題需要單獨解決,所以我們決定使用"--release 8 "標誌,以使Java 11編譯器編譯到Java 8級別的位元組碼,並限制新API的使用。這樣做的壞處是不能使用新的API和語言特性,如Set.of()和var關鍵字。然而,好處是,我們能夠更容易地保持Java 8和11之間的相容性,這是團隊一致同意的權衡。

移除庫
JavaEE庫已從JDK 11中刪除,但它們在我們的程式碼庫中被廣泛使用。這些庫中的許多都有開放原始碼的替代品。

我們不得不在這裡做出決定,是讓 repo 的所有者手動替換這些庫的例項,還是將其新增到我們的構建工具鏈中。我們決定,對於我們的工作團隊來說,移除這些使用的成本太高了,所以我們決定在構建工具鏈中預設新增一個靜態的最終版本的JavaEE庫。這些庫是相對輕量級的,所以把它們打上補丁並不是什麼大問題。

JVM選項的變化
JVM選項在Java 8和11之間也有很大的變化。有幾個選項被淘汰了,而其他選項則被廢棄,以支援更新的選項。對於大多數選項,我們使用了一個名為JaCoLine的開源服務,它可以幫助刪除過時的選項。GC日誌選項是由於JEP 271而得到重大修改的一組選項之一。在意識到日誌會看起來完全不同,而且新舊GC日誌選項之間並不總是有一個很好的對映之後,我們決定只建立一個預設選項,並要求使用者在需要時修改它。

也就是說,統一的GC日誌是邁過Java 8的另一個有力理由。它使閱讀GC日誌變得非常容易,而且它是一個可以利用來簡化很多工具的特性。

內部依賴性
LinkedIn執行在一個微服務架構上。這意味著有許多儲存庫是通過依賴關係圖相互連線的。這裡的挑戰是,如果一個依賴的 repo 沒有完成遷移,它可能會阻止一個依賴的 repo 的遷移,因為依賴的 repo 可能需要改變以相容 Java 11。這不是一個容易解決的問題。通過在依賴關係圖上使用一些圖形演算法,我們發現目標應用程式有超過25級的依賴關係。我們希望鼓勵較低層次的依賴關係首先遷移,但遵循嚴格的順序會限制遷移的速度。

最後,我們決定使用粗略的bucketing,將遷移基本上分成三部分。在每個部分中,依賴關係圖中同一級別的應用程式將被遷移。這是我們在正確性和速度之間做出的妥協,允許大多數應用程式完全不受依賴關係的阻礙,同時保持一個合適的遷移吞吐量。瞭解我們的依賴關係圖無疑是對如何做到這一點做出明智決定的關鍵。

在處理了這些路障和更多的問題之後,我們使用基礎設施的模擬測試機制反覆測試了基礎設施的變化和自動化修復,直到我們成功地自動遷移了大約一半的庫(約500)。我們也將自動化應用於應用程式,但沒有嘗試提交,因為我們要求所有者仍然要進行執行時驗證。這種執行時的驗證包括功能和非功能的約束。

問題比我們之前預計的要多,我們意識到這些變化中有幾個需要在未來的主要Java版本升級中得到解決。因此,當務之急是花一些時間來建立我們可以重複使用的高質量的基礎設施,現在Java 17已經到來,我們非常高興我們這樣做了! 

總而言之,遷移的準備工作,包括早期採用者測試、基礎設施變化、建立自動化和自動升級500個庫,花了三個季度。

遷移
實際的遷移工作計劃再進行三個季度,其中500個庫和大約1100個應用程式將被遷移到Java 11,由兩名工程師和一名技術專案經理組成的團隊負責。

由於我們在遷移前進行了徹底的測試和自動化,在整個遷移過程中,我們沒有看到太多的問題。準備工作確實得到了回報! 大多數團隊都能在幾個小時內完成遷移。

然而,我們確實看到了一些常見的執行時問題。

我們面臨的一個挑戰是,由於JVM尊重cgroup的限制,Java程式有較少的GC執行緒,因此一些應用程式的GC效能受到影響。遷移到Java 11後,這個問題在幾個應用程式中暴露出來,這些應用程式基本上都在利用LinkedIn的軟限制(cpu.shares),即可以從同一主機上閒置的 "鄰居 "應用程式中 "借用 "CPU週期。隨著cgroup限制的實施,對這些核心的訪問就會喪失。在某些情況下,需要手動增加GC執行緒的數量以保持相同的效能。

我們在所有Java 11版本中看到的另一個問題是堆外記憶體使用量的明顯增加。這似乎並不符合任何特定的操作,似乎更像是一個碎片化問題。從glibc記憶體分配器切換到mimalloc或jemalloc對這些問題有很大的幫助。

雖然這些問題一開始有點嚇人,但能夠挖掘出根本原因,找到適當的解決方案,並能夠在這篇博文中分享我們的發現,這是件好事。

在遷移期間和之後,我們試圖儘可能地測量效能。我們建立了自動化系統,利用我們的指標收集系統,以便對Java 11遷移前後的效能進行粗略測量。我們總共收集了200多個應用程式的資料,發現Java 11的P99延遲平均減少了10%,最大吞吐量平均增加了20%。值得注意的是,我們在遷移中沒有改變GC型別,以減少干擾程度並對效能進行更公平的比較。希望這些在適當的樣本量上的數字能對讀者有所幫助。

除了效能上的改進,Java 11還帶來了一些執行時的改進,比如現在開源的JFR工具。總的來說,這次遷移可以說是卓有成效的! 

未來的工作
還有很多工作要做。雖然Jetty已經完成,但我們仍然需要將剩餘的三個Java應用軌道遷移到Java 11。之後,將有可能以最小的努力啟用完整的Java 11位元組碼。此外,像ZGC、Shenandoah和Project Jigsaw這樣的新功能都可以進行試驗,看看在那裡是否有什麼好處。CMS在Java 11中也被廢棄了,並在Java 14中被移除,這意味著LinkedIn關閉CMS的使用將是另一項重大舉措。最後,Java 17已經出現,需要在未來考慮。

相關文章