首頁>技術>

Python學習 2019-10-10 18:24:39

筆者曾經開發過的幾個大型 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 記憶體洩漏”帖子中,(/file/2020/04/01/20200401230724_8802.jpg

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 都前後端分離了,咱就別做頁面跳轉了!統統 JSON 互動