先说几句题外话,前两天看见一个帖子,提到异步框架的,里面很多人推荐 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 性能衰退又有点厉害啊,直接掉三成。
1
TypeError 2021-01-24 21:09:48 +08:00
https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=fortune&l=zijzen-1r
看这个 benchmark,FastAPI 比 aiohttp 高不少, 我感觉 Python 异步框架没必要追求极致性能,我倾向顺手和生态成熟的, 目前线上生产环境 Tornado 、aiohttp 都有,更喜欢 Tornado, 接下来可能迁到 Go 了,性能方面还是静态语言优势大 |
2
LeeReamond OP @TypeError 不知道它这个怎么测试的,aiohttp 倒是和我测的差不多,即使 prefork 也有一个上限,大概四万 qps 左右,并不能 8 线程就 8 倍可用性。fastapi 我已经用 uvicorn 部署了,理论上已经是最佳配置了,不知道它这个怎么搞出来五万 qps 的。
go 和 py 这种属于无谓争论,用 py 上生产肯定还是看重开发效率,py 内部换个框架都要问一问有没有坑的问题,换 go 就更苦了,go 目前这个生态。。。 |
3
Carry0317 2021-01-24 23:02:36 +08:00
请教 我想提供一个 gpu 的服务 用哪种方式性能最高
|
4
so1n 2021-01-25 00:07:57 +08:00
可能有些配置没写对吧.
fastapi 的底子就是 starlette, 但为了做类型转化和参数校验,性能会比 starlette 略差一点. 然后 aio 的库相比其他同类的库都不太好(可能是出现太早的原因) |
5
LeeReamond OP @Carry0317 这篇帖子跟 GPU 没什么关系吧。你的意思是想提供一个高可用的 gpu 服务接口?
|
6
LeeReamond OP @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 里面的东西比同类慢。 |
7
ManjusakaL 2021-01-25 02:00:39 +08:00 via iPhone
直接 Gevent 不香么...
asyncio 那么多💩,活着不好么😂😂🐶🐶 |
8
LeeReamond OP @ManjusakaL 我觉得你对屎可能有些误解。即使在 python3.5 时代,原生异步也并不屎,这种用户态完全可控、可预测的状态显然是更优的设计。gevent 无法做到以上任何一项,用户用脚投票也说明了这点。
|
9
ManjusakaL 2021-01-25 02:38:52 +08:00 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 年的现在 |
10
ManjusakaL 2021-01-25 03:00:06 +08:00 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,其所要面对的问题都是一样的 |
11
LeeReamond OP @ManjusakaL 认真看完了,大佬确实经验丰富。我因为学习异步的时候已经出现原生异步了,所以对猴子补丁天生有不信任,承认错误。我们在简单的生产环境(非内部管理平台)中使用原生异步体验良好,可能有些过于信任。
搜了一下你说的 BPO-30698,ssl 链接泄露应该如何理解,似乎不是一个导致明文泄露的恶性 bug,而是导致内存不能回收的问题,不知是否理解正确。在 17/18 年左右倒是听说过有人 aiohttp 框架出现 ssl 内存泄露,我从未遇到过类似问题,以为在新版中已经修好了。看了这个 issue,不理解如何复现。 你在帖子中提到的同步异步混合,以及生态不支持 c 插件等问题,我个人理解这两个目前已经不是问题,我的理解中异步代码中首先不应存在同步内容,我从未体验过同步异步同时维护的复杂度。另外生态方面主要是接入后端,python 本身的阻塞实现倒是能用附带线程池的方式梭掉,顺带 cython 还能解决掉 gil,而后端方面,mysql 和 redis,oracle 也有异步连接方式,我使用 aiolibs 的库体验良好,可能是接入服务数少,我个人而言这方面没什么不满。 另外大佬这么推崇猴子补丁,有没有 gevent 系列比较合适的入门文章,我想完整评估一下 gevent 相对于原生异步方案的性能和稳定性 |
12
wdhwg001 2021-01-25 04:15:11 +08:00 via iPhone
fastapi 不是要用 gunicorn 套 uvicorn 吗? techempower 是开源的,可以去看他们的部署和代码。
|
13
wdhwg001 2021-01-25 04:34:19 +08:00 via iPhone
另外 techempower 里的 fastapi 代码是有轻微作弊的,主要是 ujson,不过也不严重,你甚至可以用 orjson 跑的更高一点。
我的观点是常量级差距都不用太在意的,fastapi 还有完善的 openapi 支持什么的,那些要更吸引人一点。 |
14
wdhwg001 2021-01-25 04:49:43 +08:00 via iPhone
另外 asyncio 应该是大势所趋了,生态在逐步完善,但是距离 wsgi 时代还是有差距的,然而依旧是好兆头。
其实单说 fastapi 也是问题多多的,缺少 session 支持是一个,不完全遵守 asgi 是一个,中间件还有闭包引用不可靠的问题,自带路由是遍历而不是树优化也是一个问题。但即使如此,fastapi 的设计也依然是比 flask 好一些的。 而且其实更大的坑是 orm,gino 和 tortoise 都有各自的坑,django 的 async orm 还在难产,我这边项目用的 tortoise,设计上基本就是抄 django 了,没什么创新点。 |
15
LeeReamond OP @wdhwg001 gunicorn 套 uvicorn 怎么实现,感觉这两个不能互相套啊
楼上说的 ssl 泄露的 issue 我看倒是确实没人理。印象中 17 年左右 stackoverflow 的 asyncio 区还是极端冷清的,不知道现在怎么样。我个人体验来讲,倒是 3.5 时代感觉原生异步的学习过程很底层,从生成器概念一步步概念学上来,最近两年倒是完全感觉在使用高级 api,完全没有底层的感觉了,基本和写同步代码没有任何区别,只是外面要套一层扳机而已。 |
16
LeeReamond OP @wdhwg001 orm 方面我是完全不做任何希望了,我觉得以 python 社区的生产能力 orm 大概是要永远难产下去了。我个人使用体验上倒是没体会到 orm 对开发速度有多大帮助,都是直接操作数据库,所以倒是感觉不很有所谓
|
17
spcharc 2021-01-25 05:35:05 +08:00
aiohttp 库 contributor 路过,很惭愧只做了一点微小的工作(大概+366 −48 这样子)
我感觉 aiohttp,一般不是配合 aiodns 来用吗? 官方都提供了 pip install aiohttp[speedups]这种安装方式来捆绑销售 aiodns,不就是因为一旦 dns 服务不稳定,就可能阻塞整个 loop 嘛? 另外官方也推荐搭配使用 uvloop,比 asyncio 自带 loop 速度快,也没有上面提到的 https 泄露之类的问题 而且 loop (不管是 python 官方的还是第三方的)都提供了 run_in_executor 的吧,有可能长时间阻塞的函数都应该用这个来运行来避免 loop 阻塞啊 |
18
LeeReamond OP @spcharc 我印象中确实是有见过生产级部署以后出现莫名泄露的问题的帖子,大概几年前。我自己没遇到过。另外我对楼上说的猴子布丁原理上性能高于 libuv 仿品很好奇
|
19
so1n 2021-01-25 09:24:04 +08:00 via Android
@LeeReamond 我不是说性能,比如 aiohttp 的 client 就一堆隐藏坑,aioredis 停止更新,集群到现在都没支持等等
|
20
Carry0317 2021-01-25 09:41:27 +08:00
@LeeReamond 是的 高可用的 gpu 服务接口
|
21
wdhwg001 2021-01-25 12:02:44 +08:00 via iPhone
@LeeReamond Django 系的 orm 大多数时候够用了,可以不用去手写 crud 的,除非你用到了很复杂的用法或者特殊的函数。totorise 作为 django orm 的残品其实也勉强够用,觉得遗憾的地方只是没有新东西,没有 flask 到 fastapi 的那种换代感。
另外你倒是看一下源码啊。 |
22
wdhwg001 2021-01-25 12:05:06 +08:00 via iPhone
@LeeReamond 另外这个泄露的问题如果用 uvloop 是不会出现的,同时只能通过重写 asyncio 解决,而重写现在还在进行中。
|
23
LeeReamond OP |
24
wdhwg001 2021-01-28 00:31:18 +08:00 1
|
25
LeeReamond OP @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 的话应该是不会重构了吧 |
26
wdhwg001 2021-01-28 03:38:35 +08:00
@LeeReamond FastAPI 主要是省事和优雅,它的 API 总体上是 Flask 风格的,可以提供 OpenAPI,也可以用 Pydantic 做自动的输入验证,还可以用依赖注入的方式比较方便地实现鉴权,总的来说就是好用,并且性能代价不高。
不过大型 Web 应用估计还是不适合用 Python,但是能在被迫用 Java 或者 Go 重写之前撑更久。 |