浅谈FastAPI

未经许可请勿转载。
Please do not reprint this article without permission.

众所周知,Python Web开发常用的三大框架是Django、Flask和Tornado。笔者在面试过程中也被常问到这几个框架的特点和区别,具体可参考Python Web 框架:Django、Flask 与 Tornado 的性能对比等等。本文对此不会作深入讨论,而是要介绍一款据说性能更高、更适用于高并发场景的框架——FastAPI。笔者在面试过程中接触到了这个框架,并马上搜索了相关的文档,发现介绍其原理的中文文章并不多,因此借着为秋招复习这个机会,探究一下FastAPI究竟为什么这么快。

什么是FastAPI?

FastAPI是一款现代化、高性能的Web框架,用于构建基于Python3.6及以上的API,其具有以下特征:

  • 速度快:非常高的性能,与NodeJS和Go不相上下,是最快的Python框架之一;
  • 编码快:将开发特性所需的速度提高大约200%到300%;
  • 错误少:减少大约40%的人为(开发)错误;
  • 直观:强大的编辑器支持,支持多场景开发,调试所花的时间更少;
  • 简单:被设计为易于使用和学习,减少阅读文档的时间;
  • 代码少:最小化重复,更少的错误;
  • 健壮:代码可随时部署到生产环境,并自动提供交互文档;
  • 标准:基于(并完全兼容)api的开放标准:OpenAPI(以前称为Swagger)和JSON模式。

具体的使用方法详见中文文档

Starlette & ASGI

根据上面的官方介绍,我们看到FastAPI的速度得益于使用了Starlette——一个轻量级的ASGI框架。

ASGI,全称为Asynchronous Server Gateway Interface,为了规范支持异步的Python Web服务器、框架和应用之间的通信而定制,同时囊括了同步和异步应用的通信规范,并且向后兼容WSGI。由于最新的HTTP协议支持异步长连接,而传统的WSGI应用支持单次同步调用,即仅在接受一个请求后返回响应,从而无法支持HTTP长轮询或WebSocket连接。在Python3.5增加async/await特性之后,基于asyncio和协程的异步应用编程变得更加方便。ASGI协议规范就是用于asyncio框架的最低限度的底层服务器/应用程序接口。

异步非阻塞I/O & 协程

阻塞I/O,非阻塞I/O,I/O多路复用都属于同步I/O。而异步I/O则不一样,当进程发起I/O操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说I/O完成。在这整个过程中,进程完全没有被阻塞。在非阻塞I/O中,虽然进程大部分时间都不会被阻塞,但是它仍然要求进程去主动的查询,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom()来将数据拷贝到用户内存。

相对于线程,协程是程序级的I/O调度,是对一个线程进行分片,使得线程在代码块之间来回切换执行,而非逐行执行,因此能够支持更快的上下文切换。协程本身并不能实现高并发,但与I/O切换结合后能够大大提高性能。每当发生I/O,自动切换协程,让出CPU资源,即可减少高并发场景下服务的响应时间。因此,结合async/await语法,将代码块定义为协程,使用异步服务器即可实现程序级I/O切换和协程调度。

...
async def app(request: Request) -> Response:
    try:
        body = None
        if body_field:
            if is_body_form:
                body = await request.form()
            else:
                body_bytes = await request.body()
                if body_bytes:
                    body = await request.json()
    except json.JSONDecodeError as e:
        raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
    except Exception as e:
        raise HTTPException(
            status_code=400, detail="There was an error parsing the body"
        ) from e
    solved_result = await solve_dependencies(
        request=request,
        dependant=dependant,
        body=body,
        dependency_overrides_provider=dependency_overrides_provider,
    )
    values, errors, background_tasks, sub_response, _ = solved_result
    if errors:
        raise RequestValidationError(errors, body=body)
    else:
        raw_response = await run_endpoint_function(
            dependant=dependant, values=values, is_coroutine=is_coroutine
        )

        if isinstance(raw_response, Response):
            if raw_response.background is None:
                raw_response.background = background_tasks
            return raw_response
        response_data = await serialize_response(
            field=response_field,
            response_content=raw_response,
            include=response_model_include,
            exclude=response_model_exclude,
            by_alias=response_model_by_alias,
            exclude_unset=response_model_exclude_unset,
            exclude_defaults=response_model_exclude_defaults,
            exclude_none=response_model_exclude_none,
            is_coroutine=is_coroutine,
        )
        response = response_class(
            content=response_data,
            status_code=status_code,
            background=background_tasks,
        )
        response.headers.raw.extend(sub_response.headers.raw)
        if sub_response.status_code:
            response.status_code = sub_response.status_code
        return response
...

可以看到app通过async语法定义为协程,在收到请求后,对于需要I/O操作的地方,会使用await关键字让出资源,待I/O完成后等待资源,最终返回响应。

事件循环

因为Python是单线程的,同一时间只能执行一个方法,所以当一系列的方法被依次调用的时候,Python会先解析这些方法,把其中的同步任务按照执行顺序排队到一个地方,这个地方叫做执行栈。主线程之外,还存在一个"任务队列"(task queue)。当遇到异步任务时,异步任务会被挂起,继续执行执行栈中任务,等异步任务返回结果后,再按照执行顺序排列到"事件队列中"。一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。如果有,就将第一个事件对应的回调推到执行栈中执行,若在执行过程中遇到异步任务,则继续将这个异步任务排列到事件队列中。主线程每次将执行栈清空后,就去事件队列中检查是否有任务,如果有,就每次取出一个推到执行栈中执行,这个过程是循环往复的,这个过程被称为Event Loop,即事件循环。

由于异步非阻塞框架基本为单线程运行,因此要利用协程实现事件循环。FastAPI推荐使用uvicorn来运行服务,uvicorn是基于uvloophttptools构建的闪电般快速的ASGI服务器。Python3.5+的标准库asyncio提供了事件循环用来实现协程,并引入了async/await关键字语法以定义协程。同是异步非阻塞框架的Tornado通过yield生成器实现协程,它自身实现了一个事件循环,其在Python3之后也支持async/await关键字语法,以使用标准库asyncio。而FastAPI则是利用了uvloop,相对于asyncio,更进一步地提升了速度。uvloop是用Cython编写的,并建立在libuv之上。libuv是一种高性能的、跨平台异步的I/O类库,nodejs也使用到了它。由于nodejs是如此的广泛和流行,可以知道libuv是快速且稳定的。uvloop实现了所有的asyncio事件循环APIs。高级别的Python对象包装了低级别的libuv结构体和函数方法。 继承可以使得代码保持DRY(不要重复自己),并确保任何手动的内存管理都可以与libuv的原生类型的生命周期保持同步。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容