最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python本地cache不當(dāng)使用導(dǎo)致內(nèi)存泄露的問題分析與解決

 更新時間:2023年08月30日 10:40:12   作者:及時  
最近在項目開發(fā)中遇到了本地cache不當(dāng)使用導(dǎo)致的一個內(nèi)存泄露問題,所以本文主要分析了問題出現(xiàn)的原因已經(jīng)解決方法,需要的小伙伴可以參考下

背景

近期一個大版本上線后,Python編寫的api主服務(wù)使用內(nèi)存有較明顯上升,服務(wù)重啟后數(shù)小時就會觸發(fā)機器的90%內(nèi)存占用告警,分析后發(fā)現(xiàn)了本地cache不當(dāng)使用導(dǎo)致的一個內(nèi)存泄露問題,這里記錄一下分析過程。

問題分析

LocalCache實現(xiàn)分析

該cache大概實現(xiàn)代碼如下:

class LocalCache():
    notFound = object() # 定義cache未命中時返回的唯一對象
    # list dict等本身不支持弱引用,但其子類支持,這里包裝下
    class Dict(dict):
        def __del__(self):
            pass

    def __init__(self, maxlen=10): # maxlen指定最多緩存的對象個數(shù)
        self.weak = weakref.WeakValueDictionary() # 存儲緩存對象弱引用的dict
        self.strong = collections.deque(maxlen=maxlen) # 存儲緩存對象強引用的deque

    # 從緩存dict中查找對應(yīng)key的對象,若已過期或不存在則返回notFound
    def get_ex(self, key):
        value = self.weak.get(key, self.notFound)
        if value is not self.notFound:
            expire = value['expire']
            if self.nowTime() > expire:
                return self.notFound
            else:
                return value['result']
        return self.notFound

    # 設(shè)置kv到緩存dict中,并設(shè)置其過期時間
    def set_ex(self, key, value, expire):
        self.weak[key] = strongRef = LocalCache.Dict({'result': value, 'expire': self.nowTime()+expire})
        self.strong.append(strongRef)

如上述代碼,該LocalCache核心在于一個存儲弱引用的weakref.WeakValueDictionary對象與存儲強引用的deque對象(Python中弱引用與強引用介紹可以參見這篇文章--Python中的弱引用與基礎(chǔ)類型支持情況探究 ),LocalCache實例化時可以指定最大緩存的對象個數(shù)。使用set_ex方法可以設(shè)置新的緩存kv,get_ex則獲取指定key的緩存對象,如果key不存在或者已過期則返回notFound。
該LocalCache通過deque在達到maxlen時按先進先出的順序移除隊列元素,而一旦對象的所有強引用被移除后,WeakValueDictionary的特性則保證了對應(yīng)對象的弱引用也會直接從dict中被移除出去,如此即實現(xiàn)了一個簡單的支持過期時間和最大緩存對象數(shù)量限制的本地cache。

LocalCache使用占用內(nèi)存的錯誤評估

按照上面的LocalCache原則,理論上只要設(shè)置合理的過期時間與maxlen值應(yīng)該可以保證其合理內(nèi)存的合理使用,而這次新版本發(fā)布新增了類似如下兩個個LocalCache:

id_local_cache0 = LocalCache(500000)
id_local_cache1 = LocalCache(500000)
id_local_cache0.set_ex('user_id_012345678901', 'display_id_ABCDEFGH', 1800)
id_local_cache1.set_ex('display_id_ABCDEFGH', 'user_id_012345678901', 1800)

如上定義了兩個50w大小的cache,其緩存的是業(yè)務(wù)內(nèi)部使用的user_id到用戶app上可見的display_id的映射關(guān)系,該映射關(guān)系在用戶創(chuàng)建時即生成固定不變,可以設(shè)置較長期時間,如果同時有效的對象數(shù)超過的maxlen,這個LocalCache直接就等價于一個LRU了,對象釋放可以完全依賴deque的先進先出淘汰機制。
在最開始評估其占用內(nèi)存時考慮了以下因素:

  • 單個k、v對 user_id最多20字節(jié),display_id最多8字節(jié),加上要存入的過期時間float字段8字節(jié),總大小20+8+8=36,加上一些額外花銷最多100字節(jié)
  • 最大50w限制內(nèi)存占用: 500000 * 100/1024 = 47.6MB
  • 線上api服務(wù)為uWSGI框架提供的多進程運行方式,單機4個worker進程,總占用內(nèi)存: 47.6 * 4 = 190MB
  • 兩個LcoalCache占用內(nèi)存: 190MB * 2 = 380MB

按照這個計算一臺主機即便每個進程都緩存滿了50w對象,也就增加不到400MB內(nèi)存占用,何況按照估算同時處于有效期內(nèi)的緩存對象應(yīng)該遠小于50w,所以剩余內(nèi)存應(yīng)當(dāng)完全是綽綽有余的,然而這個評估值其實遠小于實際值。

LocalCache占用內(nèi)存的正確評估

線上出現(xiàn)內(nèi)存問題后,嘗試使用tracemalloc分析了線上服務(wù)的內(nèi)存分配情況,發(fā)現(xiàn)很多內(nèi)存都集中于LocalCache這塊,于是結(jié)合實際重新評估這個內(nèi)存占用,發(fā)現(xiàn)了以下問題:

str與float的內(nèi)存占用評估錯誤,即便str本身len只有10個字符,其占用內(nèi)存其實是遠大于10的,而float并不是占用8字節(jié)而是24字節(jié),如下代碼可驗證:

In [20]: len('0123456789')
Out[20]: 10
In [21]: sys.getsizeof('0123456789')
Out[21]: 59
In [23]: sys.getsizeof(time.time())
Out[23]: 24

即便是一個空dict其占用內(nèi)存也有64字節(jié),而如果存入kv后則更是急速膨脹為至少232:

In [24]: sys.getsizeof({})
Out[24]: 64
In [26]: sys.getsizeof({'result': {'user_id_012345678901': 'display_id_ABCDEFGH'}, 'expire': time.time()})
Out[26]: 232

無論過期時間設(shè)置長短,由于存入該cache的對象資源回收完全是依賴于deque對其存入強引用的移除進行--即便對象按照時間已經(jīng)過期了,但是只要deque中還存有該對象,對象就不會被回收--所以最終cache中緩存的對象一定會達到設(shè)置的maxlen,占用其理論上可占用的最大內(nèi)存。

綜合以上幾點,雖然開始設(shè)置的過期時間較短,LocalCache中同時有效的對象數(shù)遠小于50w,但最終LocalCache還是會存滿50w的對象,同時實測LocalCache中存入一個對象的平均內(nèi)存大小在700~800字節(jié),這樣一評估,最終這兩個cache單主機上需要占用的最大且肯定會達到的內(nèi)存大小變成了: 700 * 500000 * 4 * 2 / 1024/1024 = 2.67GB,是之前錯誤評估值的6倍==!這樣一算主機上的內(nèi)存就不夠用了。

后續(xù)處理

結(jié)合實際正確評估內(nèi)存占用后,總結(jié)以下LocalCache使用原則:

  • maxlen的設(shè)置需根據(jù)實際數(shù)據(jù)情況設(shè)置為合理值--如最大可能同時有效對象數(shù)的1.1 ~ 2.0倍,防止大量過期對象長期占用內(nèi)存而不釋放的情況,check后確認線上代碼就有好幾處maxlen大于其最大有效對象數(shù)5~10倍的LocalCache使用。
  • 拆分大對象與小對象同時使用的cache,因為占用幾百字節(jié)的小對象的maxlen設(shè)置為1千、1萬甚至10w都合理,但是對于占用幾MB設(shè)置十幾MB的對象,maxlen設(shè)置>100就已經(jīng)可能占用掉大量內(nèi)存了。

針對api服務(wù)使用的多處LocalCache按照以上原則進行優(yōu)化后,其占用的總內(nèi)存量下降了超過3GB。

總結(jié)

在初版評估cache內(nèi)存占用時,用了想當(dāng)然評估法,而沒有實測每個類型、對象的實際占用大小,導(dǎo)致評估值遠小于實際值。
對于LocalCache的對象回收原理未深度理解,一直想當(dāng)然認為只要過了有效時間其對象即會被回收掉,沒有認識到其回收完全依賴于deque。
又一次想當(dāng)然造成的問題。

到此這篇關(guān)于Python本地cache不當(dāng)使用導(dǎo)致內(nèi)存泄露的問題分析與解決的文章就介紹到這了,更多相關(guān)Python內(nèi)存泄露內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python實現(xiàn)對指定輸入的字符串逆序輸出的6種方法

    python實現(xiàn)對指定輸入的字符串逆序輸出的6種方法

    這篇文章主要介紹了python實現(xiàn)對指定輸入的字符串逆序輸出的6種方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • VSCode中Python venv環(huán)境加載失敗的5大原因及解決方案

    VSCode中Python venv環(huán)境加載失敗的5大原因及解決方案

    本文詳細探討了VSCode中Python虛擬環(huán)境加載失敗的常見原因及解決方法,包括解釋器選擇、環(huán)境配置、路徑設(shè)置、解釋器優(yōu)先級和配置文件管理等方面,通過實際案例和配置示例,提供了從零創(chuàng)建可被VSCode識別的venv環(huán)境的實踐步驟,需要的朋友可以參考下
    2026-03-03
  • OpenCV形狀檢測的示例詳解

    OpenCV形狀檢測的示例詳解

    本文主要介紹了OpenCV中的形狀檢測,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 在Pandas中導(dǎo)入CSV數(shù)據(jù)時去除默認索引的方法匯總

    在Pandas中導(dǎo)入CSV數(shù)據(jù)時去除默認索引的方法匯總

    在Pandas中讀取CSV數(shù)據(jù)時,會默認將第一列設(shè)為索引列index,但有時候我們并不需要索引,或者希望指定自己的索引列,本文將介紹幾種在Pandas中導(dǎo)入CSV數(shù)據(jù)時去除默認索引的方法,需要的朋友可以參考下
    2023-05-05
  • Python可視化函數(shù)plt.scatter詳解

    Python可視化函數(shù)plt.scatter詳解

    這篇文章主要介紹了Python可視化函數(shù)plt.scatter詳解,?關(guān)于matplotlib的scatter函數(shù)有許多活動參數(shù),如果不專門注解,是無法掌握精髓的,本文專門針對scatter的參數(shù)和調(diào)用說起,并配有若干案例,需要的朋友可以參考下
    2023-04-04
  • 淺談Keras中shuffle和validation_split的順序

    淺談Keras中shuffle和validation_split的順序

    這篇文章主要介紹了淺談Keras中shuffle和validation_split的順序,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-06-06
  • pycharm使用技巧之自動調(diào)整代碼格式總結(jié)

    pycharm使用技巧之自動調(diào)整代碼格式總結(jié)

    這篇文章主要給大家介紹了關(guān)于pycharm使用技巧之自動調(diào)整代碼格式總結(jié)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • python3如何將docx轉(zhuǎn)換成pdf文件

    python3如何將docx轉(zhuǎn)換成pdf文件

    這篇文章主要為大家詳細介紹了python3如何將docx轉(zhuǎn)換成pdf文件的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Python中deepcopy的用法及注意事項詳細解釋

    Python中deepcopy的用法及注意事項詳細解釋

    這篇文章主要介紹了Python中deepcopy用法及注意事項的相關(guān)資料,deepcopy是Python中用于創(chuàng)建對象深拷貝的函數(shù),與淺拷貝不同,它會遞歸地復(fù)制對象及其所有子對象,確保新對象是獨立的副本,需要的朋友可以參考下
    2025-04-04
  • 深度學(xué)習(xí)小工程練習(xí)之tensorflow垃圾分類詳解

    深度學(xué)習(xí)小工程練習(xí)之tensorflow垃圾分類詳解

    這篇文章主要介紹了練習(xí)深度學(xué)習(xí)的一個小工程,代碼簡單明確,用來作為學(xué)習(xí)深度學(xué)習(xí)的練習(xí)很適合,對于有需要的朋友可以參考下,希望大家可以體驗到深度學(xué)習(xí)帶來的收獲
    2021-04-04

最新評論

洪洞县| 凤山县| 章丘市| 房山区| 余干县| 红原县| 舟山市| 贺州市| 瓮安县| 内丘县| 手机| 平安县| 寻乌县| 临安市| 陆川县| 孟州市| 浦江县| 无为县| 兴安盟| 广西| 喜德县| 垦利县| 砀山县| 白沙| 丘北县| 志丹县| 永川市| 新郑市| 寿宁县| 斗六市| 红原县| 溧水县| 屏东市| 滁州市| 天柱县| 慈利县| 焉耆| 凌云县| 扎鲁特旗| 勐海县| 南汇区|