問題描述
A服務,是一個檢測MGR集群主節點是否發生變化的服務,使用python語言實現的。
針對每個集群,主線程會創建一個子線程,並由子線程去檢測。子線程會頻繁的創建和銷毀。
上線以后,由於經常會有功能發布,從而重啟服務,開始一段時間沒有發現問題。
半個月前的周二服務發布后,大約一周時間,沒有再發布。到周末的時候,突然告警系統負載高,經過排查,發現內存幾乎耗盡,並查到是A服務占用巨大內存,沒有釋放。
排查過程
已經確定,A服務是存在內存泄露的,到底是什么地方內存使用完,卻沒有釋放呢?
這是一個令人頭疼的問題,以前確實沒有遇到過Python的內存泄露。
首先,網上搜索關於python內存泄漏的問題。大體了解到,Python的內存回收是基於引用計數的,也就是說,如果某個對象被使用一次,引用計數就會增加1。對象的引用計數為0時,內存就會被回收掉。
常見的導致內存泄露的情況有兩種:
- (1)對象一直被全局變量使用,全局變量生命周期比較長,所以內存一直得不到釋放。
- (2)循環引用中的對象定義了__del__的情況.
網上提供了各種用於排查內存泄露的工具,例如objgraph、guppy、pympler等,其具體使用參考文后的鏈接。
看了半天這些工具的使用,感覺還是應該看看自己代碼,是不是存在對象使用完,但是一直被引用的情況。
首先,排查內存泄露的位置是在主線程還是子線程。通過查看,發現「子線程一直在執行」與「子線程頻繁創建和退出」兩種情況下,內存消耗差別較大, 而且「子線程一直在執行」內存消耗很小。這樣,就可以定位到,內存泄露位置是在主線程或「子線程loop之前的代碼」。
接着,屏蔽子線程,發現內存正常。
所以,定位到問題是在「子線程loop之前的代碼」中。
最后,發現是頻繁調用第三方包的函數導致的。
解決辦法
找到問題的原因了,那么解決方法就好辦了。改用其他的包或修改使用方式,繞開這個大坑。
參考
一次調試python內存泄露的問題
使用gc、objgraph干掉python內存泄露與循環引用!
Python內存優化:Profile,slots,compact dict