一次python 記憶體泄漏解決過程

来源:https://www.cnblogs.com/zhaof/archive/2018/11/29/10031945.html
-Advertisement-
Play Games

最近工作中慢慢開始用python協程相關的東西,所以用到了一些相關模塊,如aiohttp, aiomysql, aioredis等,用的過程中也碰到的很多問題,這裡整理了一次記憶體泄漏的問題 通常我們寫python程式的時候也很少關註記憶體這個問題(當然可能我的能力還有待提升),可能寫c和c++的朋友會 ...


最近工作中慢慢開始用python協程相關的東西,所以用到了一些相關模塊,如aiohttp, aiomysql, aioredis等,用的過程中也碰到的很多問題,這裡整理了一次記憶體泄漏的問題

通常我們寫python程式的時候也很少關註記憶體這個問題(當然可能我的能力還有待提升),可能寫c和c++的朋友會更多的考慮這個問題,但是一旦我們的python程式出現了

記憶體泄漏的問題,也將是一件非常麻煩的事情了,而最近的一次代碼中也碰到了這個問題,不過好在最後記憶體溢出不是我代碼的問題,而是所用到的一個包出現了記憶體的問題,下麵我通過一個簡單的代碼模擬出記憶體的問題,然後也會將解決的過程描述一下,希望能幫助到遇到同樣問題的朋友。

一、復現問題

其實這次主要是在使用aiohttp寫一個介面的時候出現的問題,其實復現出問題非常容易,我們實現一個簡單的接受post請求介面的服務端,然後實現一個併發的客戶端來訪問這個介面,來查看記憶體的情況

註意: 這個問題是在一個包的特定版本出現的:multidict==4.5.1,我在整理這個文章2個小時前作者已經修複了這個問題發佈了4.5.2版本,已經修複了記憶體的問題,並且我也進行了測試驗證

服務端代碼:

from aiohttp import web

async def hello(request):
    return web.json_response(await request.json())

app = web.Application()
app.add_routes([web.post('/', hello)])
web.run_app(app)

客戶端代碼:

import asyncio
import aiohttp

async def foo(times):
    data = {'foo': 1}
    async with aiohttp.ClientSession() as session:
        for x in range(times):
            resp = await session.post('http://localhost:8080', json=data)
            if not x % 100:
                print(await resp.json())

loop = asyncio.get_event_loop()
loop.run_until_complete(foo(100000))
loop.close()

因為我的代碼是在linux上跑的,或者mac上我們都可以通過htop非常方面的實時查看我們程式記憶體的占用情況,我們先將服務端啟動,查看一下我們此時的記憶體情況可以看到占用的

非常少,當我們打開客戶端之後,再次觀察我們可以看到記憶體不斷增長,及時我們客戶端運行完畢記憶體也不會降低。

 當客戶端結束之後的記憶體:

如果客戶端不停止的話記憶體會一直漲,最後的結果就是把你的系統記憶體吃完,然後被系統殺掉你的進程。

 

二、解決記憶體泄漏的過程

像上面的例子是一個非常簡單的程式,不複雜我們也並沒有做上面複雜的操作就是一個簡單的接受post請求的服務端,但是如果是在實際的項目中我們可能會寫非常複雜的業務邏輯,那到時候我們又如何找到是哪裡導致的記憶體問題,當我碰到這個問題的時候,其實我和很多接觸python不久的人差不多,也是不知道怎麼查這種問題,各種百度各種查,也找到了好多推薦的工具,memory_profiler庫,objgraph庫,graphviz工具,但是都沒有幫助我迅速的找到問題點在哪裡,最後看到標準庫中的tracemalloc,地址:https://docs.python.org/3/library/tracemalloc.html

通過這個包很快幫我找到了記憶體泄漏的地方

接下來按照官網的方法我將代碼進行改寫,來測試到底哪裡的問題導致的記憶體泄漏,更改後的服務端代碼為:

 

from aiohttp import web
import tracemalloc


async def hello(request):
    return web.json_response(await request.json())

async def get_info(request):
    snapshot2 = tracemalloc.take_snapshot()
    top_stats = snapshot2.compare_to(snapshot1, 'lineno')
    print(top_stats)
    return web.Response(text="ok")


if __name__ == '__main__':
    app = web.Application()
    app.add_routes(
        [
            web.post('/', hello),
            web.get("/get_info", get_info)
        ]
    )
    tracemalloc.start()
    snapshot1 = tracemalloc.take_snapshot()
    web.run_app(app)
註意print(top_stats)這行列印的結果最後要關註

 其實這裡就是新增加了一個路由get_info, 我們啟動服務端之後開啟客戶端,當我們客戶端運行完畢之後,可以看到記憶體已經漲上去了,並且沒有不會釋放,這個時候,可以直接通過瀏覽器訪問get_info這個路由看看print列印的內容,這裡將會列印出你程式運行到這個時候那一行的代碼記憶體增長的比較多,進行一次排序,前面的幾個其實都是需要你關註的,因為這裡數據較多,我就只列印如下前幾個數據

 

<StatisticDiff traceback=<Traceback (<Frame filename='/Users/zhaofan/anaconda3/lib/python3.6/site-packages/aiohttp/web_response.py' lineno=56>,)> size=116500672 (+116500672) count=300004 (+300004)>,


<StatisticDiff traceback=<Traceback (<Frame filename='/Users/zhaofan/anaconda3/lib/python3.6/site-packages/aiohttp/web_response.py' lineno=604>,)> size=11400000 (+11400000) count=200000 (+200000)>,


<StatisticDiff traceback=<Traceback (<Frame filename='/Users/zhaofan/anaconda3/lib/python3.6/site-packages/aiohttp/web_response.py' lineno=472>,)> size=8000000 (+8000000) count=100000 (+100000)>,

<StatisticDiff traceback=<Traceback (<Frame filename='/Users/zhaofan/anaconda3/lib/python3.6/site-packages/aiohttp/web_response.py' lineno=353>,)> size=5500000 (+5500000) count=100000 (+100000)>,


<StatisticDiff traceback=<Traceback (<Frame filename='/Users/zhaofan/anaconda3/lib/python3.6/site-packages/aiohttp/web_response.py' lineno=352>,)> size=5300608 (+5300608) count=100001 (+100001)>,

 

我們拿第一行來說,我們可以非常清楚的指導web_response的56行代碼導致記憶體增長的最多,當然如果是我們複雜的項目也可以通過類似的方法,這樣就可以非常快捷的找到我們代碼中哪些地方會造成記憶體溢出,便於排查問題,我們點進去看看這行代碼:

我們找到最終行,這個時候我們大致就可以看出哪裡的問題了,我們接著看  CIMultiDict

class CIMultiDict(MultiDict):

    def _title(self, key):
        return key.title()

我們可以看到這個它繼承  MultiDict 其實這裡我們已經應該知道問題就是處在這個MultiDict上了

而這個最終其實最終就是MultiDict這個包,問題出在了這個包上,這個項目是在這裡維護的:https://github.com/aio-libs/multidict

查看這個包的時候看到了,果然有人和我遇到了同樣的問題,問題就是出在這裡了,已經有人提交了bug

https://github.com/aio-libs/multidict/issues/307

不過不得不說國外的程式員真的是熱愛自己的職業,很快這個問題得到了aio-libs小組中人的回應,問題也在我整理這個博客的時候被修複了,在最新的版本:4.5.2中已經測試沒有記憶體泄漏的問題

 

三、總結

在這裡處理的過程中,其實發現了自己很多的不足,查找問題的方式,以及遇到這種問題的解決思路,不過經過這次,至少下次遇到同樣的問題,自己能很快的去查找

以及解決問題,還有就是針對https://docs.python.org/3/library/tracemalloc.html這個庫的使用,也推薦大家多瞭解一下。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在 "上一篇" 中我們學習了行為型模式的備忘錄模式(Memento Pattern)和狀態模式(Memento Pattern)。本篇則來學習下行為型模式的最後兩個模式,觀察者模式(Observer Pattern)和空對象模式模式(NullObject Pattern)。 觀察者模式 簡介 ...
  • "迭代器模式·原文地址" "更多《設計模式系列教程》" "更多免費教程" 博主按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用 ( 靠這吃飯 )和 ( 純粹喜歡 )兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :) 0. 項目地址 迭代器模式源碼: ...
  • 單例模式 介紹 模式:創建型 意圖:保證一個類只有一個實例,並提供一個訪問它的全局訪問點 解決:一個全局使用的類頻繁地創建與銷毀 場景: 唯一序列號 web中的計數器 I/O與資料庫的連接 …… 唯一序列號 web中的計數器 I/O與資料庫的連接 …… 實現方式 餓漢式 :靜態載入,線程安全 餓漢式 ...
  • 1. 簡單工廠 1. 你開了一家披薩店,點披薩的方法可能是這樣: 可以看到,每當你想增加一種披薩類型,就要修改代碼,添加一種if else條件.當有多個系統存在orderPizza的需求時,每個系統都要同時修改他們的代碼.因此,需要將這種實例化具體對象的代碼封裝起來. 這就是簡單工廠方法,他不算一種 ...
  • 迭代器模式(Iterator Pattern)是最常被使用的幾個模式之一,被廣泛地應用到Java的API中。 定義:提供一種方法訪問一個容器對象中各個元素,而又不需暴露該對象的內部細節。 類圖如下所示。 迭代器模式有以下4個角色。 抽象迭代器(Iterator)角色:負責定義訪問和遍歷元素的介面。 ...
  • 參考於 : 大話設計模式 馬士兵設計模式視頻 代碼參考於馬士兵設計模式視頻 寫在開頭:職責鏈模式:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係 圖來自大話設計模式,下麵我的代碼中,Clien是t依賴於Handler1和Handler2的,不過可以使用配置文件或者直接給Fil ...
  • 搭建spark本地環境 搭建Java環境 (1)到官網下載JDK 官網鏈接:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html (2)解壓縮到指定的目錄 (3)設置路徑和環境變數 在 ...
  • 一.結構型設計模式 門面模式與單例模式,工廠模式不同,它是一種結構型模式。 結構型模式描述如何將對象和類組合成更大的結構 結構型模式是一種能夠簡化設計工作的模式,它能找出更簡單的方法來認識或表示實體之間的關係。 結構型模式是類和對象模式的綜合體。類模式通過繼承來描述抽象,從而提供更有用的程式介面,而 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...