V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
LeeReamond
V2EX  ›  问与答

2021 年,用 Python 部署异步网络服务的最佳实践是什么?

  •  
  •   LeeReamond · Jan 24, 2021 · 4007 views
    This topic created in 1926 days ago, the information mentioned may be changed or developed.

    先说几句题外话,前两天看见一个帖子,提到异步框架的,里面很多人推荐 fastapi 。我个人说来很惭愧,学习 python 的 web 框架,入门是 flask,异步是 aiohttp,一直与 django 和 fastapi 这类主流的、用的人比较多的框架无缘。

    所以这次也是想学习一下 fastapi,看看相对于一直使用的原生 aiohttp 有什么区别。

    根据我个人理解,异步从 python3.4 版本提出以来,现在已经不是像 3.6 版本时候那样大家都不会用,现在用异步的人应该越来越多了。目前主流不管是公司内部服务,还是生产级服务,如果上 python 的话,如果要用异步的话,应该是很多人使用 django 的 asgi,一些人使用 fastapi,几乎没有人使用 aiohttp 这样。tornado 我不太了解,因为我最初接触异步是 3.5 时代,彼时 tornado 的异步是用猴子补丁实现的,所以一直也没做接触,不知道现在是怎么样了。

    使用异步框架当然第一步还是看性能,我去 fastapi 官网看了一下教学,教学写的很友好,直接就推荐了 fastapi+uvicorn 的部署方案。

    官网上写了 fastapi 是最快的框架之一,我们都知道 python 异步刚出的时候有很多昙花一现的框架,比如 Vibora,japronto 这些,性能做的都非常夸张,单例可以达到十万 qps,实际上是用 py 胶水封装了一下 c 框架而已,性能高也很正常,可惜这些开发社区做了 demo 出来以后都不怎么活跃了,bug 不修,没法投入生产级。

    倒是 aiohttp 这个一上来看起来就很弱的,表现也不怎么亮眼的,一直更新到现在,投入生产级也完全没问题了,说句题外话,我个人使用起来主要优势就是用的熟,想实现什么效果几乎以前都做过,很快都能找到解决方案,所以学习 fastapi 对我来说倒是要考虑学习成本问题。

    =====================================================================

    说回正题,关于压力测试,我在虚拟机上用 wrk 进行压测,测试结果 fastapi 其实表现并不好,想问一下各位 fastapi 用的比较熟练的大佬,是我部署错误,还是它的性能表现就是这样的。

    另外想问一下切换到生产级服务的话,fastapi 这条路线目前坑度怎么样,比如 web 部署里的一些常用插件,cors,basic auth,jwt 等等,还有中间件开发,支持 ws 协议等等,目前这些坑都踩的差不多了吗?这个框架从名字来看就可以看出是为 api 设计的,如果用来一体化部署 spa 之类的,有额外的坑吗?

    谢谢大家

    =====================================================================

    附一些压测数据

    #笔记本随手测一下,虚拟机给了 8 核心,所以用 16 线程 500 并发进行测试
    
    # fastapi + uvicorn 部署,单进程
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    20.88ms    3.51ms  50.90ms   95.22%
        Req/Sec     0.96k    66.29     1.21k    83.54%
      95737 requests in 20.01s, 13.70MB read
    Requests/sec:   4784.35
    Transfer/sec:    700.83KB
    
    # aiohttp + 自带服务部署,单进程
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    12.90ms    2.94ms  58.01ms   94.81%
        Req/Sec     1.57k   156.69     1.82k    80.90%
      156446 requests in 20.05s, 24.32MB read
    Requests/sec:   7803.19
    Transfer/sec:      1.21MB
    
    # aiohttp + gunicorn(uvloop 模式) ,单进程
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     6.12ms    1.08ms  23.27ms   90.35%
        Req/Sec     3.28k   216.93     5.05k    72.67%
      327908 requests in 20.10s, 50.97MB read
    Requests/sec:  16315.63
    Transfer/sec:      2.54MB
    

    一般来说这些框架都会自带一个 web 服务,可以用来做测试什么的,一般因为稳定性,性能等等原因,都不会用在生产环境部署。但是根据这个单线程测试,fastapi 实际上单进程只有 aiohttp 的 60%,如果用 gunicorn 部署的话(值得吐槽的是 gunicorn 似乎本身也是 python 中不算快的部署方式。。),fastapi+uvicorn 的组合只有 aiohttp+gunicorn 25%左右的性能

    然后是多进程 prefork 测试,采用 8 线程部署服务。

    # fastapi 8 线程
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     6.53ms    1.94ms  33.58ms   79.44%
        Req/Sec     3.09k   476.62     4.02k    60.60%
      307858 requests in 20.07s, 28.48MB read
    Requests/sec:  15341.09
    Transfer/sec:      1.42MB
    
    # fastapi 增大 echo 报文长度
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     9.56ms    6.59ms  51.53ms   61.89%
        Req/Sec     2.18k     1.18k    6.39k    87.10%
      217184 requests in 20.05s, 24.85MB read
    Requests/sec:  10834.00
    Transfer/sec:      1.24MB
    
    # aiohttp 8 线程
    Running 20s test @ http://127.0.0.1:8000
      16 threads and 500 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     2.77ms    1.34ms  16.60ms   65.92%
        Req/Sec     7.30k     2.28k   19.59k    73.53%
      726936 requests in 20.10s, 123.40MB read
    Requests/sec:  36170.63
    Transfer/sec:      6.14MB
    

    可以看到同样地,fastapi 性能只有 aiohttp 的三成左右。另外值得吐槽的是使用长报文测试下,fastapi 的 echo 性能衰退又有点厉害啊,直接掉三成。

    26 replies    2021-01-28 03:38:35 +08:00
    TypeError
        1
    TypeError  
       Jan 24, 2021
    https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=fortune&l=zijzen-1r

    看这个 benchmark,FastAPI 比 aiohttp 高不少,
    我感觉 Python 异步框架没必要追求极致性能,我倾向顺手和生态成熟的,
    目前线上生产环境 Tornado 、aiohttp 都有,更喜欢 Tornado,
    接下来可能迁到 Go 了,性能方面还是静态语言优势大
    LeeReamond
        2
    LeeReamond  
    OP
       Jan 24, 2021
    @TypeError 不知道它这个怎么测试的,aiohttp 倒是和我测的差不多,即使 prefork 也有一个上限,大概四万 qps 左右,并不能 8 线程就 8 倍可用性。fastapi 我已经用 uvicorn 部署了,理论上已经是最佳配置了,不知道它这个怎么搞出来五万 qps 的。

    go 和 py 这种属于无谓争论,用 py 上生产肯定还是看重开发效率,py 内部换个框架都要问一问有没有坑的问题,换 go 就更苦了,go 目前这个生态。。。
    Carry0317
        3
    Carry0317  
       Jan 24, 2021
    请教 我想提供一个 gpu 的服务 用哪种方式性能最高
    so1n
        4
    so1n  
       Jan 25, 2021
    可能有些配置没写对吧.
    fastapi 的底子就是 starlette, 但为了做类型转化和参数校验,性能会比 starlette 略差一点. 然后 aio 的库相比其他同类的库都不太好(可能是出现太早的原因)
    LeeReamond
        5
    LeeReamond  
    OP
       Jan 25, 2021
    @Carry0317 这篇帖子跟 GPU 没什么关系吧。你的意思是想提供一个高可用的 gpu 服务接口?
    LeeReamond
        6
    LeeReamond  
    OP
       Jan 25, 2021
    @so1n 就是很简单的按照 quick start 定义了一个异步函数,绑定到'/',返回一个 echo,没有其他任何东西。部署方面,8 个 fork,关闭 log 。uvicorn main:app --worker 8 --log-level error,不知道有哪里配置还能提高性能的。

    aio 库性能差我觉得应该没有这个说法。python 的异步从一开始就没有什么黑魔法,当初 dableaz 在 pycon 花半小时就实现一个功能完整的 eventloop,可以完全替代原生进行 basic tcp socket programming 的。所以 eventloop 相同的情况下,实现方面完全可追溯,封装程度其实区别不大,影响也不大,理论上无所谓 aio 库慢与否,实践当中我也从没听说过有人说 aiolibs 里面的东西比同类慢。
    ManjusakaL
        7
    ManjusakaL  
       Jan 25, 2021 via iPhone
    直接 Gevent 不香么...
    asyncio 那么多💩,活着不好么😂😂🐶🐶
    LeeReamond
        8
    LeeReamond  
    OP
       Jan 25, 2021
    @ManjusakaL 我觉得你对屎可能有些误解。即使在 python3.5 时代,原生异步也并不屎,这种用户态完全可控、可预测的状态显然是更优的设计。gevent 无法做到以上任何一项,用户用脚投票也说明了这点。
    ManjusakaL
        9
    ManjusakaL  
       Jan 25, 2021 via iPhone
    @LeeReamond 很抱歉,可能用💩来形容不太合适,不过依旧只能用糟糕来形容.
    顺便指出几个误区

    1. Gevent 也是完全可控,可预测
    2. 原生 asyncio bug 到现在为止 bug 太多,随手举几个例子,BPO-30698 和 BPO-29406 这两个横跨 asyncio 到现在的会导致一些 https 链接泄漏的 bug 到现在依旧没有修
    3. 生态一如即往的糟糕,随手举个例子,aiomysql,目前 asyncio 生态中的 mysql lib,三个月没更新可以说是个 dead project 了. 当然你要说我用 thread Future 封个 mysqlclient 当我没说

    我自己应该是最早一批在国内推动 asyncio 上生产的人( 17-18 年)在给予厚望后,我写下了这篇文章 https://manjusaka.itscoder.com/posts/2018/10/05/why-i-dont-use-async/

    我可以很负责任的说,我这篇文章中写的大部分弊端依旧适用于 2021 年的现在
    ManjusakaL
        10
    ManjusakaL  
       Jan 25, 2021 via iPhone
    @LeeReamond BTW 你对于 asyncio 的可预测存在误解
    event loop 是无法真正意义上做到“可预测”的
    此处“可预测”指 A task 执行完后能预测下一个 task 是 B 还是 C
    无论是 asyncio 还是 Gevent 我们都只能做到一个基础的保证,即正常情况下,我能在一个切出点后能切入执行后续代码
    但是在生产环境中,配合 Python 的 GIL,能做到这点也是奢望
    随手举例时间,我们在请求 www.v2ex.com 的时候,会通过 gethostbyname ( Linux 下,参见 https://man7.org/linux/man-pages/man3/gethostbyname.3.html )来做 DNS 解析,而这个函数是不可调度函数,所以那么一旦 DNS 解析出现问题,那么可能炸整个 event loop 导致所有 task 不可调度. 而这样复杂的可能阻塞整个 event loop 调度情况还有很多,此处不一一列举. 诚然我们可以通过很多额外的手段来尽可能规避这种情况. 但是就其本身而言,无论 asyncio 还是 Gevent,其所要面对的问题都是一样的
    LeeReamond
        11
    LeeReamond  
    OP
       Jan 25, 2021
    @ManjusakaL 认真看完了,大佬确实经验丰富。我因为学习异步的时候已经出现原生异步了,所以对猴子补丁天生有不信任,承认错误。我们在简单的生产环境(非内部管理平台)中使用原生异步体验良好,可能有些过于信任。

    搜了一下你说的 BPO-30698,ssl 链接泄露应该如何理解,似乎不是一个导致明文泄露的恶性 bug,而是导致内存不能回收的问题,不知是否理解正确。在 17/18 年左右倒是听说过有人 aiohttp 框架出现 ssl 内存泄露,我从未遇到过类似问题,以为在新版中已经修好了。看了这个 issue,不理解如何复现。

    你在帖子中提到的同步异步混合,以及生态不支持 c 插件等问题,我个人理解这两个目前已经不是问题,我的理解中异步代码中首先不应存在同步内容,我从未体验过同步异步同时维护的复杂度。另外生态方面主要是接入后端,python 本身的阻塞实现倒是能用附带线程池的方式梭掉,顺带 cython 还能解决掉 gil,而后端方面,mysql 和 redis,oracle 也有异步连接方式,我使用 aiolibs 的库体验良好,可能是接入服务数少,我个人而言这方面没什么不满。

    另外大佬这么推崇猴子补丁,有没有 gevent 系列比较合适的入门文章,我想完整评估一下 gevent 相对于原生异步方案的性能和稳定性
    wdhwg001
        12
    wdhwg001  
       Jan 25, 2021 via iPhone
    fastapi 不是要用 gunicorn 套 uvicorn 吗? techempower 是开源的,可以去看他们的部署和代码。
    wdhwg001
        13
    wdhwg001  
       Jan 25, 2021 via iPhone
    另外 techempower 里的 fastapi 代码是有轻微作弊的,主要是 ujson,不过也不严重,你甚至可以用 orjson 跑的更高一点。

    我的观点是常量级差距都不用太在意的,fastapi 还有完善的 openapi 支持什么的,那些要更吸引人一点。
    wdhwg001
        14
    wdhwg001  
       Jan 25, 2021 via iPhone
    另外 asyncio 应该是大势所趋了,生态在逐步完善,但是距离 wsgi 时代还是有差距的,然而依旧是好兆头。
    其实单说 fastapi 也是问题多多的,缺少 session 支持是一个,不完全遵守 asgi 是一个,中间件还有闭包引用不可靠的问题,自带路由是遍历而不是树优化也是一个问题。但即使如此,fastapi 的设计也依然是比 flask 好一些的。
    而且其实更大的坑是 orm,gino 和 tortoise 都有各自的坑,django 的 async orm 还在难产,我这边项目用的 tortoise,设计上基本就是抄 django 了,没什么创新点。
    LeeReamond
        15
    LeeReamond  
    OP
       Jan 25, 2021
    @wdhwg001 gunicorn 套 uvicorn 怎么实现,感觉这两个不能互相套啊

    楼上说的 ssl 泄露的 issue 我看倒是确实没人理。印象中 17 年左右 stackoverflow 的 asyncio 区还是极端冷清的,不知道现在怎么样。我个人体验来讲,倒是 3.5 时代感觉原生异步的学习过程很底层,从生成器概念一步步概念学上来,最近两年倒是完全感觉在使用高级 api,完全没有底层的感觉了,基本和写同步代码没有任何区别,只是外面要套一层扳机而已。
    LeeReamond
        16
    LeeReamond  
    OP
       Jan 25, 2021
    @wdhwg001 orm 方面我是完全不做任何希望了,我觉得以 python 社区的生产能力 orm 大概是要永远难产下去了。我个人使用体验上倒是没体会到 orm 对开发速度有多大帮助,都是直接操作数据库,所以倒是感觉不很有所谓
    spcharc
        17
    spcharc  
       Jan 25, 2021
    aiohttp 库 contributor 路过,很惭愧只做了一点微小的工作(大概+366 −48 这样子)
    我感觉 aiohttp,一般不是配合 aiodns 来用吗?
    官方都提供了 pip install aiohttp[speedups]这种安装方式来捆绑销售 aiodns,不就是因为一旦 dns 服务不稳定,就可能阻塞整个 loop 嘛?
    另外官方也推荐搭配使用 uvloop,比 asyncio 自带 loop 速度快,也没有上面提到的 https 泄露之类的问题
    而且 loop (不管是 python 官方的还是第三方的)都提供了 run_in_executor 的吧,有可能长时间阻塞的函数都应该用这个来运行来避免 loop 阻塞啊
    LeeReamond
        18
    LeeReamond  
    OP
       Jan 25, 2021 via Android
    @spcharc 我印象中确实是有见过生产级部署以后出现莫名泄露的问题的帖子,大概几年前。我自己没遇到过。另外我对楼上说的猴子布丁原理上性能高于 libuv 仿品很好奇
    so1n
        19
    so1n  
       Jan 25, 2021 via Android
    @LeeReamond 我不是说性能,比如 aiohttp 的 client 就一堆隐藏坑,aioredis 停止更新,集群到现在都没支持等等
    Carry0317
        20
    Carry0317  
       Jan 25, 2021
    @LeeReamond 是的 高可用的 gpu 服务接口
    wdhwg001
        21
    wdhwg001  
       Jan 25, 2021 via iPhone
    @LeeReamond Django 系的 orm 大多数时候够用了,可以不用去手写 crud 的,除非你用到了很复杂的用法或者特殊的函数。totorise 作为 django orm 的残品其实也勉强够用,觉得遗憾的地方只是没有新东西,没有 flask 到 fastapi 的那种换代感。
    另外你倒是看一下源码啊。
    wdhwg001
        22
    wdhwg001  
       Jan 25, 2021 via iPhone
    @LeeReamond 另外这个泄露的问题如果用 uvloop 是不会出现的,同时只能通过重写 asyncio 解决,而重写现在还在进行中。
    LeeReamond
        23
    LeeReamond  
    OP
       Jan 25, 2021 via Android
    @so1n redis 集群不是自身特性,为什么需要客户端支持呢
    @Carry0317 目前按照我这个帖子,纯 py 的方法就是 uvloop+aiohttp+gunicorn 。楼上有老哥提到 fastapi 部署是 uvicorn+gunicorn 但他没说怎么做,我暂不理解。另外如果你的应用层封装简单,也可以试试 vibora,japronto 这类 c 库,应该可以获得最大转发效率,比较基础的使用上应该也没什么坑,大概吧。不过你这个 gpu 业务本身需要 python+http 转发本身就挺奇怪的
    wdhwg001
        24
    wdhwg001  
       Jan 28, 2021   ❤️ 1
    LeeReamond
        25
    LeeReamond  
    OP
       Jan 28, 2021
    @wdhwg001 感谢,我不太熟悉这个 benchmark 的项目,你不指路的话我都不知道你指的源码是这个。我按他的部署测了一下,确实是速度快一些,整体单进程和 prefork 的性能和 aiohttp+gunicorn+uvloop 几乎相当,可能 gunicorn 自己是 python 写的,性能极限就这么多了吧,在我的机器上测试 qps 并没有超过四万。

    大概是这样

    Thread Stats Avg Stdev Max +/- Stdev
    Latency 2.79ms 1.39ms 17.14ms 68.74%
    Req/Sec 7.31k 2.07k 12.89k 67.10%
    727813 requests in 20.02s, 85.37MB read
    Requests/sec: 36348.24
    Transfer/sec: 4.26MB

    作为对比,后面用 pyston 跑了一下,加入 jit 以后 fastapi 确实是更快一些
    fastapi+python3.8: Requests/sec: 36348.24
    fastapi+pyston2.1: Requests/sec: 45435.96

    aiohttp+python3.8: Requests/sec: 36170.63
    aiohttp+pyston2.1: Requests/sec: 42089.11


    所以 fastapi 相对于 aiohttp 或者 tornado 这些传统异步框架没有性能劣势,那么使用 fastapi 的优势是什么呢,除了比较火以外,用来做后端服务器似乎很合适,因为可以自动生成文档,不过这个其实也还好,不是那么的决定性,不太清楚还有没有什么其他优势。不过不管怎么说 2021 年感觉部署大型 web 应用,如果使用 py 的话,起码语言不应作为性能瓶颈看待了。不知道当初知乎、豆瓣这些网站,都是 py2 时代码出来的项目,后面都传出来过重构的新闻,如果用现在的 python 的话应该是不会重构了吧
    wdhwg001
        26
    wdhwg001  
       Jan 28, 2021
    @LeeReamond FastAPI 主要是省事和优雅,它的 API 总体上是 Flask 风格的,可以提供 OpenAPI,也可以用 Pydantic 做自动的输入验证,还可以用依赖注入的方式比较方便地实现鉴权,总的来说就是好用,并且性能代价不高。

    不过大型 Web 应用估计还是不适合用 Python,但是能在被迫用 Java 或者 Go 重写之前撑更久。
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2694 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 79ms · UTC 10:14 · PVG 18:14 · LAX 03:14 · JFK 06:14
    ♥ Do have faith in what you're doing.