從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

石杉的架構筆記發表於2019-01-17

歡迎關注個人公眾號:石杉的架構筆記(ID:shishan100)

週一至週五早8點半!精品技術文章準時送上!

目錄

(1)大部分人對Java併發仍停留在理論階段

(2)中介軟體系統的核心機制:雙緩衝機制

(3)百萬併發的技術挑戰

(4)記憶體資料寫入的鎖機制以及序列化問題

(5)記憶體緩衝分片機制 + 分段加鎖機制

(6)緩衝區寫滿時的雙緩衝交換

(7)且慢!刷寫磁碟不是會導致鎖持有時間過長嗎?

(8)記憶體 + 磁碟並行寫機制

(9)為什麼必須要用雙緩衝機制?

(10)總結

“ 這篇文章,給大家聊聊一個百萬級併發的中介軟體系統的核心程式碼裡的鎖效能優化。很多同學都對Java併發程式設計很感興趣,學習了很多相關的技術和知識。比如volatile、Atomic、synchronized底層、讀寫鎖、AQS、併發包下的集合類、執行緒池,等等。

1、大部分人對Java併發仍停留在理論階段

很多同學對Java併發程式設計的知識,可能看了很多的書,也通過不少視訊課程進行了學習。

但是,大部分人可能還是停留在理論的底層,主要是瞭解理論,基本對併發相關的技術很少實踐和使用,更很少做過複雜的中介軟體系統。

實際上,真正把這些技術落地到中介軟體系統開發中去實踐的時候,是會遇到大量的問題,需要對併發相關技術的底層有深入的理解和掌握。

然後,結合自己實際的業務場景來進行對應的技術優化、機制優化,才能實現最好的效果。

因此,本文將從筆者曾經帶過的一個高併發中介軟體專案的核心機制出發,來看看一個實際的場景中遇到的併發相關的問題。

同時,我們也將一步步通過對應的虛擬碼演進,來分析其背後涉及到的併發的效能優化思想和實踐,最後來看看優化之後的效果。

2、中介軟體系統的核心機制:雙緩衝機制

這個中介軟體專案整體就不做闡述了,因為涉及核心專案問題。我們僅僅拿其中涉及到的一個核心機制以及對應的場景來給大家做一下說明。

其實這個例子是大量的開源中介軟體系統、大資料系統中都有涉及到的一個場景,就是:核心資料寫磁碟檔案。

比如,大資料領域裡的hadoop、hbase、elasitcsearch,Java中介軟體領域裡的redis、mq,這些都會涉及到核心資料寫磁碟檔案的問題。

而很多大型網際網路公司自研的中年間系統,同樣也會有這個場景。只不過不同的中介軟體系統,他的作用和目標是不一樣的,所以在核心資料寫磁碟檔案的機制設計上,是有一些區別的。

那麼我們公司自研的中介軟體專案,簡單來說,需要實現的一個效果是:開闢兩塊記憶體空間,也就是經典的記憶體雙緩衝機制。

然後核心資料進來全部寫第一塊緩衝區,寫滿了之後,由一個執行緒進行那塊緩衝區的資料批量刷到磁碟檔案的工作,其他執行緒同時可以繼續寫另外一塊緩衝區。

我們想要實現的就是這樣的一個效果。這樣的話,一塊緩衝區刷磁碟的同時,另外一塊緩衝區可以接受其他執行緒的寫入,兩不耽誤。核心資料寫入是不會斷的,可以持續不斷的寫入這個中介軟體系統中。

我們來看看下面的那張圖,也來了解一下這個場景。

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

如上圖,首先是很多執行緒需要寫緩衝區1,然後是緩衝區1寫滿之後,就會由寫滿的那個執行緒把緩衝區1的資料刷入磁碟檔案,其他執行緒繼續寫緩衝區2。

這樣,資料批量刷磁碟和持續寫記憶體緩衝,兩個事兒就不會耽誤了,這是中介軟體系統設計中極為常用的一個機制,大家看下面的圖。

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

3、百萬併發的技術挑戰

先給大家說一下這個中介軟體系統的背景:這是一個服務某個特殊場景下的中介軟體系統,整體是叢集部署。

然後每個例項部署的都是高配置機器,定位是單機承載併發達到萬級甚至十萬級,整體叢集足以支撐百萬級併發,因此對單機的寫入效能和吞吐要求極為高。

在超高併發的要求之下,上圖中的那個核心機制的設計就顯得尤為重要了。弄的不好,就容易導致寫入併發效能過差,達不到上述的要求。

此外在這裡多提一句,類似的這種機制在很多其他的系統裡都有涉及。比如之前一篇文章:「高併發優化實踐」10倍請求壓力來襲,你的系統會被擊垮嗎?,那裡面講的一個系統也有類似機制。

只不過不同的是,那篇文章是用這個機制來做MQ叢集整體故障時的容災降級機制,跟本文的高併發中介軟體系統還有點不太一樣,所以在設計上考慮的一些細節也是不同的。

而且,之前那篇文章的主題是講這種記憶體雙緩衝機制的一個線上問題:瞬時超高併發下的系統卡死問題。

4、記憶體資料寫入的鎖機制以及序列化問題

首先我們先考慮第一個問題,你多個執行緒會併發寫同一塊記憶體緩衝,這個肯定有問題啊!

因為記憶體共享資料併發寫入的時候,必須是要加鎖的,否則必然會有併發安全問題,導致記憶體資料錯亂。

所以在這裡,我們寫了下面的虛擬碼,先考慮一下執行緒如何寫入記憶體緩衝。

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

好了,這行程式碼弄好之後,對應著下面的這幅圖,大家看一下。

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

看到這裡,就遇到了Java併發的第一個效能問題了,你要知道高併發場景下,大量執行緒會併發寫記憶體的,你要是直接這樣加一個鎖,必然會導致所有執行緒都是序列化。

即一個執行緒加鎖,寫資料,然後釋放鎖。接著下一個執行緒幹同樣的事情。這種序列化必然導致系統整體的併發效能和吞吐量會大幅度降低的。

5、記憶體緩衝分片機制+分段枷鎖機制

因此在這裡必須要對記憶體雙緩衝機制引入分段加鎖機制,也就是將記憶體緩衝切分為多個分片,每個記憶體緩衝分片就對應一個鎖。

這樣的話,你完全可以根據自己的系統壓測結果,調整記憶體分片數量,提升鎖的數量,進而允許大量執行緒高併發寫入記憶體。

我們看下面的虛擬碼,對這塊就實現了記憶體緩衝分片機制:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

好!我們再來看看,目前為止的圖是什麼樣子的:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

這裡因為每個執行緒僅僅就是加鎖,寫記憶體,然後釋放鎖。

所以,每個執行緒持有鎖的時間是很短很短的,單個記憶體分片的併發寫入經過壓測,達到每秒幾百甚至上千是沒問題的,因此線上系統我們是單機開闢幾十個到上百個記憶體緩衝分片的。

經過壓測,這足以支撐每秒數萬的併發寫入,如果將機器資源使用的極限,每秒十萬併發也是可以支援的。

6、緩衝區寫滿時的雙緩衝交換

那麼當一塊緩衝區寫滿的時候,是不是就必須要交換兩塊緩衝區?接著需要有一個執行緒來將寫滿的緩衝區資料刷寫到磁碟檔案中?

此時的虛擬碼,大家考慮一下,是不是如下所示:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

同樣,我們通過下面的圖來看看這個機制的實現:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

7、且慢!刷寫磁碟不是會導致鎖持有時間過長嗎?

且慢,各位同學,如果按照上面的虛擬碼思路,一定會有一個問題:要是一個執行緒,他獲取了鎖,開始寫記憶體資料。

然後,發現記憶體滿了,接著直接在持有鎖的過程中,還去執行資料刷磁碟的操作,這樣是有問題的。

要知道,資料刷磁碟是很慢的,根據資料的多少,搞不好要幾十毫秒,甚至幾百毫秒。

這樣的話,豈不是一個執行緒會持有鎖長達幾十毫秒,甚至幾百毫秒?

這當然不行了,後面的執行緒此時都在等待獲取鎖然後寫緩衝區2,你怎麼能一直佔有鎖呢?

一旦你按照這個思路來寫程式碼,必然導致高併發場景下,一個執行緒持有鎖上百毫秒。刷資料到磁碟的時候,後續上百個工作執行緒全部卡在等待鎖的那個環節,啥都幹不了,嚴重的情況下,甚至又會導致系統整體呈現卡死的狀態。

8、記憶體 + 磁碟並行寫機制

所以此時正確的併發優化程式碼,應該是發現記憶體緩衝區1滿了,然後就交換兩個緩衝區。

接著直接就釋放鎖,釋放鎖了之後再由這個執行緒將資料刷入磁碟中,刷磁碟的過程是不會佔用鎖的,然後後續的執行緒都可以繼續獲取鎖,快速寫入記憶體,接著釋放鎖。

大家先看看下面的虛擬碼的優化:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

按照上面的虛擬碼的優化,此時磁碟的刷寫和記憶體的寫入,完全可以並行同時進行。

因為這裡核心的要點就在於大幅度降低了鎖佔用的時間,這是java併發鎖優化的一個非常核心的思路。

大家看下面的圖,一起來感受一下:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

9、為什麼必須要用雙緩衝機制?

其實看到這裡,大家可能或多或少都體會到了一些雙緩衝機制的設計思想了,如果只用單塊記憶體緩衝的話,那麼從裡面讀資料刷入磁碟的過程,也需要佔用鎖,而此時想要獲取鎖寫入記憶體緩衝的執行緒是獲取不到鎖的。

所以假如只用單塊緩衝,必然導致讀記憶體資料,刷入磁碟的過程,長時間佔用鎖。進而導致大量執行緒卡在鎖的獲取上,無法獲取到鎖,然後無法將資料寫入記憶體。這就是必須要在這裡使用雙緩衝機制的核心原因。

10、總結

最後做一下總結,本文從筆者團隊自研的百萬併發量級中介軟體系統的核心機制出發,給大家展示了Java併發中加鎖的時候:

如何利用雙緩衝機制 記憶體緩衝分片機制 分段加鎖機制 磁碟 + 記憶體並行寫入機制 高併發場景下大幅度優化多執行緒對鎖的序列化爭用問題 長時間佔用鎖的問題

其實在很多開源的優秀中介軟體系統中,都有很多類似的Java併發優化的機制,主要就是應對高併發的場景下大幅度的提升系統的併發效能以及吞吐量。大家如果感興趣,也可以去了解閱讀一下相關的底層原始碼。

End

如有收穫,請幫忙轉發,您的鼓勵是作者最大的動力,謝謝!

一大波微服務、分散式、高併發、高可用的原創系列文章正在路上

歡迎掃描下方二維碼,持續關注:

從團隊自研的百萬併發中介軟體系統的核心設計看Java併發效能優化【石杉的架構筆記】

石杉的架構筆記(id:shishan100)

十餘年BAT架構經驗傾囊相授

推薦閱讀:

1、拜託!面試請不要再問我Spring Cloud底層原理

2、【雙11狂歡的背後】微服務註冊中心如何承載大型系統的千萬級訪問?

3、【效能優化之道】每秒上萬併發下的Spring Cloud引數優化實戰

4、微服務架構如何保障雙11狂歡下的99.99%高可用

5、兄弟,用大白話告訴你小白都能聽懂的Hadoop架構原理

6、大規模叢集下Hadoop NameNode如何承載每秒上千次的高併發訪問

7、【效能優化的祕密】Hadoop如何將TB級大檔案的上傳效能優化上百倍

8、拜託,面試請不要再問我TCC分散式事務的實現原理!

9、【坑爹呀!】最終一致性分散式事務如何保障實際生產中99.99%高可用?

10、拜託,面試請不要再問我Redis分散式鎖的實現原理!

11、【眼前一亮!】看Hadoop底層演算法如何優雅的將大規模叢集效能提升10倍以上?

12、億級流量系統架構之如何支撐百億級資料的儲存與計算

13、億級流量系統架構之如何設計高容錯分散式計算系統

14、億級流量系統架構之如何設計承載百億流量的高效能架構

15、億級流量系統架構之如何設計每秒十萬查詢的高併發架構

16、億級流量系統架構之如何設計全鏈路99.99%高可用架構

17、七張圖徹底講清楚ZooKeeper分散式鎖的實現原理

18、大白話聊聊Java併發面試問題之volatile到底是什麼?

19、大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?

20、大白話聊聊Java併發面試問題之談談你對AQS的理解?

21、大白話聊聊Java併發面試問題之公平鎖與非公平鎖是啥?

22、大白話聊聊Java併發面試問題之微服務註冊中心的讀寫鎖優化

23、網際網路公司的面試官是如何360°無死角考察候選人的?(上篇)

24、網際網路公司面試官是如何360°無死角考察候選人的?(下篇)

25、Java進階面試系列之一:哥們,你們的系統架構中為什麼要引入訊息中介軟體?

26、【Java進階面試系列之二】:哥們,那你說說系統架構引入訊息中介軟體有什麼缺點?

27、【行走的Offer收割機】記一位朋友斬獲BAT技術專家Offer的面試經歷

28、【Java進階面試系列之三】哥們,訊息中介軟體在你們專案裡是如何落地的?

29、【Java進階面試系列之四】扎心!線上服務當機時,如何保證資料100%不丟失?

30、一次JVM FullGC的背後,竟隱藏著驚心動魄的線上生產事故!

31、【高併發優化實踐】10倍請求壓力來襲,你的系統會被擊垮嗎?

32、【Java進階面試系列之五】訊息中介軟體叢集崩潰,如何保證百萬生產資料不丟失?

33、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(上)?

34、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(中)?

35、億級流量系統架構之如何在上萬併發場景下設計可擴充套件架構(下)?

36、億級流量架構第二彈:你的系統真的無懈可擊嗎?

37、億級流量系統架構之如何保證百億流量下的資料一致性(上)

38、億級流量系統架構之如何保證百億流量下的資料一致性(中)?

39、億級流量系統架構之如何保證百億流量下的資料一致性(下)?

40、網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(1)

41、網際網路面試必殺:如何保證訊息中介軟體全鏈路資料100%不丟失(2

42、面試大殺器:訊息中介軟體如何實現消費吞吐量的百倍優化?

43、高併發場景下,如何保證生產者投遞到訊息中介軟體的訊息不丟失?

44、兄弟,用大白話給你講小白都能看懂的分散式系統容錯架構

作者:石杉的架構筆記 連結:juejin.im/post/5c263a… 來源:掘金 著作權歸作者所有,轉載請聯絡作者獲得授權!

相關文章