“古怪的 Python 內存泄漏”怎麼破?

筆者曾經開發過的幾個大型 Django 應用程序都在某個時候出現了內存泄漏。Python 進程緩慢地增加它們的內存消耗,直到崩潰。這一點也不好玩。即使自動重新啓動進程之後,仍然會有一些宕機問題。

Python 中的內存泄漏通常發生在無限增長的模塊級變量中。這可能是一個具有無窮大 maxsize 的 lru_cache 變量,也可能是一個在錯誤範圍內聲明的簡單列表。

泄漏也不是隻有發生在你自己寫的代碼中才會影響你。例如,看看 BuzzFeed 的 Peter Karp 寫的這篇優秀的文章(https://docs.python.org/3/library/tracemalloc.html),他在 Python 的標準庫中發現了一個內存泄漏(已經修復了!)

解決方法

下面的解決方法都會在執行了很多請求或任務之後重新啓動 worker 進程。這是一個清除任何潛在的無限積累的 Python 對象的簡單方法。如果您的 web 服務器、隊列 worker 或類似的應用程序有此能力,但還沒有被功能化,請告訴我,我會添加它!

即使您現在還沒有看到任何內存泄漏,添加這些解決方法也會提高您的應用程序的彈性。

Gunicorn

如果您正在使用 Gunicorn 作爲您的 Python web 服務器,您可以使用--max-requests 設置來定期重啓 worker。與它的兄弟--max-requests-jitter 配合使用以防止所有 worker 同時重啓。這有助於減少 worker 的啓動負載。 

例如,在最近的一個項目中,我將 Gunicorn 配置爲:

gunicorn --max-requests 1000 --max-requests-jitter 50 ... app.wsgi

對於此項目的流量水平、worker 數量和服務器數量來說,這將大約每1.5小時重新啓動 worker。5% 的浮動足以消除重啓負載的相關性。

Uwsgi

如果您正在使用 uwsgi,你可以使用它類似的 max-requests 設置。此設置在很多次請求之後,也會重新啓動 worker。

例如,在以前的一個項目中,筆者在 uwsgi.ini 文件中像這樣使用了這個設置:

[uwsgi]master = truemodule = app.wsgi...max-requests = 500

Uwsgi 還提供了 max-requests-delta 選項用於添加其他浮動。但由於它是一個絕對數字,所以配置起來要比 Gunicorn 更麻煩。如果你更改了 worker 的數量或 max-requests 的值,那你就需要重新計算 max-requests-delta 來保持您的浮動在一個特定的百分比。

Celery

Celery 爲內存泄漏提供了幾個不同的設置。

首先是 worker_max_tasks_per_child 設置。這將在 worker 子進程處理了許多任務之後重新啓動它們。此設置沒有浮動選項,但是 Celery 任務的運行時間範圍很廣,所以會有一些自然的浮動。

例如:​​​​​​​

app = Celery("inventev")app.conf.worker_max_tasks_per_child = 100

或者你正在使用 Django 設置:

CELERY_WORKER_MAX_TASKS_PER_CHILD = 100

100個任務比筆者上面建議的 web 請求數要小一些。在過去,筆者最終爲 Celery 使用了較小的值,因爲筆者在後臺任務中看到了更多的內存消耗。(我想我還在 Celery 本身中遇到了內存泄漏。)

你可以使用的另一個設置是 worker_max_memory_per_child。這指定子進程在父進程替換它之前可以使用的最大千字節內存。它有點複雜,所以我還沒用過。

如果你確實使用了 worker_max_memory_per_child 設置,那麼你可能應該將其計算爲總內存的百分比,併除以每個子進程。這樣,如果你更改了子進程的數量或你的服務器的可用內存,它將會自動擴展。例如(未測試):​​​​​​​

import psutilcelery_max_mem_kilobytes = (psutil.virtual_memory().total * 0.75) / 1024app.conf.worker_max_memory_per_child = int(celery_max_mem_kilobytes / app.conf.worker_concurrency)

這使用psutil 來查找整個系統內存。它將最多75%(0.75)的內存分配給Celery,只有在服務器是一個專用 Celery 服務器的情況下你纔會需要它。

跟蹤泄漏

在 Python 中調試內存泄漏是很不容易的,因爲任何函數都可以在任何模塊中分配全局對象。它們也可能出現在與 C API 集成的擴展代碼中。

筆者用過的一些工具:

  • 標準庫模塊 tracemalloc.

  • objgraph 和 guppy3 包都是在 tracemalloc 之前完成的,並嘗試做類似的事情。它們都不太友好,但我以前成功地使用過它們。

  • Scout APM 用 CPython 的內存分配計數來檢測每個“span”(請求、SQL 查詢、模板標籤,等等)。少數 APM 解決方案能做到這一點。提示:我維護着 Python 集成。

更新 (2019-09-19): Riccardo Magliocchetti在Twitter上提到,pyuwsgimemhog 項目可以解析 uwsgi 日誌文件,並告訴您哪些路徑正在泄漏內存。很簡潔!

其他一些有用的博文:

  • Buzzfeed Tech撰寫了一篇《如何在生產Python web服務上使用tracemalloc 的指南》。

  • Fugue 的文章(https://www.fugue.co/blog/diagnosing-and-fixing-memory-leaks-in-python.html)也使用了 tracemalloc。

  • 在 Benoit Bernard 的“古怪的 Python 內存泄漏”帖子中,(https://benbernardblog.com/tracking-down-a-freaky-python-memory-leak/#monitoringmemoryusingperformancemonitor}他使用各種工具來跟蹤C層面的泄漏。

結束啦!

祝您內存泄漏更少!

—Adam

英文原文:https://adamj.eu/tech/2019/09/19/working-around-memory-leaks-in-your-django-app/

Python ,go,k8s資料請添加Amywechat:17812796384

 

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