2020年是时候更新你的技术武器库了:Asgi vs Wsgi(FastAPI vs Flask)

2020年是时候更新你的技术武器库了:Asgi vs Wsgi(FastAPI vs Flask)

    也许这一篇的标题有那么一点不厚道,因为Asgi(Asynchronous Server Gateway Interface)毕竟是Wsgi(Web Server Gateway Interface)的扩展,而FastAPI毕竟也是站在Flask的肩膀上才有了突飞猛进的发展,大多数人听说Asgi也许是因为Django的最新版(3.0)早已宣布支持Asgi网络规范,这显然是一个振奋人心的消息,2020年,如果你在Web开发面试中不扯一点Asgi,显然就有点落后于形势了。

    那么到底啥是Wsgi,什么又是Asgi,放心,不扯CGI,不扯各种抽象概念,简单粗暴理解:

    Wsgi是同步通信服务规范,客户端请求一项服务,并等待服务完成,只有当它收到服务的结果时,它才会继续工作。当然了,可以定义一个超时时间,如果服务在规定的时间内没有完成,则认为调用失败,调用方继续工作。

    Wsgi简单工作原理示意图:

    简单实现:

#WSGI example 


def application(environ, start_response):


start_response('200 OK', [('Content-Type', 'text/plain')])


return b'Hello, Wsgi\n'


    Asgi是异步通信服务规范。客户端发起服务呼叫,但不等待结果。调用方立即继续其工作,并不关心结果。如果调用方对结果感兴趣,有一些机制可以让其随时被回调方法返回结果。

    Asgi简单工作原理示意图:

    简单实现:

#Asgi example

async def application(scope, receive, send):


event = await receive()


...


await send({"type": "websocket.send", ...})

    简单总结一下:Asgi是异步的,Wsgi是同步的,而基于Wsgi的Flask是同步框架,基于Asgi的FastAPI是异步框架,就这么简单,那么同步框架和异步框架的区别到底在哪儿?为什么要把Flask换成FastAPI?

    不靠拍脑门儿、也不是道听途说、人云亦云。玩技术的应该用数据说话,论点永远依托论据,所以我们来简单对两款框架的性能做一个测试,首先分别安装依赖的库。

    Flask:

pip install gunicorn
pip install gevent
pip install flask

    FastAPI:

pip install fastapi
pip install uvicorn

    我们首先干的一件事就是,看看Flask和FastAPI如何处理来自多个客户端的多个请求。特别是当代码存在效率问题时(比如数据库查询时间长这种耗时任务),这里故意使用time.sleep()来模拟耗时任务,为什么不用asyncio呢?因为众所周知的原因:time.sleep是阻塞的。

    Flask:

from flask import Flask
from flask_restful import Resource, Api
from time import sleep

app = Flask(__name__)
api = Api(app)

class Root(Resource):
def get(self):
print('睡10秒')
sleep(10)
print('醒了')
return {'message': 'hello'}

api.add_resource(Root, '/')

if __name__ == "__main__":
app.run()

    FastApi:

import uvicorn
from fastapi import FastAPI
from time import sleep
app = FastAPI()

@app.get('/')
async def root():
print('睡10秒')
sleep(10)
print('醒了')
return {'message': 'hello'}

if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)

    分别启动服务

    Flask:python3 manage.py

    FastAPI:uvicorn manage:app --reload

    同时一时间内,开启多个浏览器,分别并发请求首页 。

    Flask:http://localhost:5000

    FastAPI:http://localhost:8000

    观察后台打印结果:

    Flask:

    FastAPI:

    可以看到,同样的四次请求,Flask先是阻塞了40秒,然后依次返回结果,FastAPI则是第一次阻塞后直接返回,这代表了在FastAPI中阻塞了一个事件队列,证明FastAPI是异步框架,而在Flask中,请求可能是在新线程中运行的。将所有CPU绑定的任务移到单独的进程中,所以在FastAPI的例子中,只是在事件循环中sleep(所以异步框架这里最好不要使用time.sleep而是asyncio.sleep)。在FastAPI中,异步运行IO绑定的任务。

    当然这不能说明太多问题,我们继续使用鼎鼎有名的ApacheBench分别对两款框架进行压测。

    一共设置5000个请求,QPS是100(请原谅我的机器比较渣)。

ab -n 5000 -c 100 http://127.0.0.1:5000/
ab -n 5000 -c 100 http://127.0.0.1:8000/

    这里为了公平起见,Flask配合Gunicorn服务器,开3个worker,FastAPI配合Uvicorn服务器,同样开3个worker。

    Flask压测结果:

liuyue:mytornado liuyue$ ab -n 5000 -c 100 http://127.0.0.1:5000/
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software: gunicorn/20.0.4
Server Hostname: 127.0.0.1
Server Port: 5000

Document Path: /
Document Length: 28 bytes

Concurrency Level: 100
Time taken for tests: 4.681 seconds
Complete requests: 5000
Failed requests: 0
Total transferred: 1060000 bytes
HTML transferred: 140000 bytes
Requests per second: 1068.04 [#/sec] (mean)
Time per request: 93.629 [ms] (mean)
Time per request: 0.936 [ms] (mean, across all concurrent requests)
Transfer rate: 221.12 [Kbytes/sec] received

    FastAPI压测结果:

liuyue:mytornado liuyue$ ab -n 5000 -c 100 http://127.0.0.1:8000/
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software: uvicorn
Server Hostname: 127.0.0.1
Server Port: 8000

Document Path: /
Document Length: 19 bytes

Concurrency Level: 100
Time taken for tests: 2.060 seconds
Complete requests: 5000
Failed requests: 0
Total transferred: 720000 bytes
HTML transferred: 95000 bytes
Requests per second: 2426.78 [#/sec] (mean)
Time per request: 41.207 [ms] (mean)
Time per request: 0.412 [ms] (mean, across all concurrent requests)
Transfer rate: 341.27 [Kbytes/sec] received

    显而易见,5000个总请求,Flask花费4.681秒,每秒可以处理1068.04个请求,而FastAPI花费2.060秒,每秒可以处理2426.78个请求。

    结语:曾几何时,当人们谈论Python框架的性能时,总是不自觉的嗤之以鼻 ,而现在,Python异步生态正在发生着惊天动地的变化,新的框架应运而生(Sanic、FastAPI),旧的框架正在重构(Django3.0),很多库也开始支持异步(httpx、Sqlalchemy、Mortor)。软件科技发展的历史表明,一项新技术的出现和应用,常常会给这个领域带来深刻的变革,古语有云:察势者智,顺势者赢,驭势者独步天下。所以,只有拥抱未来、拥抱新技术、顺应时代才是正确的、可持续发展的道路。