文章首发公众号「码农吴先生」, 欢送订阅关注。
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,聊技术聊人生~