Supervisor託管jar包出現OOM問題分析及解決方案

supervisor託管jar包出現OOM問題分析及解決方案

問題發現

使用superviosr託管jar包,啓動命令爲java -jar xxx.jar形式,啓動11個Java微服務,按啓動順序啓動java進程後,eureka中註冊的服務只能穩定7-9個,其他的服務在supervisor中的狀態顯示爲running,但是查看日誌一直在反覆重啓,且正常啓動的7到9個服務中也會出現反覆重啓的情況。反覆重啓時進程在supervisor中狀態在running和starting之間轉換,重啓一定次數後supervisor狀態變爲back off報錯狀態。

查看java進程日誌,一般有三種錯誤導致重啓:

  • eureka無法連接Completed shut down of DiscoveryClient;
  • task-service無法連接;
  • 內存溢出;

前兩種錯誤一般是因爲eureka服務和task-service服務重啓導致的,所以根本原因是因爲內存溢出。

 

Supervisor開機自啓說明:

網上給出supervisor的自啓方式都是編寫systemd文件,使用systemd啓動,測試發現,如果在supervisord.conf配置文件中使用/etc/profile中的環境變量則無法正常啓動,例如進程啓動命令爲

command = %(ENV_JDK_PATH)s %(ENV_SMALL_JVM_MEMORY)s -jar %(ENV_ONESRM_PATH)s/libs/%(program_name)s-%(ENV_PROGRAM_VERSION)s.jar %(ENV_RUN_PARAM)s

其中JDK_PATH等變量爲/etc/profile中的環境變量。

若使用systemd開機自啓,無法載入環境變量,supervisor啓動報錯。調研後,決定使用/etc/init.d/after.local文件自啓,在文件中首先source /etc/profile,然後使用命令啓動supervisor,即可開機自啓並載入環境變量。

 

問題分析與解決思路

  • 分析重啓原因爲內存溢出後,嘗試將虛擬機物理內存由8G上調至32G,但是依舊只能啓動7到9個進程。
  • 接着又嘗試調整了jvm初始化分配內存-Xms與最大使用內存-Xmx,由128m/256m調整爲512m/2048m,依然未解決問題。實際上其中佔用內存最大的task-service正常啓動後,最大內存也只佔用1g左右,設置2048m完全夠用,基本可以排除是jvm參數的問題。
  • 考慮到雖然config-service,eureka-service,task-service順序啓動時間隔較大,但是剩下的服務幾乎是同時啓動,是否因爲同時啓動對系統資源佔用較大導致OOM的問題,於是我關閉java自啓,手動啓動每個服務,等待上一個服務完全啓動後再啓動下一個,依舊只能啓動7-9個服務。
  • 網上查詢java進程OOM可能的原因,網上給出一種解決方案,是因爲系統限制了最大進程數量,到達最大數量後,可能會導致進程內存溢出,於是按照網上的步驟,將* - nofile 278528添加至/etc/security/limits.conf,確認修改成功後重啓,但是依然無法解決問題。
  • 使用top命令查看系統資源佔用,發現即使所有java進程啓動後,也只佔用7g左右的內存,swap都未佔用,且不使用supervisor託管java進程,使用同樣的jvm內存參數手動啓動時,11個進程都可完整啓動,所以可以排除是內存不夠用導致的。

下圖爲11個進程全部啓動後的資源佔用情況

 

  • 由於在服務器環境虛擬機較多,硬件壓力大,我在本地電腦安裝VMware,搭建了與服務器環境完全相同的suse12sp3環境,並發現可以復現OOM,確定不是服務器環境的問題。
  • 使用suse12自帶的系統監控軟件,發現supervisor開機啓動後,java進程全部啓動,並且進程開始遇到OOM重啓時,python2進程CPU佔用率較大,維持在30%左右,於是我將開機啓動的命令由

/usr/bin/python2 /usr/bin/supervisord -c $ONESRM_PATH/conf/supervisord.conf

改爲

/usr/bin/supervisord -c $ONESRM_PATH/conf/supervisord.conf

啓動supervisor的進程由python2改爲python(實際上python也是指向python2的),發現可以解決在java進程自動重啓時python2進程CPU佔用大的問題,但是依舊不能解決java進程OOM的問題。

  • 測試過程中發現,如果不設置supervisor開機自啓,開機後手動使用命令啓動supervisor,再啓動java,所有java進程都可穩定運行,只有supervisor開機自啓時,java進程纔會出現OOM的問題,於是對比手動啓動與開機自啓supervisor兩個進程有什麼區別,發現只有Control Group不一樣,手動啓動爲user.slice,開機自啓爲system.slice。
  • 發現兩個進程的區別後,在網上查詢關於cgroup的相關資料,查詢到cgroup可以限制任務數(https://segmentfault.com/a/1190000007468509),於是繼續查詢相關資料,發現開機自啓supervisor的控制組/system.slice/after-local.service的最大任務數被限制爲512個,使用systemd-cgtop監控java進程啓動時的task情況,發現java進程啓動到7-9個時,task數量剛好到512個,一旦超過512個task,整個java進程都會被強行停止,並且重啓,反覆重啓無法啓動時,java進程便會報OOM的異常。因爲開機自啓的進程較多,/system.slice/after-local.service控制組除java進程外的task數量較多,而手動啓動supervisor時,/user.slice/user-0.slice除java進程外的task數量少,所以即使java全部啓動,也達不到/user.slice/user-0.slice的512task限制,所以手動啓動supervisor無OOM的問題。

systemd-cgtop監控界面如下,/system.slice/after-local.service控制組的task數量爲511。

 

問題解決方案

發現導致java進程OOM的根本原因後,網上查閱相關資料,task數量爲512限制可在文件/sys/fs/cgroup/pids/system.slice/after-local.service/pids.max中查看,將

DefaultTasksMax=2048

添加至/etc/systemd/system.conf文件,可永久修改cgroup最大task限制。

修改爲2048後,supervisor開機自啓,啓動java進程再無OOM問題,至此問題解決。

總結

supervisor託管java進程報內存溢出錯誤,並且java進程反覆重啓,導致supervisor報錯無法重啓java進程的問題,在確定物理內存足夠用,並且jvm內存參數調整後依舊無法解決問題的情況下,極有可能是Linux對進程數量限制,suse12sp3中cgourp對一個控制組默認限制最大進程數量爲512,在11個java進程都啓動時,task數量能達到700左右,所以將cgroup的進程數量增加即可解決問題。

 

對Linux基礎知識的缺乏導致這次問題解決時間較長,而java進程報Out Of Memory錯誤,導致前期分析問題的思路一直在調整內存和jvm參數上,未考慮過Linux進程限制,實際完整的報錯爲

java.lang.OutOfMemoryError: unable to create new native thread

後面的unable to create new native thread說明可能是系統最大進程限制導致的OOM。

問題解決後,查閱了關於Linux進程限制的相關資料

https://blog.csdn.net/redenval/article/details/91414602

這篇文章詳細說明了Linux對進程存在限制的幾處,之後再遇到相關問題,可以參考解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章