Python非同步程式設計之web框架 非同步vs同步 資料庫IO任務並行支援對比

2023-06-08 12:01:40

測試基本資訊

主題:比較非同步框架和同步框架在資料庫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非同步程式設計的文章。包括同非同步框架效能對比、非同步事情驅動原理等。首發微信公眾號,歡迎關注第一時間閱讀。