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

2021-01-24 19:41:19 +08:00
 LeeReamond

先说几句题外话,前两天看见一个帖子,提到异步框架的,里面很多人推荐 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 性能衰退又有点厉害啊,直接掉三成。

3008 次点击
所在节点    问与答
26 条回复
wdhwg001
2021-01-25 12:02:44 +08:00
@LeeReamond Django 系的 orm 大多数时候够用了,可以不用去手写 crud 的,除非你用到了很复杂的用法或者特殊的函数。totorise 作为 django orm 的残品其实也勉强够用,觉得遗憾的地方只是没有新东西,没有 flask 到 fastapi 的那种换代感。
另外你倒是看一下源码啊。
wdhwg001
2021-01-25 12:05:06 +08:00
@LeeReamond 另外这个泄露的问题如果用 uvloop 是不会出现的,同时只能通过重写 asyncio 解决,而重写现在还在进行中。
LeeReamond
2021-01-25 17:45:45 +08:00
@so1n redis 集群不是自身特性,为什么需要客户端支持呢
@Carry0317 目前按照我这个帖子,纯 py 的方法就是 uvloop+aiohttp+gunicorn 。楼上有老哥提到 fastapi 部署是 uvicorn+gunicorn 但他没说怎么做,我暂不理解。另外如果你的应用层封装简单,也可以试试 vibora,japronto 这类 c 库,应该可以获得最大转发效率,比较基础的使用上应该也没什么坑,大概吧。不过你这个 gpu 业务本身需要 python+http 转发本身就挺奇怪的
wdhwg001
2021-01-28 00:31:18 +08:00
LeeReamond
2021-01-28 01:11:11 +08:00
@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
2021-01-28 03:38:35 +08:00
@LeeReamond FastAPI 主要是省事和优雅,它的 API 总体上是 Flask 风格的,可以提供 OpenAPI,也可以用 Pydantic 做自动的输入验证,还可以用依赖注入的方式比较方便地实现鉴权,总的来说就是好用,并且性能代价不高。

不过大型 Web 应用估计还是不适合用 Python,但是能在被迫用 Java 或者 Go 重写之前撑更久。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/747931

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX