目錄
背景
問題
分析并解決
1.控制線程數
2.更換IO組件
3.Linux進程信息文件分析
總結加餐
參考文檔
背景
隔壁業務組系統是簡單的主從結構,寫索引的服務(主)叫primary, 讀索引并提供搜索功能的服務(從)叫replica。業務線同步數據并不是平滑的,在同步數據時primary瞬間負載上升,磁盤寫IO增加,這是合理的。
primary 寫完索引,會馬上觸發索引的同步操作,因此,瞬間的磁盤讀IO增加以及網絡流量的增加都是合理的。
問題
在節假日高峰期前期,團隊增加了50%的replica服務器,這造成了primary的同步壓力進一步增加,觀測系統監控,發現了“吃swap”的現象,這就不合理了,需要分析優化。
這里簡單解釋下“吃swap”:
在計算機系統中,"吃swap"指的是系統開始使用交換空間(swap space)來補充物理內存(RAM)的不足。交換空間是一塊預留在磁盤上的區域,用于存儲那些當前不常用的內存數據。當物理內存不足時,操作系統會將一些數據從內存中移到交換空間,以騰出內存給其他需要的進程。
具體來說,“吃swap”通常表示以下幾種情況:
- 內存不足:系統的物理內存已經被用完,導致操作系統不得不使用交換空間來存儲數據。
- 性能下降:因為磁盤的讀寫速度遠低于內存的讀寫速度,當系統頻繁訪問交換空間時,性能會顯著下降。用戶可能會注意到系統變得響應緩慢。
- 高負載場景:在高負載場景下,如大量的索引寫入和同步操作,內存需求突然增加,導致系統不得不使用swap。
以上描述的情況下,primary服務器在索引寫入和同步操作的高負載下,內存不足,導致操作系統開始頻繁使用swap,從而出現“吃swap”的現象。這是不合理的,因為頻繁使用swap會顯著降低系統性能,影響整體服務的響應速度和穩定性。因此,需要通過優化措施來減少對swap的依賴,提高系統的性能。
分析并解決
1.控制線程數
參考以前的經驗,replica 有個索引備份功能,在晚上4點會備份本地索引文件(30G)到指定目錄
這個操作就會造成瞬間IO的增加以及負載的升高,因此最初我們判斷這是索引復制太快,導致的內存耗盡和負載增加。我們嘗試通過控制primary服務線程池的大小,來放慢索引復制的速度。
經過壓測發現,線程很小(5個)的情況,載峰值有所降低,但是吃swap的問題仍然存在。
結論:primary采用的gRPC框架,通過控制線程池大小來放慢索引復制的速度在gRPC框架中并不奏效,因為gRPC的異步調用機制允許高并發執行,獨立于線程數量。有效的優化策略需要針對異步調用特性和具體負載情況進行調整,而不僅僅是控制線程數。
2.更換IO組件
Lucene提供了兩種磁盤訪問方式MMapDirectory和NIODirectory,因為mmap方式會大量使用堆外內存,因此我們懷疑是堆外內存失控導致的系統內存不足,經過更換Directory,發現吃swap問題仍然存在。
Apache Lucene 是一個高性能、可伸縮的信息檢索庫,用于全文搜索和索引。Lucene 提供了多種方式來訪問磁盤存儲的索引數據,主要包括
MMapDirectory
和NIODirectory
。
結論:mmap雖然會使用堆外內存,但是linux應該是可以控制好堆外內存的回收的。
3.Linux進程信息文件分析
我們分析了linux下的 /proc/$pid/status文件,VmSwap是swap占用情況,VmHWM是歷史內存占用。以及java進程當前的內存占用情況 sudo -u tomcat jhsdb jmap --heap --pid $pid
發現java進程的歷史內存占用很高,而當前內存占用并不高(所以排除了內存泄漏),各個進程都有占用swap的現象,說明是java進程在某個時刻(文件傳輸)耗盡了內存,導致其他進程吃swap,而那個時刻過后,java進程是可以回收內存的。所以推測是索引文件傳輸過程中,某些buffer設置不合理導致的。
經過代碼排查 ,發現gRPG(高度異步)在讀取索引文件時需要多次判斷serverCallStreamObserver.isReady(),如果只判斷一次isReady,就把所以數據寫入響應流,就會占用大量系統緩沖區。官方推薦的是一種callback機制,示例代碼如下:
|
- 緩沖區管理不當:原始代碼在判斷
serverCallStreamObserver.isReady()
一次后,立即寫入所有數據,導致系統緩沖區被大量占用,內存迅速耗盡。- 回調機制:通過設置
serverCallStreamObserver.setOnReadyHandler
回調函數,確保只有在緩沖區準備好時才寫入數據。這樣可以避免一次性寫入過多數據,導致內存占用過高。- 狀態管理:使用
upto
變量跟蹤傳輸偏移量,每次isReady
時僅寫入一部分數據,緩解系統緩沖區的壓力。- 資源釋放:通過
setOnCancelHandler
和setOnCloseHandler
確保在取消或關閉時正確關閉輸入流,避免資源泄漏。
這種寫法會比較反直覺,通過回調函數關閉InputStream看上去沒有finally去關閉安全,好處是緩沖區壓力小,最終問題解決。
總結加餐
以上描述的問題的根本原因在于 gRPC 在文件傳輸過程中緩沖區管理不當,導致內存占用過高。通過引入官方推薦的回調機制和合理的狀態管理,成功解決了該問題。因此在日常開發中,涉及到文件寫入與讀取時,還是需要多關注一下流的關閉與打開與緩沖區buffer的使用是否合理。
Linux
將物理內存分為內存段,叫做頁面。交換是指內存頁面被復制到預先設定好的硬盤空間(叫做交換空間)的過程,目的是釋放這份內存頁面。物理內存和交換空間的總大小是可用的虛擬內存的總量。我們知道swap space
是磁盤上的一塊區域,可以是一個分區,也可以是一個文件,或者以它們的組合方式出現。簡單點說,當系統物理內存吃緊時,Linux
系統會將內存中不常訪問的數據保存到 swap 上,這樣系統就有更多的物理內存為其他進程服務,而當系統需要訪問swap
上存儲的內容時,系統會再將 swap 上的數據加載到內存中,這就是我們常說的swap out
和swap in
了。那么配置多大的 Swap 比較合適?
- 當物理內存小于
1G
且不需要休眠時,設置和內存同樣大小的swap
空間即可;當需要休眠時,建議配置兩倍物理內存的大小,但最大值不要超過兩倍內存大小。- 當物理內存大于
1G
且不需要休眠時,建議大小為sqrt(RAM)
,其中RAM
為物理內存大小;當需要休眠時,建議大小是RAM+round(sqrt(RAM))
,但最大值不要超過兩倍內存大小。- 如果兩倍物理內存大小的
swap
空間還不夠用,建議增加內存而不是增加swap
。
此外,在一篇參考文獻中看到如下這段話:
?如果頻繁的訪問 swap 的話,怎么優化 swap 都沒用,跟內存比還是低幾個數量級,性能還是下降的厲害,如果不頻繁訪問 swap 的話,優化 swap 又有啥意義呢?所以其實優化 swap 性能的實際意義不大。
參考文檔
https://zhuanlan.zhihu.com/p/467976849