老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】

石杉的架構筆記發表於2019-03-21


這篇文章,給大家聊一個生產環境的實踐經驗:線上系統部署的時候,JVM堆記憶體大小是越大越好嗎

先說明白一個前提,本文主要討論的是Kafka和Elasticsearch兩種分散式系統的線上部署情況,不是普通的Java應用系統。


1、是否依賴Java系統自身記憶體處理資料?

先說明一點,不管是我們自己開發的Java應用系統,還是一些中介軟體系統,在實現的時候都需要選擇是否基於自己Java程式的記憶體來處理資料。

大家應該都知道,Java、Scala等程式語言底層依賴的都是JVM,那麼只要是使用JVM,就可以考慮在JVM程式的記憶體中來放置大量的資料。

還是給大家舉個例子,大家應該還記得之前聊過訊息中介軟體系統。

比如說系統A可以給系統B傳送一條訊息,那麼中間需要依賴一個訊息中介軟體,系統A要先把訊息傳送到訊息中介軟體,然後系統B從這個訊息中介軟體消費到這條訊息。

大家看下面的示意圖。

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】

大家應該都知道,一條訊息傳送到訊息中介軟體之後,有一種處理方式,就是把這條資料先緩衝在自己的JVM記憶體裡。

然後過一段時間之後,再從自己的記憶體重新整理到磁碟上去,這樣可以持久化儲存這條訊息,如下圖。

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】



2、依賴Java系統自身記憶體有什麼缺陷


如果用類似上述的方式,依賴Java系統自身記憶體處理資料,比如說設計一個記憶體緩衝區,來緩衝住高併發寫入的大量訊息,那麼是有其缺陷的。

最大的缺陷,其實就是JVM的GC問題,這個GC就是垃圾回收,這裡簡單說一下他是怎麼回事。

大家可以想一下,如果一個Java程式里老是塞入很多的資料,這些資料都是用來緩衝在記憶體裡的,但是過一會兒這些資料都會寫入磁碟。

那麼寫入磁碟之後,這些資料還需要繼續放在記憶體裡嗎?

明顯是不需要的了,此時就會依託JVM垃圾回收機制,把記憶體裡那些不需要的資料給回收掉,釋放掉那些記憶體空間騰出來。

但是JVM垃圾回收的時候,有一種情況叫做stop the world,就是他會停止你的工作執行緒,就專門讓他進行垃圾回收。

這個時候,他在垃圾回收的時候,有可能你的這個中介軟體系統就執行不了了。

比如你傳送請求給他,他可能都沒法響應給你,因為他的接收請求的工作執行緒都停了,現在人家後臺的垃圾回收執行緒正在回收垃圾物件。

大家看下圖。

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】

雖然說現在JVM的垃圾回收器一直在不斷的演進和發展,從CMS到G1,儘可能的在降低垃圾回收的時候的影響,減少工作執行緒的停頓。

但是你要是完全依賴JVM記憶體來管理大量的資料,那在垃圾回收的時候,或多或少總是有影響的。

所以特別是對於一些大資料系統,中介軟體系統,這個JVM的GC(Garbage Collector,垃圾回收)問題,真是最頭疼的一個問題。


3、優化為依賴OS Cache而不是JVM


所以類似Kafka、Elasticsearch等分散式中介軟體系統,雖然也是基於JVM執行的,但是他們都選擇了依賴OS Cache來管理大量的資料。

也就是說,是作業系統管理的記憶體緩衝,而不是依賴JVM自身記憶體來管理大量的資料。

具體來說,比如說Kafka吧,如果你寫一條資料到Kafka,他實際上會直接寫入磁碟檔案。

但是磁碟檔案在寫入之前其實會進入os cache,也就是作業系統管理的記憶體空間,然後過一段時間,作業系統自己會選擇把他的os cache的資料刷入磁碟。

然後後續在消費資料的時候,其實也會優先從os cache(記憶體緩衝)裡來讀取資料。

相當於寫資料和讀資料都是依託於os cache來進行的,完全依託作業系統級別的記憶體區域來進行,讀寫效能都很高。

此外,還有另外一個好處,就是不要依託自身JVM來緩衝大量的資料,這樣可以避免複雜而且耗時的JVM垃圾回收操作。

大家看下面的圖,其實就是一個典型的Kafka的執行流程。

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】

然後比如Elasticsearch,他作為一個現在最流行的分散式搜尋系統,也是採用類類似的機制。

大量的依賴os cache來緩衝大量的資料,然後在進行搜尋和查詢的時候,也可以優先從os cache(記憶體區域)中讀取資料,這樣就可以保證非常高的讀寫效能。


4、老司機經驗之談:依賴os cache的系統JVM記憶體越大越好?

所以現在就可以進入我們的主題了,那麼比如就以上述說的kafka、elasticsearch等系統而言,線上上生產環境部署的時候,你知道他們是大量依賴於os cache來緩衝大量資料的。

那麼,給他們分配JVM堆記憶體大小的時候是越大越好嗎?

明顯不是的,假如說你有一臺機器,有32GB的記憶體,現在你如果在搞不清楚狀況的情況下,要是傻傻的認為還是給JVM分配越大記憶體越好,此時比如給了16G的堆記憶體空間給JVM,那麼os cache剩下的記憶體,可能就不到10GB了,因為本身其他的程式還要佔用幾個GB的記憶體。

那如果是這樣的話,就會導致你在寫入磁碟的時候,os cache能容納的資料量很有限。

比如說一共有20G的資料要寫入磁碟,現在就只有10GB的資料可以放在os cache裡,然後另外10GB的資料就只能放在磁碟上。

此時在讀取資料的時候,那麼起碼有一半的讀取請求,必須從磁碟上去讀了,沒法從os cache裡讀,誰讓你os cache裡就只能放的下10G的一半大小的資料啊,另外一半都在磁碟裡,這也是沒辦法的,如下圖。

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】

那此時你有一半的請求都是從磁碟上在讀取資料,必然會導致效能很差。

所以很多人在用Elasticsearch的時候就是這樣的一個問題,老是覺得ES讀取速度慢,幾個億的資料寫入ES,讀取的時候要好幾秒。

那能不花費好幾秒嗎?你要是ES叢集部署的時候,給JVM記憶體過大,給os cache留了幾個GB的記憶體,導致幾億條資料大部分都在磁碟上,不在os cache裡,最後讀取的時候大量讀磁碟,耗費個幾秒鐘是很正常的。


5、正確的做法:針對場景合理給os cache更大記憶體

所以說,針對類似Kafka、Elasticsearch這種生產系統部署的時候,應該要給JVM比如6GB或者幾個GB的記憶體就可以了。

因為他們可能不需要耗費過大的記憶體空間,不依賴JVM記憶體管理資料,當然具體是設定多少,需要你精準的壓測和優化。

但是對於這類系統,應該給os cache留出來足夠的記憶體空間,比如32GB記憶體的機器,完全可以給os cache留出來20多G的記憶體空間,那麼此時假設你這臺機器總共就寫入了20GB的資料,就可以全部駐留在os cache裡了。

然後後續在查詢資料的時候,不就可以全部從os cache裡讀取資料了,完全依託記憶體來走,那你的效能必然是毫秒級的,不可能出現幾秒鐘才完成一個查詢的情況。

整個過程,如下圖所示:

老司機生產實踐經驗:線上系統的JVM記憶體是越大越好嗎?【石杉的架構筆記】


所以說,建議大家線上上生產系統引入任何技術的時候,都應該先對這個技術的原理,甚至原始碼進行深入的理解,知道他具體的工作流程是什麼,然後針對性的合理設計生產環境的部署方案,保證最佳的生產效能。


END



相關文章