Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin

news2024/11/27 3:38:04

一、前言

异步编程在构建高性能 Web 应用中起着关键作用,而 FastAPI、Sanic、Tornado 都声称具有卓越的性能。本文将通过性能压测对这些框架与Go的Gin框架进行全面对比,揭示它们之间的差异。

原文:Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin

二、环境准备

系统环境配置

编程语言

语言版本官网/Github
Python3.10.12https://www.python.org/
Go1.20.5https://go.dev/

压测工具

工具介绍官网/Github
abApache的压力测试工具,使用简单https://httpd.apache.org/docs/2.4/programs/ab.html
wrk高性能多线程压力测试工具https://github.com/wg/wrk
JMeter功能强大的压力/负载测试工具https://github.com/apache/jmeter

这里选择 wrk 工具进行压测,mac 安装直接通过brew快速安装

brew install wrk

window安装可能要依赖它的子系统才方便安装,或者换成其他的压测工具例如JMeter。

web框架

框架介绍压测版本官网/Github
FastAPI基于Python的高性能web框架0.103.1https://fastapi.tiangolo.com/
SanicPython的异步web服务器框架23.6.0https://sanic.dev/zh/
TornadoPython的非阻塞式web框架6.3.3https://www.tornadoweb.org/en/stable/
GinGo语言的web框架1.9.1https://gin-gonic.com/
Fibertodotodohttps://gofiber.io/
Flasktodotodohttps://github.com/pallets/flask
Djangotodotodohttps://www.djangoproject.com/

数据库配置

数据库名介绍压测版本依赖库
MySQL关系型数据库8.0sqlalchemy+aiomysql
RedisNoSQL数据库7.2aioredis

三、wrk 工具 http压测

FastAPI

普通http请求压测

依赖安装

pip install fastapi==0.103.1
pip install uvicorn==0.23.2

编写测试路由

from fastapi import FastAPI


app = FastAPI(summary="fastapi性能测试")


@app.get(path="/http/fastapi/test")
async def fastapi_test():
    return {"code": 0, "message": "fastapi_http_test", "data": {}}

Uvicorn 运行,这里是起四个进程运行部署

uvicorn fastapi_test:app --log-level critical --port 8000 --workers 4

wrk压测

开20个线程,建立500个连接,持续请求30s

wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/test

压测结果

~ wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/test

Running 30s test @ http://127.0.0.1:8000/http/fastapi/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.06ms    2.89ms  36.65ms   85.34%
    Req/Sec     3.85k     3.15k   41.59k    70.05%
    
  2298746 requests in 30.11s, 383.64MB read
  Socket errors: connect 267, read 100, write 0, timeout 0
  
Requests/sec:  76357.51
Transfer/sec:     12.74MB

Thread Stats 这里是 20、30个压测线程的平均结果指标

  • 平均延迟(Avg Latency):每个线程的平均响应延迟
  • 标准差(Stdev Latency):每个线程延迟的标准差
  • 最大延迟(Max Latency):每个线程遇到的最大延迟
  • 延迟分布(+/- Stdev Latency):每个线程延迟分布情况
  • 每秒请求数(Req/Sec):每个线程每秒完成的请求数
  • 请求数分布(+/- Stdev Req/Sec):每个线程请求数的分布情况

Socket errors: connect 267, read 100, write 0, timeout 0,是压测过程中socket的错误统计

  • connect:连接错误,表示在压测过程中,总共有 267 次连接异常
  • read:读取错误,表示有 100 次读取数据异常
  • write:写入错误,表示有0次写入异常
  • timeout:超时错误,表示有0次超时

MySQL数据查询请求压测

这里在简单试下数据库查询时候的情况

首先先补充下项目依赖

pip install hui-tools[db-orm, db-redis]==0.2.0

hui-tools是我自己开发的一个工具库,欢迎大家一起来贡献。https://github.com/HuiDBK/py-tools

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { fastapi性能测试 }
# @Date: 2023/09/10 12:24
import uvicorn
from fastapi import FastAPI
from py_tools.connections.db.mysql import SQLAlchemyManager, DBManager

app = FastAPI(summary="fastapi性能测试")


async def init_orm():
    db_client = SQLAlchemyManager(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="123456",
        db_name="house_rental"
    )
    db_client.init_mysql_engine()
    DBManager.init_db_client(db_client)


@app.on_event("startup")
async def startup_event():
    """项目启动时准备环境"""

    await init_orm()
    
@app.get(path="/http/fastapi/mysql/test")
async def fastapi_mysql_query_test():
    sql = "select id, username, role from user_basic where username='hui'"
    ret = await DBManager().run_sql(sql)

    column_names = [desc[0] for desc in ret.cursor.description]
    result_tuple = ret.fetchone()
    user_info = dict(zip(column_names, result_tuple))

    return {"code": 0, "message": "fastapi_http_test", "data": {**user_info}}

wrk压测

wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/mysql/test
~ wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/mysql/test

Running 30s test @ http://127.0.0.1:8000/http/fastapi/mysql/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    38.81ms   19.35ms 226.42ms   76.86%
    Req/Sec   317.65    227.19   848.00     57.21%
    
  180255 requests in 30.09s, 36.95MB read
  Socket errors: connect 267, read 239, write 0, timeout 0
  Non-2xx or 3xx responses: 140
  
Requests/sec:   5989.59
Transfer/sec:      1.23MB

可以发现就加入一个简单的数据库查询,QPS从 76357.51 降到 5989.59 足足降了有10倍多,其实是单机数据库处理不过来太多请求,并发的瓶颈是在数据库,可以尝试加个redis缓存对比MySQL来说并发提升了多少。

Redis缓存查询压测

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { fastapi性能测试 }
# @Date: 2023/09/10 12:24
import json
from datetime import timedelta

import uvicorn
from fastapi import FastAPI
from py_tools.connections.db.mysql import SQLAlchemyManager, DBManager
from py_tools.connections.db.redis_client import RedisManager

app = FastAPI(summary="fastapi性能测试")


async def init_orm():
    db_client = SQLAlchemyManager(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="123456",
        db_name="house_rental"
    )
    db_client.init_mysql_engine()
    DBManager.init_db_client(db_client)


async def init_redis():
    RedisManager.init_redis_client(
        async_client=True,
        host="127.0.0.1",
        port=6379,
        db=0,
    )


@app.on_event("startup")
async def startup_event():
    """项目启动时准备环境"""

    await init_orm()

    await init_redis()


@app.get(path="/http/fastapi/redis/{username}")
async def fastapi_redis_query_test(username: str):
    # 先判断缓存有没有
    user_info = await RedisManager.client.get(name=username)
    if user_info:
        user_info = json.loads(user_info)
        return {"code": 0, "message": "fastapi_redis_test", "data": {**user_info}}

    sql = f"select id, username, role from user_basic where username='{username}'"
    ret = await DBManager().run_sql(sql)

    column_names = [desc[0] for desc in ret.cursor.description]
    result_tuple = ret.fetchone()
    user_info = dict(zip(column_names, result_tuple))

    # 存入redis缓存中, 3min
    await RedisManager.client.set(
        name=user_info.get("username"),
        value=json.dumps(user_info),
        ex=timedelta(minutes=3)
    )

    return {"code": 0, "message": "fastapi_redis_test", "data": {**user_info}}


if __name__ == '__main__':
    uvicorn.run(app)

运行

wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/redis/hui

结果

~ wrk -t20 -d30s -c500 http://127.0.0.1:8000/http/fastapi/redis/hui

Running 30s test @ http://127.0.0.1:8000/http/fastapi/redis/hui
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.60ms    5.59ms 126.63ms   88.41%
    Req/Sec     1.22k     0.91k    3.45k    57.54%
    
  730083 requests in 30.10s, 149.70MB read
  Socket errors: connect 267, read 101, write 0, timeout 0
  
Requests/sec:  24257.09
Transfer/sec:      4.97MB

缓存信息

添加了redis缓存,并发能力也提升了不少,因此在业务开发中一些查多改少的数据可以适当的做缓存。

压测结论

压测类型测试时长线程数连接数请求总数QPS平均延迟最大延迟总流量吞吐量/s
普通请求30s20500229874676357.513.06ms36.65ms383.64MB12.74MB
MySQL查询30s205007300835989.5938.81ms226.42ms36.95MB1.23MB
Redis缓存30s2050073008324257.099.60ms126.63ms149.70MB4.97MB

给 mysql 查询加了个 redis 缓存 qps 提升了 3倍多,对于一些查多改少的数据,根据业务设置适当的缓存可以大大提升系统的吞吐能力。其他框架我就直接上代码测,就不一一赘述了,直接看结果指标。

Sanic

压测方式都是一样的我就不像fastapi一样的一个一个写了,直接写全部压测然后看结果

环境安装

pip install sanic==23.6.0
pip install hui-tools'[db-orm, db-redis]'==0.2.0

编写测试路由

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { sanic性能测试 }
# @Date: 2023/09/10 12:24
import json
from datetime import timedelta

from py_tools.connections.db.mysql import SQLAlchemyManager, DBManager
from py_tools.connections.db.redis_client import RedisManager
from sanic import Sanic
from sanic.response import json as sanic_json

app = Sanic("sanic_test")


async def init_orm():
    db_client = SQLAlchemyManager(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="123456",
        db_name="house_rental"
    )
    db_client.init_mysql_engine()
    DBManager.init_db_client(db_client)


async def init_redis():
    RedisManager.init_redis_client(
        async_client=True,
        host="127.0.0.1",
        port=6379,
        db=0,
    )


@app.listener('before_server_start')
async def server_start_event(app, loop):
    await init_orm()
    await init_redis()


@app.get(uri="/http/sanic/test")
async def fastapi_test(req):
    return sanic_json({"code": 0, "message": "sanic_http_test", "data": {}})


@app.get(uri="/http/sanic/mysql/test")
async def sanic_myql_query_test(req):
    sql = "select id, username, role from user_basic where username='hui'"
    ret = await DBManager().run_sql(sql)

    column_names = [desc[0] for desc in ret.cursor.description]
    result_tuple = ret.fetchone()
    user_info = dict(zip(column_names, result_tuple))

    return sanic_json({"code": 0, "message": "sanic_mysql_test", "data": {**user_info}})


@app.get(uri="/http/sanic/redis/<username>")
async def sanic_redis_query_test(req, username: str):
    # 先判断缓存有没有
    user_info = await RedisManager.client.get(name=username)
    if user_info:
        user_info = json.loads(user_info)
        return sanic_json({"code": 0, "message": "sanic_redis_test", "data": {**user_info}})

    sql = f"select id, username, role from user_basic where username='{username}'"
    ret = await DBManager().run_sql(sql)

    column_names = [desc[0] for desc in ret.cursor.description]
    result_tuple = ret.fetchone()
    user_info = dict(zip(column_names, result_tuple))

    # 存入redis缓存中, 3min
    await RedisManager.client.set(
        name=user_info.get("username"),
        value=json.dumps(user_info),
        ex=timedelta(minutes=3)
    )

    return sanic_json({"code": 0, "message": "sanic_redis_test", "data": {**user_info}})


def main():
    app.run()


if __name__ == '__main__':
    # sanic sanic_test.app -p 8001 -w 4 --access-log=False
    main()

运行

Sanic 内置了一个生产web服务器,可以直接使用

sanic python.sanic_test.app -p 8001 -w 4 --access-log=False

普通http请求压测

同样是起了四个进程看看性能如何

wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/test

压测结果

~ wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/test

Running 30s test @ http://127.0.0.1:8001/http/sanic/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.93ms    2.20ms  61.89ms   91.96%
    Req/Sec     6.10k     3.80k   27.08k    69.37%
    
  3651099 requests in 30.10s, 497.92MB read
  Socket errors: connect 267, read 163, write 0, timeout 0
  
Requests/sec: 121286.47
Transfer/sec:     16.54MB

Sanic 果然性能很强,在python中估计数一数二了。

mysql数据查询请求压测

运行

wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/mysql/test

结果

~ wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/mysql/test

Running 30s test @ http://127.0.0.1:8001/http/sanic/mysql/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    35.22ms   21.75ms 264.37ms   78.52%
    Req/Sec   333.14    230.95     1.05k    68.99%
    
  198925 requests in 30.10s, 34.72MB read
  Socket errors: connect 267, read 146, write 0, timeout 0
  
Requests/sec:   6609.65
Transfer/sec:      1.15MB

Redis缓存查询压测

运行

wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/redis/hui

结果

~ wrk -t20 -d30s -c500 http://127.0.0.1:8001/http/sanic/redis/hui

Running 30s test @ http://127.0.0.1:8001/http/sanic/redis/hui
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.91ms    4.13ms 217.47ms   95.62%
    Req/Sec     1.71k     0.88k    4.28k    68.05%
    
  1022884 requests in 30.09s, 178.52MB read
  Socket errors: connect 267, read 163, write 0, timeout 0
  
Requests/sec:  33997.96
Transfer/sec:      5.93MB

压测结论

压测类型测试时长线程数连接数请求总数QPS平均延迟最大延迟总流量吞吐量/s
普通请求30s205003651099121286.471.93ms61.89ms497.92MB16.54MB
MySQL查询30s205001989256609.6535.22ms264.37ms34.72MB1.15MB
Redis缓存30s20500102288433997.966.91ms217.47ms178.52MB5.93MB

Tornado

环境安装

pip install tornado==6.3.3
pip install gunicorn==21.2.0
pip install hui-tools[db-orm, db-redis]==0.2.0

编写测试路由

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { tornado 性能测试 }
# @Date: 2023/09/20 22:42
import asyncio
from datetime import timedelta
import json
import tornado.web
import tornado.ioloop
from tornado.httpserver import HTTPServer
from py_tools.connections.db.mysql import SQLAlchemyManager, DBManager
from py_tools.connections.db.redis_client import RedisManager

class TornadoBaseHandler(tornado.web.RequestHandler):
    pass

class TornadoTestHandler(TornadoBaseHandler):
    async def get(self):
        self.write({"code": 0, "message": "tornado_http_test", "data": {}})

class TornadoMySQLTestHandler(TornadoBaseHandler):
    async def get(self):
        sql = "select id, username, role from user_basic where username='hui'"
        ret = await DBManager().run_sql(sql)

        column_names = [desc[0] for desc in ret.cursor.description]
        result_tuple = ret.fetchone()
        user_info = dict(zip(column_names, result_tuple))
        self.write({"code": 0, "message": "tornado_mysql_test", "data": {**user_info}})

class TornadoRedisTestHandler(TornadoBaseHandler):
    async def get(self, username):
        user_info = await RedisManager.client.get(name=username)
        if user_info:
            user_info = json.loads(user_info)
            self.write(
                {"code": 0, "message": "tornado_redis_test", "data": {**user_info}}
            )
            return

        sql = f"select id, username, role from user_basic where username='{username}'"
        ret = await DBManager().run_sql(sql)

        column_names = [desc[0] for desc in ret.cursor.description]
        result_tuple = ret.fetchone()
        user_info = dict(zip(column_names, result_tuple))

        # 存入redis缓存中, 3min
        await RedisManager.client.set(
            name=user_info.get("username"),
            value=json.dumps(user_info),
            ex=timedelta(minutes=3),
        )
        self.write({"code": 0, "message": "tornado_redis_test", "data": {**user_info}})

def init_orm():
    db_client = SQLAlchemyManager(
        host="127.0.0.1",
        port=3306,
        user="root",
        password="123456",
        db_name="house_rental",
    )
    db_client.init_mysql_engine()
    DBManager.init_db_client(db_client)

def init_redis():
    RedisManager.init_redis_client(
        async_client=True,
        host="127.0.0.1",
        port=6379,
        db=0,
    )

def init_setup():
    init_orm()
    init_redis()

def make_app():
    init_setup()
    return tornado.web.Application(
        [
            (r"/http/tornado/test", TornadoTestHandler),
            (r"/http/tornado/mysql/test", TornadoMySQLTestHandler),
            (r"/http/tornado/redis/(.*)", TornadoRedisTestHandler),
        ]
    )

app = make_app()

async def main():
    # init_setup()
    # app = make_app()
    server = HTTPServer(app)
    server.bind(8002)
    # server.start(4) # start 4 worker
    # app.listen(8002)
    await asyncio.Event().wait()

if __name__ == "__main__":
    # gunicorn -k tornado -w=4 -b=127.0.0.1:8002 python.tornado_test:app
    asyncio.run(main())

运行tornado服务

gunicorn -k tornado -w=4 -b=127.0.0.1:8002 python.tornado_test:app

wrk 压测

wrk -t20 -d30s -c500 http://127.0.0.1:8002/http/tornado/test

wrk -t20 -d30s -c500 http://127.0.0.1:8002/http/tornado/mysql/test

wrk -t20 -d30s -c500 http://127.0.0.1:8002/http/tornado/redis/hui

结果

~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8002 /http/tornado/test
Running 30s test @ http://127.0.0.1:8002/http/tornado/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.54ms    1.92ms  34.75ms   63.85%
    Req/Sec     1.79k     1.07k    3.83k    56.23%
    
  1068205 requests in 30.07s, 280.15MB read
  Socket errors: connect 267, read 98, write 0, timeout 0
  
Requests/sec:  35525.38
Transfer/sec:      9.32MB

➜  ~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8002 /http/tornado/mysql/test
Running 30s test @ http://127.0.0.1:8002/http/tornado/mysql/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    41.29ms   16.51ms 250.81ms   71.45%
    Req/Sec   283.47    188.81     0.95k    65.31%
    
  169471 requests in 30.09s, 51.88MB read
  Socket errors: connect 267, read 105, write 0, timeout 0
  
Requests/sec:   5631.76
Transfer/sec:      1.72MB

➜  ~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8002 /http/tornado/redis/hui
Running 30s test @ http://127.0.0.1:8002/http/tornado/redis/hui
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    11.69ms    3.83ms 125.75ms   78.27%
    Req/Sec     1.00k   537.85     2.20k    64.34%
    
  599840 requests in 30.07s, 183.63MB read
  Socket errors: connect 267, read 97, write 0, timeout 0
  Non-2xx or 3xx responses: 2
  
Requests/sec:  19947.28
Transfer/sec:      6.11MB

Gin

环境安装

go get "github.com/gin-gonic/gin"
go get "github.com/go-redis/redis"
go get "gorm.io/driver/mysql"
go get "gorm.io/gorm"

代码编写

package main

import (
    "encoding/json"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

var (
    db          *gorm.DB
    redisClient *redis.Client
)

type UserBasic struct {
    Id       int    `json:"id"`
    Username string `json:"username"`
    Role     string `json:"role"`
}

func (UserBasic) TableName() string {
    return "user_basic"
}

func initDB() *gorm.DB {
    var err error
    db, err = gorm.Open(mysql.Open("root:123456@/house_rental"), &gorm.Config{
        // 将LogMode设置为logger.Silent以禁用日志打印
        Logger: logger.Default.LogMode(logger.Silent),
    })
    if err != nil {
        panic("failed to connect database")
    }

    sqlDB, err := db.DB()

    // SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
    sqlDB.SetMaxIdleConns(10)

    // SetMaxOpenConns sets the maximum number of open connections to the database.
    sqlDB.SetMaxOpenConns(30)

    // SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
    sqlDB.SetConnMaxLifetime(time.Hour)

    return db
}

func initRedis() *redis.Client {
    redisClient = redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    return redisClient
}

func jsonTestHandler(c *gin.Context) {
    c.JSON(200, gin.H{
        "code": 0, "message": "gin json", "data": make(map[string]any),
    })
}

func mysqlQueryHandler(c *gin.Context) {

    // 查询语句
    var user UserBasic
    db.First(&user, "username = ?", "hui")
    //fmt.Println(user)

    // 返回响应
    c.JSON(200, gin.H{
        "code":    0,
        "message": "go mysql test",
        "data":    user,
    })

}

func cacheQueryHandler(c *gin.Context) {
    // 从Redis中获取缓存
    username := "hui" // 要查询的用户名
    cachedUser, err := redisClient.Get(username).Result()
    if err == nil {
        // 缓存存在,将缓存结果返回给客户端
        var user UserBasic
        _ = json.Unmarshal([]byte(cachedUser), &user)
        c.JSON(200, gin.H{
            "code":    0,
            "message": "gin redis test",
            "data":    user,
        })
        return
    }

    // 缓存不存在,执行数据库查询
    var user UserBasic
    db.First(&user, "username = ?", username)

    // 将查询结果保存到Redis缓存
    userJSON, _ := json.Marshal(user)
    redisClient.Set(username, userJSON, time.Minute*2)

    // 返回响应
    c.JSON(200, gin.H{
        "code":    0,
        "message": "gin redis test",
        "data":    user,
    })
}

func initDao() {
    initDB()
    initRedis()
}

func main() {
    //r := gin.Default()
    r := gin.New()
    gin.SetMode(gin.ReleaseMode) // 生产模式

    initDao()

    r.GET("/http/gin/test", jsonTestHandler)

    r.GET("/http/gin/mysql/test", mysqlQueryHandler)

    r.GET("/http/gin/redis/test", cacheQueryHandler)

    r.Run("127.0.0.1:8003")
}

wrk 压测

wrk -t20 -d30s -c500 http: //127.0.0.1:8003/http/gin/test

wrk -t20 -d30s -c500 http: //127.0.0.1:8003/http/gin/mysql/test

wrk -t20 -d30s -c500 http: //127.0.0.1:8003/http/gin/redis/test

结果

~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8003 /http/gin/test
Running 30s test @ http://127.0.0.1:8003/http/gin/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.45ms    5.68ms 186.48ms   91.70%
    Req/Sec     6.36k     5.62k   53.15k    83.99%
    
  3787808 requests in 30.10s, 592.42MB read
  Socket errors: connect 267, read 95, write 0, timeout 0
  
Requests/sec: 125855.41
Transfer/sec:     19.68MB

➜  ~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8003 /http/gin/mysql/test
Running 30s test @ http://127.0.0.1:8003/http/gin/mysql/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    40.89ms   83.70ms   1.12s    90.99%
    Req/Sec   522.33    322.88     1.72k    64.84%
    
  308836 requests in 30.10s, 61.26MB read
  Socket errors: connect 267, read 100, write 0, timeout 0
  
Requests/sec:  10260.63
Transfer/sec:      2.04MB
➜  ~~ wrk -t20 -d30s -c500 http:// 127.0.0.1 : 8003 /http/gin/redis/test
Running 30s test @ http://127.0.0.1:8003/http/gin/redis/test
  20 threads and 500 connections
  
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     7.18ms    1.76ms  79.40ms   81.93%
    Req/Sec     1.63k     1.09k    4.34k    62.59%
    
  972272 requests in 30.10s, 193.79MB read
  Socket errors: connect 267, read 104, write 0, timeout 0
  
Requests/sec:  32305.30
Transfer/sec:      6.44MB

四、总结

web框架压测类型测试时长线程数连接数请求总数QPS平均延迟最大延迟总流量吞吐量/s
FastAPI普通请求30s205002298746(229w)76357.51
(76k)
3.06ms36.65ms383.64MB12.74MB
MySQL查询30s20500180255
(18w)
5989.59
(5.9k)
38.81ms226.42ms36.95MB1.23MB
Redis缓存30s20500730083
(73w)
24257.09
(24k)
9.60ms126.63ms149.70MB4.97MB
Sanic普通请求30s205003651099(365w)121286.47(120k)1.93ms61.89ms497.92MB16.54MB
MySQL查询30s20500198925
(19w)
6609.65
(6k)
35.22ms264.37ms34.72MB1.15MB
Redis缓存30s205001022884(100w)33997.96
(33k)
6.91ms217.47ms178.52MB5.93MB
Tornado普通请求30s205001068205(106w)35525.38(35k)6.54ms34.75ms280.15MB9.32MB
MySQL查询30s20500169471
(16w)
5631.76
(5.6k)
41.29ms250.81ms51.88MB1.72MB
Redis缓存30s20500599840
(59w)
19947.28
(19k)
11.69ms125.75ms183.63MB6.11MB
Gin普通请求30s205003787808(378w)125855.41(125k)2.45ms186.48ms592.42MB19.68MB
MySQL查询30s20500308836
(30w)
10260.63
(10k)
40.89ms1.12s61.26MB2.04MB
Redis缓存30s20500972272
(97w)
32305.30(32k)7.18ms79.40ms193.79MB6.44MB

性能

从性能角度来看,各个Web框架的表现如下:

Gin > Sanic > FastAPI > Tornado

Gin:在普通请求方面表现最佳,具有最高的QPS和吞吐量。在MySQL查询中,性能很高,但最大延迟也相对较高。gin承受的并发请求最高有 1w qps,其他python框架都在5-6k qps,但gin的mysql查询请求最大延迟达到了1.12s, 虽然可以接受这么多并发请求,但单机mysql还是处理不过来。

还有非常重要的一点,cpython的多线程由于GIL原因不能充分利用多核CPU,故而都是通过开了四个进程来处理请求,资源开销远远大于go的gin,go底层的GMP的调度策略很强,天然支持并发。

注意:Python使用asyncio语法时切记不要使用同步IO操作不然会堵塞住主线程的事件loop,从而大大降低性能,如果没有异步库支持可以采用线程来处理同步IO。

综合评价

除了性能之外,还有其他因素需要考虑,例如框架的社区活跃性、生态系统、文档质量以及团队熟悉度等。这些因素也应该在选择Web框架时考虑。

最终的选择应该基于具体需求和项目要求。如果性能是最重要的因素之一,那么Sanic和go的一些框架可能是不错的选择。如果您更关注其他方面的因素,可以考虑框架的社区支持和适用性。我个人还是挺喜欢使用FastAPI。

五、测试源代码

https://github.com/HuiDBK/WebFrameworkPressureTest

Github上已经有其他语言的web框架的压测,感兴趣也可以去了解下: https://web-frameworks-benchmark.netlify.app/result

不知道为啥他们测试的python性能好低,可能异步没用对😄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042469.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (101)-- 算法导论9.3 7题

七、用go语言&#xff0c;设计一个 O(n)时间的算法&#xff0c;对于一个给定的包含n个互异元素的集合 S 和一个正整数k≤n&#xff0c;该算法能够确定 S 中最接近中位数的k个元素。 文心一言&#xff1a; 要设计一个 O(n) 时间的算法来找到集合 S 中最接近中位数的 k 个元素&…

诊断27服务介绍

在UDS诊断协议中,有一些服务,比如2E服务写入DID数据,2F服务控制输入输出,它们都会改变ECU控制器的内存数据,所以在请求这类服务时需要慎之又慎。诊断协议设计了一个安全解锁机制,让ECU在接收到某些诊断服务(2E、2F等)前需要处于解锁状态,这就是27服务实现。 Tester发…

【校招VIP】前端操作系统之存储管理-交换

考点介绍&#xff1a; 前端开发的时候&#xff0c;在页面刷新之后&#xff0c;我们的所有数据都会被清空&#xff0c;这时候就要用到本地存储技术了&#xff0c;前端的存储数据的方式有四种&#xff1a;cookie、localStorage、sessionStorage和indexDB。 本期分享的前端操作系…

JavaScript简介引入方式(JavaScript基础语法、JavaScript对象、BOM、DOM、事件监听)

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JavaScript简介&引入方式 简介&#xf…

手把手教你实现:将后端SpringBoot项目部署到华为云服务器上

前言 前提&#xff1a;有一个后端项目&#xff0c;项目能够运行在本地&#xff0c;可以通过本地访问&#xff08;localhost&#xff09; 如果没有可以看这篇&#xff1a;一个基于SpringBoot的后端项目 注册华为云账号 华为云官网 购买云服务器 产品 -> 华为云耀云服务器…

数据结构--插入排序

目录 插入排序 算法实现 算法效率分析 插入排序的优化-折半插入排序 最终的结果&#xff1a;&#xff08;方式&#xff09; 优化-折半查找的代码实现 ​回顾 插入排序 算法实现 算法效率分析 空间复杂度和问题规模无关 插入排序的优化-折半插入排序 之前的元素有序&am…

【2023集创赛】加速科技杯三等奖作品:私密性高精度刷手身份认证系统

本文为2023年第七届全国大学生集成电路创新创业大赛&#xff08;“集创赛”&#xff09;加速科技杯三等奖作品分享&#xff0c;参加极术社区的【有奖征集】分享你的2023集创赛作品&#xff0c;秀出作品风采&#xff0c;分享2023集创赛作品扩大影响力&#xff0c;更有丰富电子礼…

pdf怎么压缩?pdf压缩方法大全

pdf怎么压缩&#xff1f;PDF是一种广受欢迎的文件格式&#xff0c;相信现在有很多用户都在使用。这是因为PDF具有出色的兼容性&#xff0c;适用于包含数据、图片、表格和文字等各种内容&#xff0c;不管是在电脑、手机、平板上&#xff0c;都可以让文件以最规范的方式打开呈现给…

垃圾收集器ParNewCMS与底层三色标记算法

JVM字节码文件class&#xff1a;&#xff08;16进制&#xff09; 前四个字节码CA FE BA BE&#xff0c;固定logo包含lineNumberTable&#xff1a;用于异常的定位默认第一个localVariableTable为this&#xff0c;无构造方法也会存在最大接口数FFFF 》65535包含最大操作数栈深度…

【算法思想】贪心

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

postgresql,在pgAdmin中修改列名称和列的类型

修改列名 alter table "表名称" rename "旧列名" to "新名称";是否要加引号要看情况&#xff0c;不加引号如果报错&#xff0c;就要加上。无引号&#xff1a;那么所有内容将自动为小写&#xff0c;不区分大小写&#xff1b;带引号&#xff1a;所…

网络安全,weblogic漏洞复现

WebLogic是美国Oracle公司出品的一个Java应用服务器&#xff0c;是一个基于JAVAEE架构的中间件&#xff0c;用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用。 2|0弱口令登陆部署shell文件 App Weblogic Path weblogic/weak_password 2|1介绍 用户管理…

Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc

启动项目 npm run dev报错&#xff1a; vue3项目运行报错&#xff0c;试了很多方法都无法解决时&#xff0c;请再查看一下是否node版本是否正确&#xff0c;nodejs版本不能低于 16

安防视频/集中云存储平台EasyCVR(V3.3)部分通道显示离线该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

MySQL系统架构设计

MySQL 一、MySQL整体架构1.1 SQL接口1.2 解析器 Parser1.3 查询优化器 Optimizer1.3.1 逻辑优化1.3.2 物理优化1.3.3 explain 1.4 缓存 Cache1.5 存储引擎 Stroage Management1.6 一条查询SQL的执行流程 二、缓存池&#xff08;Buffer Pool&#xff09;2.1 Buffer Pool 预读机制…

【JVM】运行时数据区之方法区——自问自答

开局从康师傅那里借图几张 线程共享与私有 《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分&#xff0c;但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。” 但对于HotSpotJVM而言&#xff0c;方法区还有一个别名叫做Non-Heap(非堆)&#…

React项目中如何实现一个简单的锚点目录定位

小册 这是我整理的学习资料&#xff0c;非常系统和完善&#xff0c;欢迎一起学习 现代JavaScript高级小册 深入浅出Dart 现代TypeScript高级小册 linwu的算法笔记&#x1f4d2; 前言 锚点目录定位功能在长页面和文档类网站中非常常见,它可以让用户快速定位到页面中的某个…

Pridwen: Universally Hardening SGX Programs via Load-Time Synthesis【ATC`22】

目录 摘要引言性能贡献 背景英特尔SGXSGX的异常SGX侧通道WebAssembly&#xff08;Wasm&#xff09;Wasm中的内存安全PRIDWEN和Wasm 概述场景威胁模型目标适应性证明性可扩展性 架构 标题&#xff1a;Pridwen: Universally Hardening SGX Programs via Load-Time Synthesis 作者…

系统学习Mysql

1.select语句 关键字执行顺序&#xff1a; 1.from 2.where 3.group by 4.select 5.having 6.order by 7.limit SQL 语句执行顺序如下&#xff1a; FROM: 指定要查询的表或子查询&#xff0c;可以包含 JOIN、WHERE 子句过滤等。 WHERE: 对 FROM 子句指定的表或子查询进行限制和…

OpenCV之直线曲线拟合

直线拟合fitLine void fitLine( InputArray points, OutputArray line, int distType,double param, double reps, double aeps ); points:二维点的数组或vector line:输出直线,Vec4f (2d)或Vec6f (3d)的vector distType:距离类型 param:距离参数 reps:径向的精度参数 a…