當前位置:
首頁 > 知識 > 在 Django中使用 Redis和Celery處理非同步任務

在 Django中使用 Redis和Celery處理非同步任務

介紹

在本教程中,我將大致介紹為什麼celery消息隊列是有價值的,以及如何在Django應用程序中使用celery和Redis。為了演示實現細節,我將構建一個最小化的圖像處理應用程序,它將生成用戶提交的圖像的縮略圖。

本文將包含以下主題:

  • Celery消息隊列和Redis的背景知識
  • 在本地開發環境中設置Django、Celery和Redis
  • 在Celery任務中創建圖像縮略圖
  • 部署到Ubuntu伺服器

(此處已添加圈子卡片,請到今日頭條客戶端查看)

如果你只想直接跳到一個功能完整的應用程序,那你可以在GitHub(https://github.com/amcquistan/image_parroter )上找到這個例子的代碼,以及安裝和設置說明,不然的話,在本文的其餘部分中,我將為你介紹如何從頭構建所有東西。


Celery消息隊列和 Redis的背景知識

Celery是一個基於Python的任務隊列軟體包,它支持執行非同步計算工作負載,這些工作負載由應用程序代碼(本例中為Django)發送給Celery任務隊列的消息所驅動。Celery還可以用來執行重複性的、周期性的(即計劃好的)任務,但這不是本文的重點。

Celery最好與經常被稱為消息代理的存儲解決方案一起結合使用。和Celery經常一起使用的消息代理是Redis,它是一個基於內存的持久化的鍵-值數據存儲系統。具體來說,Redis用於存儲應用程序代碼生成的消息,這些消息描述了Celery任務隊列中要完成的工作。Redis還用作Celery隊列返回的結果的存儲,以便Celery隊列的消費者稍後進行檢索。

在本地開發環境中設置Django、Celery和Redis

我將從最困難的部分開始,首先是安裝Redis。

在Windows上安裝Redis

  1. 下載Redis zip文件並解壓到某個目錄中
  2. 找到名為redis-server.exe的文件,雙擊在命令窗口中啟動伺服器
  3. 類似地,找到另一個名為redis-clil .exe的文件,雙擊它,在一個單獨的命令窗口中打開這個程序
  4. 在運行cli客戶端的命令窗口中進行測試,以確保客戶端可以通過發出命令ping與伺服器進行通信,如果一切正常,伺服器會返回一個PONG響應

在Mac OSX / Linux上安裝Redis

  1. 下載Redis tarball文件並將其解壓到某個目錄中
  2. 使用make install命令運行make文件來構建程序
  3. 打開終端窗口並運行redis-server命令
  4. 在另一個終端窗口中運行redis-cli
  5. 在運行cli客戶端的終端窗口中進行測試,以確保客戶機可以通過發出ping命令與伺服器通信,如果一切正常,伺服器會返回一個PONG響應

安裝Python虛擬環境和依賴項

現在,我可以繼續創建Python3虛擬環境,並安裝這個項目所需的依賴項。

首先,我將創建一個名為image_parroter的目錄作為項目的主目錄,然後在其中創建虛擬環境。從這裡開始,所有的命令都將只有unix風格,但是,大多數(如果不是所有的話)命令對於windows環境來說都是相同的。

在 Django中使用 Redis和Celery處理非同步任務

現在激活虛擬環境後,我就可以安裝以下Python包了。

在 Django中使用 Redis和Celery處理非同步任務

  • Pillow是一個用於圖像處理的與celery無關的Python包,我將在本教程的後面使用它來演示celery任務的實際用例。
  • Django Widget Tweaks是一個Django插件,提供了表單輸入呈現的靈活性。

設置Django項目

接下來,我創建了一個名為image_parroter的Django項目,然後是一個名為thumbnailer的Django App。

在 Django中使用 Redis和Celery處理非同步任務

此時的目錄結構如下:

在 Django中使用 Redis和Celery處理非同步任務

為了在這個Django項目中集成Celery,我按照Celery文檔中描述的約定添加了一個新的imageparroter/imageparrroter/celery.py模塊。在這個新的Python模塊中,我導入了os包和Celery類。

os模塊用於將Celery環境變數DJANGO_SETTINGS_MODULE與Django項目的settings模塊關聯起來。然後,我實例化Celery類的一個實例來創建celery_app實例變數。然後,我將在settings文件中添加很多以"CELERY_"開頭的設置,這些設置可以更新Celery應用程序的配置。最後,我告知新創建的celery_app實例去自動發現項目中的任務。

完成的celery.py模塊如下所示:

在 Django中使用 Redis和Celery處理非同步任務

現在,在項目的settings.py模塊的最底部,我為celery設置定義了一個部分,並添加了如下所示的設置。這些設置告訴Celery使用Redis作為消息代理,以及在哪裡連接到它。它們還告訴Celery消息會在Celery任務隊列之間來回傳遞,而Redis 消息代理將會以application/json的mime類型進行傳遞。

在 Django中使用 Redis和Celery處理非同步任務

接下來,我需要確保前面創建和配置的celery應用程序在運行時會被注入Django應用程序。這是通過在Django項目的主__init__ .py腳本中導入Celery應用程序,並在「image_parroter」Django包中顯式地將其註冊為一個帶名稱空間的符號來實現的。

在 Django中使用 Redis和Celery處理非同步任務

我繼續遵循文檔建議的約定,在「thumbnailer」應用程序中添加了一個名為tasks.py的新模塊。在tasks.py模塊中,我導入了shared_tasks函數裝飾器,並使用它來定義一個名為adding_task的celery任務函數,如下所示。

在 Django中使用 Redis和Celery處理非同步任務

最後,我需要將thumbnailer應用程序添加到image_parroter項目的settings.py模塊中的INSTALLED_APPS列表中。在這裡,我還應該添加「widget_tweaks」應用程序,用於控制表單輸入的呈現,稍後我將使用它來允許用戶上傳文件。

在 Django中使用 Redis和Celery處理非同步任務

現在,我可以在三個終端上使用一些簡單的命令來進行測試。

在一個終端中,我需要將redis-server運行,像這樣:

在 Django中使用 Redis和Celery處理非同步任務

在第二個終端中,在先前安裝的Python虛擬環境實例激活的情況下,在項目的根包目錄中(與包含manage.py模塊的目錄相同),我啟動了celery程序。

在 Django中使用 Redis和Celery處理非同步任務

在第三個也是最後一個終端中,同樣是在Python虛擬環境激活的情況下,我可以啟動Django Python shell並測試我的adding_task,如下所示:

在 Django中使用 Redis和Celery處理非同步任務

注意在adding_task對象上使用的.delay(…)方法。這是將任何必要的參數傳遞給處理它們的任務對象的常用方法,也是將任務對象發送到消息代理和任務隊列的常用方法。調用.delay(…)方法的結果是一個類型為celery.result.AsyncResult的promise-like的返回值。這個返回值包含任務的id、執行狀態和任務狀態等信息,以及通過.get()方法訪問任務生成的任何結果的能力,如示例所示。


在Celery任務中創建圖像縮略圖

現在,將一個Redis支持的Celery實例集成到Django應用程序的boiler plate setup(套路設置)已經完成,我可以繼續使用前面提到的thumbnailer應用程序來演示一些更有用的功能。

回到tasks.py模塊中,我從PIL包中導入了Image類,然後添加一個名為make_thumbnails的新任務,它接受一個圖像文件路徑和一個2元組(寬度和高度尺寸)的列表來創建縮略圖。

在 Django中使用 Redis和Celery處理非同步任務

上面的縮略圖任務只是將輸入的圖像文件載入到一個Pillow圖像實例中,然後對傳遞給任務的尺寸列表進行循環,並為每個尺寸創建縮略圖,將每個縮略圖添加到一個zip歸檔文件中,同時清理中間文件。最後,它會返回一個簡單的字典,其中包含了下載縮略圖zip文檔的URL。

定義了celery任務之後,我繼續構建Django視圖來提供一個帶有文件上傳表單的模板。

首先,我為Django項目提供了一個MEDIA_ROOT位置,其中可以放置圖像文件和zip存檔(我在上面的示例任務中使用了這個位置),並指定了可以提供內容的MEDIA_URL。在image_parroter/settings.py模塊中,我添加了MEDIA_ROOT、MEDIA_URL、IMAGES_DIR設置位置,並提供了當這些位置不存在時創建它們的邏輯。

在 Django中使用 Redis和Celery處理非同步任務

在thumbnailer/views.py模塊中,我導入了django.views.View 類,並使用它創建了一個包含get和post方法的HomeView類,如下所示。

get方法只簡單地返回一個home.html模板,並向它傳遞一個包含ImageField 欄位的FileUploadForm,如HomeView類上面所示。

post方法使用請求中發送的數據來構造FileUploadForm對象,並檢查其有效性, 如果它是有效的,post方法就將它上傳的文件保存到IMAGES_DIR 中,並啟動一個make_thumbnails任務,同時抓取任務id和狀態傳遞給模板,或者將帶有錯誤提示的表單返回給home.html模板。

在 Django中使用 Redis和Celery處理非同步任務

在HomeView類的下面,我放置了一個TaskView類,我們將使用它通過一個AJAX請求來檢查make_thumbnails任務的狀態。在這裡,你會注意到我已經從celery包中導入了current_app對象,並使用它從請求中檢索與task_id相關聯的任務的AsyncResult對象。我創建了一個包含任務狀態和id的response_data詞典,然後如果狀態表示任務已經成功執行,我將通過調用AsynchResult對象的get()方法來獲取結果,同時將結果分配給response_data的results鍵,該response_data將會以JSON的形式被返回給HTTP請求者。

在創建模板UI之前,我需要將上面的Django視圖類映射到一些合理的URL上。首先,我在thumbnailer應用程序中添加一個urls.py模塊,並定義以下URL:

在 Django中使用 Redis和Celery處理非同步任務

然後在項目的主URL配置中,我需要包含應用程序級的url,並讓它識別到媒體URL,像這樣:

在 Django中使用 Redis和Celery處理非同步任務

接下來,我開始構建一個簡單的模板視圖,讓用戶提交一個圖像文件,並檢查提交的make_thumbnails任務的狀態,並開始下載生成的縮略圖。開始之前,我需要在thumbnailer目錄中創建一個目錄來存放這個模板,如下所示:

在 Django中使用 Redis和Celery處理非同步任務

然後,我在這個templates/thumbnailer目錄中添加了一個名為home.html的模板。在home.html中,我首先載入「widget_tweaks」模板標記,然後通過導入一個名為bulma CSS的CSS框架和一個名為Axios.js的JavaScript庫來定義這個HTML。在這個HTML頁面的body中,我提供了一個標題、一個用於顯示進度消息中結果的佔位符和一個文件上傳表單。

在 Django中使用 Redis和Celery處理非同步任務

在 Django中使用 Redis和Celery處理非同步任務

在 Django中使用 Redis和Celery處理非同步任務

在body元素的底部,我添加了JavaScript來提供一些額外的行為。首先,我創建了一個對文件輸入框的引用,並註冊了一個更改監聽器,一旦文件被選中,它就會將所選文件的名稱添加到UI中。

接下來是更相關的部分。我使用Django的模板邏輯if操作符檢查從HomeView類視圖中傳遞的task_id是否存在。這表示make_thumbnails任務被提交後的一個響應。然後,我使用Django url模板標記來構造一個適當的任務狀態檢查URL,並使用前面提到的Axios庫開始對該URL發起一個間隔計時AJAX請求。

如果一個任務狀態被報告為「SUCCESS」,我會將一個下載鏈接注入DOM並使其啟動,觸發下載並清除間隔計時器。如果任務狀態是「FAILURE」,我就只需要清除這個interval(間隔),如果狀態既不是「SUCCESS」也不是「FAILURE」,那麼在調用下一個間隔之前我什麼都不做。

此時,我可以打開另一個終端,再次啟用Python虛擬環境,並啟動Django開發伺服器,如下所示:

在 Django中使用 Redis和Celery處理非同步任務

  • 前面描述的redis-server和celery任務終端也需要運行,如果從添加make_thumbnails任務之後,你還沒有重新啟動Celery 工作程序,那你可能需要按Ctrl + C來停止該工作程序,然後再次發送celery worker -A image_parroter --loglevel=info命令來重新啟動它。每次對celery 任務相關的代碼做了更改之後,Celery工作程序就必須重新啟動。

現在,我可以在瀏覽器中訪問http://localhost:8000 來載入home.html視圖,提交一個圖像文件,這個應用程序應該會返回一個results.zip歸檔文件,其中包含原始圖像和一個128x128像素的縮略圖。


部署到Ubuntu 伺服器上

為了完成本文,我將演示如何在Ubuntu v18 LTS伺服器上安裝和配置這個Django應用程序,並使用Redis和Celery來執行非同步後台任務。

一旦通過SSH連接上了伺服器,我就對伺服器進行更新,然後安裝必要的包。

在 Django中使用 Redis和Celery處理非同步任務

我還創建了一個名為「webapp」的用戶,它為我提供了一個安裝Django項目的主目錄。

在 Django中使用 Redis和Celery處理非同步任務

輸入用戶數據之後,我將webapp用戶添加到sudo和www-data組中,並切換到webapp用戶,然後用cd切換到到其主目錄中。

在 Django中使用 Redis和Celery處理非同步任務

在web應用程序目錄中,我可以克隆image_parroter GitHub倉庫, cd到這個倉庫中,創建一個Python虛擬環境,並激活它,然後從requirements.txt文件安裝依賴項。

在 Django中使用 Redis和Celery處理非同步任務

除了我剛剛安裝的依賴項之外,我還想為uwsgi web應用程序容器添加一個新的依賴項,它將為Django應用程序提供服務。

在 Django中使用 Redis和Celery處理非同步任務

在繼續之前,最好先更新一下settings.py文件,將DEBUG值切換為False,並將IP地址添加到ALLOWED_HOSTS列表中。

在此之後,進入Django image_parroter項目目錄(包含wsgi.py模塊的目錄),並添加一個用於保存uwsgi配置設置的新文件,將其命名為uwsgi.ini,並向其中加入以下內容:

在 Django中使用 Redis和Celery處理非同步任務

在我忘記之前,我應該繼續添加日誌目錄,並給它適當的許可權和所有者。

在 Django中使用 Redis和Celery處理非同步任務

接下來,我創建了一個systemd服務文件來管理位於/etc/systemd/system/uwsgi.service的uwsgi應用程序伺服器,並包含以下內容:

在 Django中使用 Redis和Celery處理非同步任務

現在,我可以啟動uwsgi服務了,檢查它的狀態是否正常,並啟用它,以便在引導時自動啟動。

在 Django中使用 Redis和Celery處理非同步任務

至此,Django應用程序和uwsgi服務就已經設置好了,我可以繼續配置redis-server。

我個人更喜歡使用systemd服務,所以我將通過將supervised參數設置為systemd來編輯/etc/redis/redis.conf配置文件。然後重啟redis-server,檢查它的狀態,並使它能夠在引導時啟動。

在 Django中使用 Redis和Celery處理非同步任務

接下來就是配置Celery。我為Celery創建了一個日誌位置,並給這個位置適當的許可權和所有者,就像這樣:

在 Django中使用 Redis和Celery處理非同步任務

接著,我在與前面描述的uwsgi.ini文件相同的目錄中添加了一個名為celery.conf 的Celery配置文件,並向其中加入以下內容:

在 Django中使用 Redis和Celery處理非同步任務

為了完成對celery的配置,我在/etc/systemd/system/celery.service添加了它自己的systemd服務文件,並在其中添加以下內容:

在 Django中使用 Redis和Celery處理非同步任務

最後要做的是將nginx配置為uwsgi/django應用程序的反向代理,並提供媒體目錄中的內容。為此,我在/etc/nginx/sites-available/image_parroter中添加了一個nginx配置文件,其中包含以下內容:

在 Django中使用 Redis和Celery處理非同步任務

接下來,我刪除了默認的nginx配置,允許我使用server_name _;去捕獲80埠上的所有http通信,然後在我剛剛在「sites-available」目錄中添加的配置文件和與之相鄰的「sites-enabled」目錄之間創建一個符號鏈接。

在 Django中使用 Redis和Celery處理非同步任務

這樣做之後,我就可以重新啟動nginx,檢查它的狀態,並允許它開機啟動。

在 Django中使用 Redis和Celery處理非同步任務

此時,我可以將瀏覽器轉向這個Ubuntu伺服器的IP地址,並測試這個thumbnailer應用程序。

在 Django中使用 Redis和Celery處理非同步任務


結論

本文描述了為什麼要使用,以及如何使用Celery來實現啟動非同步任務的常見目的,該非同步任務將連續運行直至完成。這將顯著改善用戶體驗,減少長時間運行的代碼路徑對web應用程序伺服器處理進一步請求的影響。

我已經儘力地去詳細說明從設置開發環境、實現celery任務、在Django應用程序代碼中生成任務到通過Django和一些簡單的JavaScript處理結果的整個過程。

感謝你的閱讀,請隨時在下面進行評論或批評指正。


英文原文:https://stackabuse.com/asynchronous-tasks-in-django-with-redis-and-celery/

譯者:測試

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

技術簡歷最重要的部分:經驗

TAG:Python部落 |