目录
先上结论
sanic处理get请求最快,是其他框架的2-4倍
sanic处理post请求最快,是其他框架的2-2.5倍
tornado替换uvloop,能带来1/10的提升,但提升很有限
实际生产部署(平均4000行代码,2次Redis操作,3次post请求),对比tornado和sanic,都是17rps,两个框架没有性能差别,目前分析发现:
1.aioredis效率不稳定,单独测试1000并发时,平均耗时增加1s/每次,不知是否与Redis的单机部署有关,但这个不是最后瓶颈
2.一个业务接口,总逻辑代码近8000行,无io操作,Python实际运行行数大概在2000-6000行,耗时25-50ms不等,效率很低,即使异步无io操作,已经决定了1s只能处理最多50个请求
3.协程之前切换切换效率低:测试代码中仅仅加一个sleep 0.1s,tornado并发由1555降低到400,再次说明由于Python本身运行效率,导致切换速度并不快
综上,对于高并发、复杂逻辑的服务,Python无论本身运行效率还是协程切换效率都是瓶颈,还是直接用go开发吧
tornado | tornado_uvloop | sanic | fastapi | |
get并发RPS | 1002 | 1691 | 4145 | 2025 |
get平均耗时ms | 99 | 59 | 24 | 49 |
post并发RPS | 1555 | 1731 | 4118 | 2008 |
post平均耗时ms | 64 | 57 | 24 | 49 |
实验代码
安装包
pip install sanic==20.6.2
pip install fastapi==0.61.0
pip install uvicorn
pip install tornado==6.0.2
服务端代码
tornado异步
#run_tornado.py
# tornado-6.0.2
import tornado.ioloop
import tornado.web
import json
class MainHandler(tornado.web.RequestHandler):
async def get(self):
self.write("Hello, world")
async def post(self):
self.write(json.dumps({"code": "0000", "msg": "Hello, world"}))
def make_app():
return tornado.web.Application([
(r"/test", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8001)
tornado.ioloop.IOLoop.current().start()
tornado_uvloop异步
# run_tornado_uvloop.py
# tornado-6.0.2
import asyncio
import tornado.ioloop
import tornado.web
import json
import uvloop
from tornado.platform.asyncio import AsyncIOMainLoop
class MainHandler(tornado.web.RequestHandler):
async def get(self):
self.write("Hello, world")
async def post(self):
self.write(json.dumps({"code": "0000", "msg": "Hello, world"}))
def make_app():
return tornado.web.Application([
(r"/test", MainHandler),
])
if __name__ == '__main__':
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
AsyncIOMainLoop().install()
app = make_app()
app.listen(8003)
asyncio.get_event_loop().run_forever()
fastapi异步
# run_fastapi.py
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/test")
def read_root():
return "Hello, world"
@app.post("/test")
def read_item():
return {"code": "0000", "msg": "Hello, world"}
fast_api启动命令:nohup uvicorn run_fastapi:app --reload &
sanic异步
# run_sanic.py
from sanic import Sanic
from sanic.response import json,html
app = Sanic()
@app.route('/test',methods=['GET'])
async def hello(request):
return html("Hello, world")
@app.route('/test', methods=['POST'])
async def hello2(request):
return json({"code": "0000", "msg": "Hello, world"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8002)
sanic启动命令:nohup python run_sanic.py &
异步请求代码
# async_request.py
import time
import httpx
import asyncio
loop = asyncio.get_event_loop()
async def requ(port):
url = f"http://localhost:{port}/test"
s = time.time()
async with httpx.AsyncClient() as client:
res = await client.get(url=url)
# print(port,res.text)
# print(time.time() - s)
s = time.time()
async with httpx.AsyncClient() as client:
res = await client.post(url=url)
# print(port,res.text)
# print(time.time() - s)
for port in [8000,8001,8002,8003]:
tasks = asyncio.gather(*[requ(port) for i in range(100)])
ss = time.time()
loop.run_until_complete(tasks)
print(f"{port}总耗时:",time.time() - ss)
# 8000总耗时: 3.102787733078003
# 8001总耗时: 3.017672061920166
# 8002总耗时: 3.00693678855896
# 8003总耗时: 3.008575916290283
ab测试代码
测试get请求
ab -n 100 -c 100 http://localhost:8000/test
测试post请求
ab -n 100 -c 100 -T 'application/json' http://localhost:8000/test
ab命令详解
ab 命令参数说明:
-n 发送请求的数量
-c 同一时刻模拟用户的数量
-s 每个响应的最长等待时间 默认是30秒
-k
-p 发送post请求参数存放的文件 (使用此选项的时候一定要加上-T参数)
-T 文本类型 默认为’text-plain‘ post请求时一般为'application/x-www-form-urlencoded'
ab模拟GET请求
ab -n 100 -c 10 'http://testurl.com/xxxx?para1=aaa¶2=bbb'
ab模拟POST请求
需要把将要post的数据(一般是json格式)放在文件里。比如一个post接口需要如下方式访问
curl -H 'Content-Type:application/json' -X POST -d '{"actionType":"collect","appId":1,"contentId":"1770730744","contentType":"musictrack","did":"866479025346031","endType":"mobile","recommendId":"104169490_1_0_1434453099#1770730744#musictrack#USER_TO_SONG_TO_SONGS#gsql_similarity_content2content","tabId":0,"uid":"104169490"}' http://localhost:8083/query/leui/v0/post/user/behavior/content
需要吧-d 后面的json数据放在一个文件里,比如建立一个文件post_data.txt,放入:
{"actionType":"collect","appId":1,"contentId":"1770730744","contentType":"musictrack","did":"866479025346031","endType":"mobile","recommendId":"104169490_1_0_1434453099#1770730744#musictrack#USER_TO_SONG_TO_SONGS#gsql_similarity_content2content","tabId":0,"uid":"104169490"}
然后用-p参数解析并发送这个json数据:
ab -n 100 -c 10 -p post_data.txt -T 'application/json' http://localhost:8083/query/leui/v0/post/user/behavior/content