三个生产级别的Django 异步应用实例

智码棱镜客
• 阅读 6106
文章首发公众号「码农吴先生」, 欢迎订阅关注。

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

三个生产级别的Django 异步应用实例

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
郜小超 郜小超
4年前
用 async/await 来处理异步
一级标题昨天看了一篇vue的教程,作者用async/await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await已经被标准化,是时候学习一下了。先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思,异步函数也就意味着该函数的执行不会阻塞后面代码的执行。写一个async
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
200的大额人民币即将面世?央行:Yes!
点击上方蓝字关注我们!(https://oscimg.oschina.net/oscnet/2a1c2ac00bf54458a78c48a6c2e547d5.png)点击上方“印象python”,选择“星标”公众号重磅干货,第一时间送达!!(
Stella981 Stella981
3年前
Jupyter notebook使用技巧大全
点击上方“蓝字”,轻松关注!(https://oscimg.oschina.net/oscnet/3a406a00d29b44568aebb8be9d319d3b.gif)JupyterNotebook简介JupyterNotebook是一款开源的web应用,它允许使用者创建和分享包含代码,公式,可
可莉 可莉
3年前
200的大额人民币即将面世?央行:Yes!
点击上方蓝字关注我们!(https://oscimg.oschina.net/oscnet/2a1c2ac00bf54458a78c48a6c2e547d5.png)点击上方“印象python”,选择“星标”公众号重磅干货,第一时间送达!!(
Stella981 Stella981
3年前
Noark入门之异步事件
引入异步事件主要是为了各模块的解耦,每当完成一个动作时,向系统发布一个事件,由关心的模块自己监听处理,可选择同步处理,异步处理,延迟处理。何时发布事件,当其他模块关心此动作时<br比如获得道具时,任务系统模块要判定完成进度,BI模块需要上报等等都可以监听此事件,已达模块解耦0x00事件源一个实现xyz.noark.core.event
Immerse Immerse
4个月前
Promise 这个新 API 真香!
Hey,我是沉浸式趣谈本文首发于【沉浸式趣谈】,我的个人博客https://yaolifeng.com也同步更新。转载请在文章开头注明出处和版权信息。如果本文对您有所帮助,请点赞、评论、转发,支持一下,谢谢!聊到异步,Promise大家肯定都不陌生,是咱们
智码棱镜客
智码棱镜客
Lv1
稚子牵衣问归来何太迟?共谁争岁月;赢得鬓边丝?
文章
5
粉丝
0
获赞
0