1. 程式人生 > >一次python 記憶體洩漏解決過程

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

最近工作中慢慢開始用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這個庫的使用,也推薦大家多瞭解一下。