Django 3.1異步視圖實例學習

2021-01-13 蟲蟲搜奇

新發布得到Django 3.1中,提供了對步視圖的支持。在附帶的官方教程提供了一個有關Django異步視圖示例演示在調用時的異步執行asyncio.sleep。但是對此很多人會疑惑,這個sleep能幹什麼呢?本文我們就一起來學習一下 Django中的異步視圖就能幹啥。

Django異步視圖

Django現在允許用戶編寫可以異步運行的視圖view。Django中一個簡單且最小的同步視圖刷新內存的示例:

def index(request):

return HttpResponse("This is a page.")

該例子接受一個請求對象並返回一個響應對象。在實際項目中,視圖承擔著很多工作,包括從資料庫中獲取記錄,調用服務或渲染模板。在目前的情況下,它們都是同步工作得,需要按照順序一個接一個地來執行。

在Django的MTV(Model Template View,模型-模板-視圖)體系結構中,視圖比其他部件更強大(大略感覺相當於MVC架構中的控制器)。在視圖中,幾乎可以執行創建響應所需的任何邏輯。這就是為什麼異步視圖的重要性,可以讓我們同時做更多的事情。

編寫異步視圖非常容易,只需在一般的函數前面增加個async。例如,上述最小示例的異步版本為:

async def index_async(request):

return HttpResponse("This is a asynchronously page!")

但是這樣定義的看上去和函數很像,但是她是協程而不是函數。我們不能直接調用,而要創建一個事件循環來執行。

請注意,此特定視圖不是異步調用任何內容。如果Django以傳統的WSGI模式運行,則將創建(自動)新的事件循環來運行此協程。因此,在這種情況下,它可能會比同步版本慢一些。但這是因為沒有使用它來同時運行任務。

那麼,為什麼還要麻煩編寫異步視圖呢?同步視圖的局限性只有在訪問規模很大才顯現出其瓶頸。當涉及到大型Web應用程式時,比如FaceBook。

FaceBook的視圖

Facebook發布了靜態分析工具pysa來檢測和預防Python中的安全問題。在關注其代碼時候,發現其示例都是異步的寫法。

可以肯定雖然這不是Django,但是肯定是類似的框架。

綜合考慮,Django將目前默認同步執行的視圖改為異步還是非常有意義的。雖然等待I/O操作數微秒時,但是這會阻塞。如果換成異步就不會任何阻塞,可以同時處理其他任務,從而以較低的延遲處理更多的請求。這尤其對Facebook這樣的大型網站性能改善而言。線程調度程序可能會在破壞性的共享資源更新之間中斷,從而導致難以調試競爭條件。與線程相比,協程可以以更少的開銷實現更高級別的並發。

誤導性sleep例子

Django異步視圖教程中都只簡單提供了一個涉及sleep的示例。甚至正式的Django發行說明也包含以下示例:

async def my_view(request):

await asyncio.sleep(0.5)

return HttpResponse('Hello, async world!')

對於絕大多數人來說,這代碼在可能會有誤導。同步或異步發生的sleep對最終用戶沒有啥意義和效果。開連結到該視圖的URL,需要等待0.5秒,然後它才會返回一個 "Hello, async world!"。如果是一位新手,則可能會期望立即得到答覆。這與time.sleep()視圖中的同步對象相比,沒有啥意義。

異步世界中的大多數事情一樣,在事件循環中。如果事件循環中還有其他任務等待運行,則該半秒窗口將為其他任務提供運行該任務的機會。協程假定每個人都能快速工作,並迅速將控制項移交給事件循環。

一些命令行界面使用sleep來給用戶足夠的時間以使其消失之前閱讀消息。但這對於Web應用程式是相反的-來自Web伺服器的更快響應是改善用戶體驗的關鍵。

更好的實例

編寫異步視圖之前要記住的經驗法則是檢查它是受I/O密集型還是受CPU密集型。大部分時間花費CPU密集型任務中的視圖(例如,矩陣乘法或圖像處理)實際上不會從異步視圖中受益,而專注於I/O綁定的活動。

調用微服務

目前大多數大型Web應用程式正從單一架構轉型到有很多微服務組成的架構。渲染視圖可能需要許多內部或外部服務的結果。

比如這樣一個示例,在書籍電子商務網站顯示推薦書籍,為登錄用戶量身定製了首頁。推薦引擎通常被實現為單獨的微服務,該微服務基於過去的購買歷史以及通過了解過去的推薦的成功程度來進行一些機器學習來做出推薦。

在這種情況下,還需要另一個微服務的結果,該服務決定將哪些促銷橫幅顯示為旋轉橫幅或幻燈片顯示給用戶。這些標語不是為登錄用戶量身定製的,而是根據當前銷售的商品(有效的促銷活動)或日期而變化。這樣一個實例的同步版本:

def sync_home(request):

context = {}

try:

response = httpx.get(PROMO_SERVICE_URL)

if response.status_code == httpx.codes.OK:

context["promo"] = response.json()

response = httpx.get(RECCO_SERVICE_URL)

if response.status_code == httpx.codes.OK:

context["recco"] = response.json()

except httpx.RequestError as exc:

print(f"An error occurred while requesting {exc.request.url!r}.")

return render(request, "index.html", context)

使用httpx庫來代替流行的Python請求庫,因為它支持發出同步和異步Web請求。接口幾乎是相同的。

該視圖的問題在於,由於這些服務順序發生,因此調用這些服務所花費的時間加在一起。Python進程被掛起,直到第一個服務響應,在最壞的情況下這可能需要很長時間。

讓嘗試使用簡單(且無效)的await調用並發運行它們:

async def async_home_inefficient(request):

context = {}

try:

async with httpx.AsyncClient() as client:

response = await client.get(PROMO_SERVICE_URL)

if response.status_code == httpx.codes.OK:

context["promo"] = response.json()

response = await client.get(RECCO_SERVICE_URL)

if response.status_code == httpx.codes.OK:

context["recco"] = response.json()

except httpx.RequestError as exc:

print(f"An error occurred while requesting {exc.request.url!r}.")

return render(request, "index.html", context)

請注意,視圖已從函數更改為協程(由於async def關鍵字)。另請注意,實例中兩個地方等待每種服務的響應。不必嘗試在這裡理解每一行,因為將通過一個更好的示例進行解釋。

有趣的是,該視圖不能同時工作,並且所花費的時間與同步視圖相同。如果熟悉異步編程,可能已經猜到只是等待協程並不會使其同時運行其他事情,只會將控制權交還給事件循環。視圖仍然被暫停。

讓我們看一下同時運行事務的正確方法:

async def async_home(request):

context = {}

try:

async with httpx.AsyncClient() as client:

response_p, response_r = await asyncio.gather(

client.get(PROMO_SERVICE_URL), client.get(RECCO_SERVICE_URL)

)

if response_p.status_code == httpx.codes.OK:

context["promo"] = response_p.json()

if response_r.status_code == httpx.codes.OK:

context["recco"] = response_r.json()

except httpx.RequestError as exc:

print(f"An error occurred while requesting {exc.request.url!r}.")

return render(request, "index.html", context)

如果我們正在調用的兩個服務具有相似的響應時間,那麼與同步版本相比,此視圖應在_half _time中完成。這是因為調用可以同時發生。

有一個外部try ... except塊可以在進行任何HTTP調用時捕獲請求錯誤。然後是一個內部async ... with塊,它提供了一個包含客戶端對象的上下文。

最重要的一行是asyncio.gather調用,其中包含兩個client.get調用創建的協程。collect調用將同時執行它們,並且僅在它們都完成時才返回。結果將是響應的元組,將其分解為兩個變量response_p和response_r。如果沒有錯誤,則將這些響應填充到發送的用於模板渲染的上下文中。

微服務通常是組織內部的,因此響應時間短且變化少。但是,絕對不依賴同步調用在微服務之間進行通信永遠不是一個好主意。隨著服務之間的依賴性增加,它會創建一長串的請求和響應調用。這樣的連鎖會減慢服務速度。

還有一很實際的例子就是Web抓取的問題,因為有許多異步示例使用它們。這樣同時獲取和抓取多個外部網站或網站中的頁面以獲取實時股票市場(或比特幣)價格等信息的情況。該實現將與我們微服務示例中看到的非常相似。

但這是非常危險的,因為視圖應能儘快將響應返回給用戶。因此,嘗試獲取具有這種隨著信息時間變化的的站點可能會導致獲取過時的信息。而微服務調用通常是內部的,因此可以通過適當的SLA來控制響應時間。

理想情況下,抓取應在安排為定期運行的單獨過程中進行(使用celery或)。該視圖應僅選擇已採集的值並將其顯示給用戶。

文件服務

通常有一個需求,需要通過動態內容來提供文件服務。文件通常位於基於磁碟的存儲(較慢的)中。儘管使用Python可以很容易地完成此文件操作,但就大型文件的性能而言,它可能會很昂貴。無論文件大小如何,這都是一個潛在的阻塞I/O操作,可用於同時運行另一個任務。

假設我們需要在Django視圖中提供PDF證書。但是,出於某種原因(可能用於標識和驗證),需要將下載證書的日期和時間存儲在PDF文件的元數據中。

該示例中我們使用aiofiles庫進行異步文件I/O。該API與熟悉的Python內置文件API幾乎相同。下面異步視圖的編寫方式:

async def serve_certificate(request):

timestamp = datetime.datetime.now().isoformat()

response = HttpResponse(content_type="application/pdf")

response["Content-Disposition"] = "attachment; filename=certificate.pdf"

async with aiofiles.open("homepage/pdfs/certificate-template.pdf", mode="rb") as f:

contents = await f.read()

response.write(contents.replace(b"%timestamp%", bytes(timestamp, "utf-8")))

return response

該例子說明了為什麼我們需要在Django中進行異步模板渲染。但是在實現之前,只能使用aiofiles庫來提取本地文件拍。直接使用本地文件而不是Django的staticfiles有不利之處。

處理上傳

另一方面,上傳文件也可能是很長的阻塞操作。出於安全和組織方面的原因,Django將所有上傳的內容存儲在單獨的"媒體"目錄中。

如果有一種允許上傳文件的表單,那麼我們需要預料到一些討厭的用戶會上傳一個不可能很大的文件。值得慶幸的是,Django將文件以一定大小的塊傳遞給視圖。結合aiofile異步寫入文件的功能,我們可以支持高度並發的上傳。

async def handle_uploaded_file(f):

async with aiofiles.open(f"uploads/{f.name}", "wb+") as destination:

for chunk in f.chunks():

await destination.write(chunk)

async def async_uploader(request):

if request.method == "POST":

form = UploadFileForm(request.POST, request.FILES)

if form.is_valid():

await handle_uploaded_file(request.FILES["file"])

return HttpResponseRedirect("/")

else:

form = UploadFileForm()

return render(request, "upload.html", {"form": form})

同樣,這繞過了Django的默認文件上傳機制,因此您需要注意安全隱患。

總結

Django Async項目具有完全向後兼容性,這是其主要目標之一。因此,可以繼續使用舊的同步視圖,而無需將其重寫為異步視圖。異步視圖也並不是解決所有性能問題的靈丹妙藥,因此大多數項目仍將會繼續使用同步代碼,因為它們非常容易推理。

實際上,可以在同一項目中同時使用異步視圖和同步視圖。Django將負責以適當的方式調用視圖。但是,如果使用的是異步視圖,建議將應用程式部署在ASGI伺服器上。

相關焦點

  • Django 3.0 將支持異步功能
    根據 Django 的項目目錄,Django 異步功能草案(DEP 0009)已被技術委員會通過,預計將在 3.0 中正式引入。
  • 使用Django搭建個人博客
    採用了MVC的軟體設計模式,即模型M,視圖V和控制器C。它最初是被開發來用於管理勞倫斯出版集團旗下的一些以新聞內容為主的網站的,即是CMS(內容管理系統)軟體。並於2005年7月在BSD許可證下發布。這套框架是以比利時的吉普賽爵士吉他手Django Reinhardt來命名的。
  • 基於django的單元測試
    1.【知道】認識單元測試單元測試:測類、方法、函數,測試最小單位由於django的特殊性,通過接口測單元,代碼邏輯都放在類視圖中單元測試好處消滅低級錯誤【掌握】編寫和運行django的單元測試django環境資料庫編碼資料庫用戶權限(需要建臨時資料庫、刪臨時資料庫)每個應用,自帶tests.py
  • 「Django教程」第07天:上傳圖片/顯示圖片
    具體使用的方法參考本篇文章,底部原文可以進入django的官文去看一看官方文檔。自定義form表單中上傳圖片1)打開booktest/views.py文件,創建視圖pic_upload。url(r'^pic_upload/$', views.pic_upload),3)在templates/booktest/目錄下創建模板pic_upload.html。
  • django中遇到錯誤:Forbidden CSRF cookie not set
    表示django全局發送post請求均需要字符串驗證功能:防止跨站請求偽造的功能工作原理:客戶端訪問伺服器端,在伺服器端正常返回給客戶端數據的時候,而外返回給客戶端一段字符串,等到客戶端下次訪問伺服器端時,伺服器端會到客戶端查找先前返回的字符串
  • 「原創」Django第六章、模型操作
    相信大家都是抱著學習Django的目的來看這一套入門筆記,我就不敢再各位資料庫大佬們面前班門弄斧了,直接進入本章學習了。| |1 1|title |varchar(300)|1 | |0 2|body |text |1 | |0 3|publish |datetime |1 | |0
  • Django官方為什麼沒有標準項目結構
    方法1 加參數其實!它是支持的,只是我們漏了一個參數。startproject的完整格式為django-admin startproject name [directory],可以在後面追加一個目錄參數:...
  • 如何輕鬆了解 Python 必學的 django 框架?
    django 框架配有一個功能強大的管理站點,裡面已經預定義了許多管理站點需要進行的操作,但是有時可能默認的操作不能滿足需求,如需要批量更新選中的對象,這需要編寫自定義 action 實現。本文基於 django 官方英文文檔梳理了一下自定義管理操作需要做的工作,方便快速了解自定義管理操作的實現過程。
  • 「原創」Django第五章、模型建立與遷移
    通過這個類我們可以創建一個專門用來保存博客文章的資料庫表,代碼如下:from django.db import modelsfrom django.utils import timezone # 新增from django.contrib.auth.models import
  • 如何使用Visual Studio創建Django項目
    Studiopython環境技術Djangopython一般情況下,我們使用pycharm工具創建python項目,還可以新建django下面利用實例說明:操作如下:1、打開Visual Studio工具,點擊文件--->新建--->項目
  • 了解vue3.0的異步更新原理
    「關注 前端開發社區 ,回復「 1」 即可加入 前端技術交流群,回復 「 2」 即可免費領取500G前端乾貨!今天我們簡單了解下vue3.0的異步更新原理,了解一下effect,watchEffect的特點以及最主要queueFlush函數的實現(函數名字本意就是:排隊刷新)effect特點import { effect, reactive } from '.
  • Django 項目從零開始:Django 項目創建
    1.利用Startproject創建DjangoDemo項目在CMD中利用CD將路徑切換到自己項目路徑下再執行下面的命令django-admin startproject DjangoDemo2.測試啟動Django 網站服務在CMD中利用CD進入DjangoDemo文件夾下面,執行下面的命令python manage.py runserver執行此命令默認用127.0.0.1:8000來訪問網站當然也可以用
  • 三相異步電機的用途、結構
    1、三相異 步電機的結構準備一臺待拆卸的三相異步電機,從外到裡看它的結構。圖6-1是三相異步電機的外形圖和剖視圖。從電機外部可以看到風扇罩、風扇、左右端蓋、銘牌、接線盒。打開接線盒,可以看到接線柱和引出線。
  • 開發中Django和Flask框架的區別是什麼?
    想要熟練掌握框架需要先學習框架的基礎知識、基本實現原理;結合項目實踐,提升編碼能力和業務邏輯的理解;翻看框架源碼深入理解源碼精髓。  3、 通過大數據統計分析  全球著名的stackshare網站對Django和Flask這兩個框架的調查分析。
  • 「Django」RBAC權限管理系統模塊-理解
    >10 Minutes Django-RBAC:PART 1 這權限管理系統主要功能是什麼?分享部分代碼供參考,就是先通過用戶判斷組別,然後在進行數據過濾篩選所謂對象級權限,即針對的Model對象實例進行授權操作,使用我們可以精確的對單個對象進行權限的控制,以實現更細精度的權限控制。
  • 三相異步電動機維護保養_三相異步電動機額定電流計算
    3億kw以上,用電量約佔全國發電量的60%,當前的主要問題是運行效率偏低(平均比先進國家低25%~30%),而加強電動機的維護與修理,是提高運行效率的重要措施這一。   三相異步電動機額定電流計算   三相異步電機的額定電流=額定功率÷(1.732×額定電壓×功率因數×效率)。
  • 機件的表達方法-視圖的分類1
    二、機件的結構和形狀是多種多樣的,對於複雜的機件,僅用三視圖不一定能將其完整、清洗地表示出來,而有些形狀簡單的機件又沒有必要用三視圖表達,所以機件表達方案的選擇確定非常重要。三、首先說一下視圖的選擇與繪製1、視圖是物體向投影面投射所得的圖形,主要用來表達機件的外部結構形狀,一般只畫物體的可見部分輪廓,必要時才畫出其不可見部分輪廓。2、視圖包含:基本視圖、向視圖、局部視圖和斜視圖4種。
  • Glimpse(Photoshop替代)圖像視頻美顏處理快速入門實例1
    本實例將使用 "曝光 "對曝光不足的老照片進行增亮,使用 "曝光 "可以通過增加圖像中每個像素的亮度來模擬增加光照度。打開圖片複製圖層按 Ctrl + Shift + D即可曝光設置顏色 曝光,勾選分割視圖選項,將顯示比較所應用的效果與原始效果的並排視圖。將曝光值設置為約 2.25 ,這個操作導致本該是暗的區域變得渾濁,所以現在我們要通過調整黑色級別來抵消。將黑色級別值設置為0.002,然後單擊 "確定"。
  • Workerman 3.3.6 發布,新增多個異步 PHP 組件
    高性能PHP Socket框架Workerman發布3.3.6版本底層支持ReactPHP,同步支持ReactPHP
  • 單相異步電動機型號及分類
    特徵代號——表示電機的鐵芯長度和極數,鐵芯長度號有l(長)、m(中)、s(短)及數字1、2。特徵代號的後面一位為極數,2、4、6等偶數。 特殊環境代號——表示產品適用的環境,一般環境不標註,見表2。 表1 單相系列產品代號表