測試基本信息
主題:比較異步框架和同步框架在數據庫IO操作的性能差異
python版本:python 3.8
數據庫:mysql 8.0.27 (docker部署)
壓測工具:locust
web框架:同步:flask 異步:starlette
請求併發量: 模擬10個用戶
服務器配置: Intel(R) i7-12700F
客戶端配置:Intel(R) i7-8700 3.20GHz
flask 同步框架
python中操作數據庫通常使用ORM框架完成。flask 使用的ORM框架是大名鼎鼎的sqlalchemy,該框架是所有ORM框架中生態最完善的,但是sqlalchemy對異步的支持並不算友好,目前支持原生SQL操作。
定義表結構
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, Column, Integer, String
DB_URI = "mysql+pymysql://root:[email protected]:3306/test"
engine = create_engine(DB_URI)
Base = declarative_base(engine) # SQLORM基類
class Student(Base):
__tablename__ = 'student' # 表名
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50))
Base.metadata.create_all() # 將模型映射到數據庫中
api接口
from flask import Flask, request
from sqlalchemy.orm import sessionmaker
from model import Student, engine, Base
app = Flask(__name__)
@app.route('/users')
def list_all():
session = sessionmaker(engine)() # 構建session對象
students = session.query(Student).all()
res = [stu.name for stu in students]
session.close()
return res
@app.route("/user", methods=["POST"])
def create():
session = sessionmaker(engine)() # 構建session對象
student = Student(name='Tony') # 創建一個student對象
session.add(student) # 添加到session
session.commit() # 提交到數據庫
session.close()
return {"res":"success"}
if __name__ == '__main__':
app.run(port=8090, host="0.0.0.0")
接口調用
post請求
get 請求:
壓測結果
併發量:225
starlette 異步框架
starlette 異步框架配套使用的ORM框架是 tortoise-orm。tortoise-orm 是一款方便、易用、可靠的異步ORM框架,文檔:https://tortoise.github.io/
安裝數據庫
pip install tortoise-orm
pip install aiomysql
定義表結構
from tortoise import fields, models
class Users(models.Model):
id = fields.IntField(pk=True)
username = fields.CharField(max_length=20)
def __str__(self) -> str:
return f"User {self.id}: {self.username}"
api接口
from model import Users
from starlette.applications import Starlette
from tortoise.contrib.starlette import register_tortoise
from starlette.requests import Request
from starlette.responses import JSONResponse
from uvicorn.main import run
app = Starlette()
# 查詢記錄
@app.route("/user", methods=["GET"])
async def list_all(_: Request):
users = await Users.all()
return JSONResponse({"users": [str(user) for user in users]})
# 創建記錄
@app.route("/user", methods=["POST"])
async def create(request: Request):
body = await request.json
username = body.get("user_name")
user = await Users.create(username=username)
return JSONResponse({"ret":"success"})
# 表遷移
register_tortoise(app, db_url="mysql://root:[email protected]:3306/test", modules={"models": ["model"]}, generate_schemas=True)
if __name__ == "__main__":
run(app, host="0.0.0.0")
接口調用
post請求創建一個用戶
get請求
壓測結果
併發量:1496
性能對比
併發曲線圖對比:
參數對比:
框架 | 數據庫IO | 延遲 | 純框架無IO | 文件IO |
---|---|---|---|---|
fastapi | 225 | 47 ms | 463 | 453 |
starlette | 1496 | 6 ms | 1539 | 1538 |
總結
在數據庫讀寫方面,異步框架性能是同步框架的6倍,可以看出同步框架在數據庫讀寫頻繁時性能下降明顯,幾乎是無IO讀寫的一半,而異步框架在有數據庫IO的情況下併發幾乎保持不變。主要原因是在當前簡單的場景下數據庫讀操作耗時少而連接釋放耗時較多,異步編程非阻塞模型不等待,所以數據庫連接釋放耗時並不阻塞異步框架。下一篇比較redis IO的併發性能。
連載一系列關於python異步編程的文章。包括同異步框架性能對比、異步事情驅動原理等。首發微信公衆號,歡迎關注第一時間閱讀。