文章首發公衆號「碼農吳先生」, 歡迎訂閱關注。
Django3.0 發佈的時候,我嘗試着用了下它的異步功能。當時它僅僅添加了對ASGI的支持(可見以前的文章 Django 3.0 異步試用分享,直到Django3.1的發佈,才支持了視圖和中間件的異步,可是關鍵的Django ORM層仍是沒有異步。Django生態對第三方異步的ORM支持又不是很友好,這就致使不少用戶面對Django的異步功能無從下手。html
很過文章在描述Django view 和中間件的異步使用方法時,由於沒有ORM的異步,在view中大多數用asyncio.sleep
來代替,並無真實的案例。這便進一步致使讀者無從下手,認爲Django 異步徹底沒生產使用價值。這觀點徹底是錯誤的,現階段Django 的異步功能徹底可用於生成。python
下邊是來自Arun Ravindran(<Django設計模式和最佳實踐>做者) 的三個生產級別的Django 異步使用案例,供你們參考。django
現階段,大多數系統架構已經從單一架構進化爲微服務架構,在業務邏輯中調用其餘服務的接口成爲常有的事情。Django 的異步view 在這種狀況下,能夠很大程度上提升性能。json
讓咱們看下做者的例子:經過兩個微服務的接口來獲取最後展現在home頁的數據。設計模式
# 同步版本 def sync_home(request): """Display homepage by calling two services synchronously""" context = {} try: # httpx 支持異步http client ,可理解爲requests的升級異步版,徹底兼容requests 的api。 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) # 異步版本 async def async_home_inefficient(request): """Display homepage by calling two awaitables synchronously (does NOT run concurrently)""" 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_home(request): """Display homepage by calling two services asynchronously (proper concurrency)""" context = {} try: async with httpx.AsyncClient() as client: # 使用asyncio.gather 併發執行協程 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)
同步版本很顯然,當有一個服務慢時,總體的邏輯就會阻塞等待。服務的耗時依賴最後返回的那個接口的耗時。api
再看異步版本,改用了異步http client 調用,這裏的寫法並不能增長該view 的速度,兩個協程並不能同時執行。當一個協查await時,只是將控制器交還回了事件循環,而不是當即執行本view的其餘邏輯或協程。對於本view來講,仍然是阻塞的。安全
最後看下異步升級版,使用了asyncio.gather ,它會同時執行兩個協程,並在他們都完成的時候返回。升級版至關於併發,普通版至關於串行,Arun Ravindran說效率提高了一半(有待驗證)。網絡
當django 視圖須要從文件提取數據,來渲染到模板中時。無論是從本地磁盤仍是網絡環境,都會是一個潛在的阻塞I/O操做。在阻塞的這段時間內,徹底能夠幹別的事情。咱們可使用aiofile
庫來進行異步的文件I/O操做。架構
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
此實例,使用了本地的磁盤文件,若是使用網絡文件時,記着修改對應代碼。併發
文件上傳是一個很長的I/O阻塞操做,結合 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的默認文件上傳機制,所以須要注意安全隱患。
本文根據Arun Ravindran的三個準生產級別的實例,闡述了Django 現階段異步的使用。從這些例子當中能夠看出,Django 的異步加上一些異步的第三方庫,已經徹底能夠應用到生產。咱們生產系統的部分性能瓶頸,特別是I/O類型的,能夠考慮使用Django 的異步特性來優化一把了。
我是DeanWu,一個努力成爲真正SRE的人。
關注公衆號「碼農吳先生」, 可第一時間獲取最新文章。回覆關鍵字「go」「python」獲取我收集的學習資料,也可回覆關鍵字「小二」,加我wx,聊技術聊人生~