文章首发公众号「码农吴先生」, 欢送订阅关注。
Django3.0 公布的时候,我尝试着用了下它的异步性能。过后它仅仅增加了对 ASGI 的反对(可见之前的文章 Django 3.0 异步试用分享,直到 Django3.1 的公布,才反对了视图和中间件的异步,然而要害的 Django ORM 层还是没有异步。Django 生态对第三方异步的 ORM 反对又不是很敌对,这就导致很多用户面对 Django 的异步性能无从下手。
很过文章在形容 Django view 和中间件的异步应用办法时,因为没有 ORM 的异步,在 view 中大多数用 asyncio.sleep
来代替,并没有实在的案例。这便进一步导致读者无从下手,认为 Django 异步齐全没生产应用价值。这观点齐全是谬误的,现阶段 Django 的异步性能齐全可用于生成。
下边是来自 Arun Ravindran(<Django 设计模式和最佳实际 > 作者)的三个生产级别的 Django 异步应用案例,供大家参考。
Django 异步的用例
微服务调用
现阶段,大多数零碎架构曾经从繁多架构进化为微服务架构,在业务逻辑中调用其余服务的接口成为常有的事件。Django 的异步 view 在这种状况下,能够很大水平上进步性能。
让咱们看下作者的例子:通过两个微服务的接口来获取最初展现在 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)
同步版本很显然,当有一个服务慢时,整体的逻辑就会阻塞期待。服务的耗时依赖最初返回的那个接口的耗时。
再看异步版本,改用了异步 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,聊技术聊人生~