文章首发公众号「码农吴先生」, 欢送订阅关注。

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,聊技术聊人生~