Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin
一、前言
异步编程在构建高性能 Web 应用中起着关键作用,而 FastAPI、Sanic、Tornado 都声称具有卓越的性能。本文将通过性能压测对这些框架与Go的Gin框架进行全面对比,揭示它们之间的差异。
原文:Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin
二、环境准备
系统环境配置
编程语言
语言 | 版本 | 官网/Github |
---|---|---|
Python | 3.10.12 | https://www.python.org/ |
Go | 1.20.5 | https://go.dev/ |
压测工具
工具 | 介绍 | 官网/Github |
---|---|---|
ab | Apache的压力测试工具,使用简单 | 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.1 | https://fastapi.tiangolo.com/ |
Sanic | Python的异步web服务器框架 | 23.6.0 | https://sanic.dev/zh/ |
Tornado | Python的非阻塞式web框架 | 6.3.3 | https://www.tornadoweb.org/en/stable/ |
Gin | Go语言的web框架 | 1.9.1 | https://gin-gonic.com/ |
Fiber | todo | todo | https://gofiber.io/ |
Flask | todo | todo | https://github.com/pallets/flask |
Django | todo | todo | https://www.djangoproject.com/ |
数据库配置
数据库名 | 介绍 | 压测版本 | 依赖库 |
---|---|---|---|
MySQL | 关系型数据库 | 8.0 | sqlalchemy+aiomysql |
Redis | NoSQL数据库 | 7.2 | aioredis |
三、wrk 工具 http压测
FastAPI
普通http请求压测
依赖安装
pip install fastapi==0.103.1
pip install uvicorn==0.23.2
编写测试路由
from fastapi import FastAPIapp = 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/testRunning 30s test @ http://127.0.0.1:8000/http/fastapi/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 100, write 0, timeout 0Requests/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, DBManagerapp = 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/testRunning 30s test @ http://127.0.0.1:8000/http/fastapi/mysql/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 239, write 0, timeout 0Non-2xx or 3xx responses: 140Requests/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 timedeltaimport uvicorn
from fastapi import FastAPI
from py_tools.connections.db.mysql import SQLAlchemyManager, DBManager
from py_tools.connections.db.redis_client import RedisManagerapp = 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缓存中, 3minawait 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/huiRunning 30s test @ http://127.0.0.1:8000/http/fastapi/redis/hui20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 101, write 0, timeout 0Requests/sec: 24257.09
Transfer/sec: 4.97MB
缓存信息
添加了redis缓存,并发能力也提升了不少,因此在业务开发中一些查多改少的数据可以适当的做缓存。
压测结论
压测类型 | 测试时长 | 线程数 | 连接数 | 请求总数 | QPS | 平均延迟 | 最大延迟 | 总流量 | 吞吐量/s |
---|---|---|---|---|---|---|---|---|---|
普通请求 | 30s | 20 | 500 | 2298746 | 76357.51 | 3.06ms | 36.65ms | 383.64MB | 12.74MB |
MySQL查询 | 30s | 20 | 500 | 730083 | 5989.59 | 38.81ms | 226.42ms | 36.95MB | 1.23MB |
Redis缓存 | 30s | 20 | 500 | 730083 | 24257.09 | 9.60ms | 126.63ms | 149.70MB | 4.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 timedeltafrom 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_jsonapp = 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缓存中, 3minawait 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=Falsemain()
运行
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/testRunning 30s test @ http://127.0.0.1:8001/http/sanic/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 163, write 0, timeout 0Requests/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/testRunning 30s test @ http://127.0.0.1:8001/http/sanic/mysql/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 146, write 0, timeout 0Requests/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/huiRunning 30s test @ http://127.0.0.1:8001/http/sanic/redis/hui20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 163, write 0, timeout 0Requests/sec: 33997.96
Transfer/sec: 5.93MB
压测结论
压测类型 | 测试时长 | 线程数 | 连接数 | 请求总数 | QPS | 平均延迟 | 最大延迟 | 总流量 | 吞吐量/s |
---|---|---|---|---|---|---|---|---|---|
普通请求 | 30s | 20 | 500 | 3651099 | 121286.47 | 1.93ms | 61.89ms | 497.92MB | 16.54MB |
MySQL查询 | 30s | 20 | 500 | 198925 | 6609.65 | 35.22ms | 264.37ms | 34.72MB | 1.15MB |
Redis缓存 | 30s | 20 | 500 | 1022884 | 33997.96 | 6.91ms | 217.47ms | 178.52MB | 5.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 RedisManagerclass TornadoBaseHandler(tornado.web.RequestHandler):passclass 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}})returnsql = 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缓存中, 3minawait 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:appasyncio.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/testwrk -t20 -d30s -c500 http://127.0.0.1:8002/http/tornado/mysql/testwrk -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/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 98, write 0, timeout 0Requests/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/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 105, write 0, timeout 0Requests/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/hui20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 97, write 0, timeout 0Non-2xx or 3xx responses: 2Requests/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 mainimport ("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.DBredisClient *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 errordb, 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 UserBasicdb.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 UserBasicdb.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/testwrk -t20 -d30s -c500 http: //127.0.0.1:8003/http/gin/mysql/testwrk -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/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 95, write 0, timeout 0Requests/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/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 100, write 0, timeout 0Requests/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/test20 threads and 500 connectionsThread Stats Avg Stdev Max +/- StdevLatency 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 readSocket errors: connect 267, read 104, write 0, timeout 0Requests/sec: 32305.30
Transfer/sec: 6.44MB
四、总结
web框架 | 压测类型 | 测试时长 | 线程数 | 连接数 | 请求总数 | QPS | 平均延迟 | 最大延迟 | 总流量 | 吞吐量/s |
---|---|---|---|---|---|---|---|---|---|---|
FastAPI | 普通请求 | 30s | 20 | 500 | 2298746(229w) | 76357.51 (76k) | 3.06ms | 36.65ms | 383.64MB | 12.74MB |
MySQL查询 | 30s | 20 | 500 | 180255 (18w) | 5989.59 (5.9k) | 38.81ms | 226.42ms | 36.95MB | 1.23MB | |
Redis缓存 | 30s | 20 | 500 | 730083 (73w) | 24257.09 (24k) | 9.60ms | 126.63ms | 149.70MB | 4.97MB | |
Sanic | 普通请求 | 30s | 20 | 500 | 3651099(365w) | 121286.47(120k) | 1.93ms | 61.89ms | 497.92MB | 16.54MB |
MySQL查询 | 30s | 20 | 500 | 198925 (19w) | 6609.65 (6k) | 35.22ms | 264.37ms | 34.72MB | 1.15MB | |
Redis缓存 | 30s | 20 | 500 | 1022884(100w) | 33997.96 (33k) | 6.91ms | 217.47ms | 178.52MB | 5.93MB | |
Tornado | 普通请求 | 30s | 20 | 500 | 1068205(106w) | 35525.38(35k) | 6.54ms | 34.75ms | 280.15MB | 9.32MB |
MySQL查询 | 30s | 20 | 500 | 169471 (16w) | 5631.76 (5.6k) | 41.29ms | 250.81ms | 51.88MB | 1.72MB | |
Redis缓存 | 30s | 20 | 500 | 599840 (59w) | 19947.28 (19k) | 11.69ms | 125.75ms | 183.63MB | 6.11MB | |
Gin | 普通请求 | 30s | 20 | 500 | 3787808(378w) | 125855.41(125k) | 2.45ms | 186.48ms | 592.42MB | 19.68MB |
MySQL查询 | 30s | 20 | 500 | 308836 (30w) | 10260.63 (10k) | 40.89ms | 1.12s | 61.26MB | 2.04MB | |
Redis缓存 | 30s | 20 | 500 | 972272 (97w) | 32305.30(32k) | 7.18ms | 79.40ms | 193.79MB | 6.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性能好低,可能异步没用对😄
相关文章:
Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin
一、前言 异步编程在构建高性能 Web 应用中起着关键作用,而 FastAPI、Sanic、Tornado 都声称具有卓越的性能。本文将通过性能压测对这些框架与Go的Gin框架进行全面对比,揭示它们之间的差异。 原文:Python异步框架大战:FastAPI、Sa…...
Docker笔记1
一、Docker介绍 Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。 容器是完全使用沙箱机制&a…...
TensorFlow-Federated简介与安装
1、简介 TensorFlow Federated(TFF)是一个用于机器学习和其他分布式数据计算的开源框架。TFF 的开发旨在促进联邦学习 (FL)的开放研究和实验。联邦学习是一种机器学习方法,其中一个共享的全局模型在许多参与的客户之间…...
【强化学习】基础概念
1. Agent (智能体) 智能体是进行决策和学习的实体,它能感知环境的状态,并基于策略采取动作以影响环境。智能体的目标是通过与环境的交互获得最大化的累积奖励。 2. Environment (环境) 环境是智能体所处的外部系统,它与智能体交互。环境的…...
云原生Kubernetes:K8S集群各组件服务重启
目录 一、理论 1.各组件服务重启命令 一、理论 1.各组件服务重启命令 (1)Master节点Node节点共同服务 systemctl restart etcd systemctl daemon-reload systemctl enable flanneld systemctl restart flanneld (2)Master节…...
闲话Python编程-循环
1. for循环 Python的for语句有点特别,只能对序列和字符串进行处理,序列自然包括list、tuple和range对象。 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # 练习for语句def loop_for():names [Tom, Jack, Black]for name in names:print(name)s ab…...
建筑能源管理(3)——建筑能源监管
为了全面落实科学发展观,提高建筑能源管理水平,进一步降低能源和水资源消耗、合理利用资源,以政府办公建筑和大型公共建筑的运行节能管理为突破口,建立了既有政府办公建筑和大型公共建筑运行节能监管体系,旨在提高政府…...
中国逐年干燥度指数数据集
简介: 中国逐年干燥度指数,空间分辨率为1km,时间为1901-2022,为比值,没有单位。该数据集是基于中国1km逐月潜在蒸散发(PET)和降水量(PRE)采用比值法计算式得到ÿ…...
Azure Arc 概要:功能、管理和应用场景详解,AZ900 考点示例
文章目录 本文大纲一、什么是 Azure Arc二、使用 Azure Arc 可以做什么操作三、使用 Azure Arc 可以管理什么资源3.1 如何使用Azure Arc与服务器? 四、Azure Arc 支持的主要场景五、在 AZ900 中的考点示例5.1 示例题 15.2 示例题 2 本文大纲 本文思维导图概述的主要内容&…...
JavaScript Web APIs第一天笔记
复习: splice() 方法用于添加或删除数组中的元素。 **注意:**这种方法会改变原始数组。 删除数组: splice(起始位置, 删除的个数) 比如:1 let arr [red, green, blue] arr.splice(1,1) // 删除green元素 consol…...
十六.镜头知识之工业镜头的质量判断因素
十六.镜头知识之工业镜头的质量判断因素 文章目录 十六.镜头知识之工业镜头的质量判断因素1.分辨率(Resolution)2.明锐度(Acutance)3.景深(DOF):4. 最大相对孔径与光圈系数5.工业镜头各参数间的相互影响关系5.1.焦距大小的影响情况5.2.光圈大小的影响情况5.3.像场中…...
网络协议--概述
1.2 分层 网络协议通常分不同层次进行开发,每一层分别负责不同的通信功能。一个协议族,比如TCP/IP,是一组不同层次上的多个协议的组合。 TCP/IP通常被认为是一个四层协议系统,如图1-1所示。每一层负责不同的功能: 1.链…...
aarch64 平台 musl gcc 工具链手动编译方法
目标 手动编译一个 aarch64 平台的 musl gcc 工具链 musl libc 与 glibc、uclibc 等,都是 标准C 库, musl libc 是基于系统调用之上的 标准C 库,也就是用户态的 标准C 库。 musl libc 轻量、开源、免费,是一些 操作系统的选择,当前 Lite-OS 与 RT-Smart 等均采用自制的 mu…...
计算机图像处理-高斯滤波
高斯滤波 高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到…...
lv5 嵌入式开发-9 信号机制(上)
目录 1 信号机制 2 信号的产生 3 常用信号 4 相关命令 4.1 信号相关命令 kill / killall 4.2 信号发送 – kill / raise 4.3 定时器函数相关函数 – alarm /ualarm/ pause 4.4 信号捕捉:设置信号响应方式 – signal /sigaction,闹钟实现 4.5 子…...
460. LFU 缓存
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。 实现 LFUCache 类: LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1…...
YOLOV8 C++ opecv_dnn模块部署
废话不多说:opencv>4.7.0 opencv编译不做解释,需要的话翻看别的博主的编译教程 代码饱含V5,V7,V8部署内容 头文件yoloV8.h #pragma once #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using namespace cv; using name…...
STM32 DMA从存储器发送数据到串口
1.任务描述 (1)ds18b20测量环境温度存储到存储器(数组)中。 (2)开启DMA将数组中的内容,通过DMA发送到串口 存在问题,ds18b20读到的数据是正常的,但是串口只是发送其低…...
Flask连接数据库返回json数据
常用方法: json.dumps(字典) 将python的字典转换为json字符串json.loads(字符串) 将字符串转换为python中的字典方法一:将python字典转化为json from flask import Flask import jsonapp Flask(__name__)app.route("/index") def index():# 返回json数据的方法…...
Openresty通过Lua+Redis 实现动态封禁IP
求背景 为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的 IP 黑名单。对于黑名单之内的 IP ,拒绝提供服务。并且可以设置失效 1.安装Openresty(编译安装) wget https://openresty.org/download/openresty-1.…...
碎片笔记|AIGC核心技术综述
前言:AIGC全称为AI-Generated Content,直译为人工智能内容生成。即采用人工智能技术来自动生产内容。AIGC在2022年的爆发,主要是得益于深度学习模型方面的技术创新。不断涌现的生成算法、预训练模型以及多模态等技术的融合引发了AIGC的技术变…...
28385-2012 印刷机械 锁线机 学习笔记
声明 本文是学习GB-T 28385-2012 印刷机械 锁线机. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了锁线机的型式、基本参数、要求、试验方法、检验规则、标志、包装、运输与贮存。 本标准适用于用线将书帖装订成书芯的锁线机。 …...
【大规模 MIMO 检测】基于ADMM的大型MU-MIMO无穷大范数检测研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
MySQL数据库记录的删除操作与特殊字符
在数据库管理中,除了添加和修改记录之外,删除操作也是一个重要的方面。同时特殊字符序列的处理也是必不可少的一步。 本文将深入探讨如何在MySQL数据库中进行表记录的删除操作,以及如何处理特殊字符序列。将使用《三国志》游戏数据作为示例来进行解释。 文章目录 表记录的…...
什么是TypeScript
TypeScript是一个开源的编程语言,它是JavaScript的超集。它允许开发人员编写更具可靠性和高效性的代码,同时提供了强类型支持、类、接口、模块等新的特性。TypeScript的代码可以编译成纯JavaScript代码,可以在任何支持JavaScript的平台上运行…...
[docker]笔记-网络故障处理
1、同事在虚拟机上部署docker,发现电脑无法登录虚拟机了。首先ping测是通的,从我电脑继续进行登录测试发现没问题,初步判断是她电脑网络和虚拟机网络之间连接出错。 2、进行虚拟机登录查看,首先使用route -n命令查看路由…...
牛客网_HJ1_字符串最后一个单词的长度
HJ1_字符串最后一个单词的长度 原题思路代码运行截图收获 原题 字符串最后一个单词的长度 思路 从最后一个字符开始遍历,遇到第一个空格时的长度即为最后一个单词的长度 代码 #include <iostream> #include <string> using namespace std;int main…...
智算创新,美格智能助力智慧支付加速发展
9月21日,以“智算引领创新未来”为主题的紫光展锐2023泛物联网终端生态论坛在深圳举行。作为紫光展锐重要战略合作伙伴,美格智能标准模组产品线总经理郭强华、高级产品总监刘伟鹏受邀出席论坛。美格智能基于紫光展锐5G、4G、智能SoC、Cat.1 bis等芯片平台…...
常用SQL语法总结
1.库操作 1.1.创建数据库 CREATE DATABASE 语句用来创建一个新的数据库。 语法:CREATE DATABASE DatabaseName; DatabaseName 为数据库名字,它的名字必须是唯一的,不能和其它数据库重名。 1.2.删除数据库 DROP DATABASE语句用来删除已经…...
Promise击鼓传花的游戏
Promise击鼓传花的游戏 Promise系列导航前言一、学习Promise的原因二、揭开击鼓传花游戏的面纱补充小知识 Promise系列导航 1.Promise本质击鼓传花的游戏 2.Promise四式击鼓 3.Promise击鼓传花 4.Promise花落谁家知多少 前言 👨💻👨&…...
怎么网站建设多少钱/百度网站怎么提升排名
今天在调试项目的时候使用SqlServer遇到了上面的错误: 我像管理员表中插入数据: inert into Admin vlues(1,liu,123) 在结果窗口中显示了:仅当使用了列的列表,并且 IDENTITY_INSERT 为 ON 时,才能在表 Admin 中为标识列…...
大连普兰店网站建设/企业网站建站模板
wiki 地址:http://wiki.apache.org/solr/FrontPage, 里面有各个参数详细的介绍。 一.基本查询 q 查询的关键字,此参数最为重要,例如,qid:1,默认为q*:*, fl 指定返回哪些字段,用逗号…...
公司网站建设合同模板/网推
使用Flask以Web方式部署TensorFlow模型 flyfish 目的:可以通过浏览器访问服务,上传一个图片,然后服务器能够返回已经检测完成的图片或者json字符串 浏览器可以看到检测完成的图片 源码地址: https://github.com/shaoshengsong/…...
做软件界面的网站/怎样做好网络推广呀
虚拟环境为什么需要虚拟环境:到目前位置,我们所有的第三方包安装都是直接通过pip install xx的方式进行安装的,这样安装会将那个包安装到你的系统级的Python环境中。但是这样有一个问题,就是如果你现在用Django 1.10.x写了个网站&…...
宁波最靠谱的网站建设/seo关键词优化经验技巧
本文发布于我的个人网站:https://wintc.top/article/58,转载请注明。 多行文本超过指定行数隐藏超出部分并显示“...查看全部”是一个常遇到的需求,网上也有人实现过类似的功能,不过还是想自己写写看,于是就写了一个Vu…...
抚宁网站建设/推广普通话宣传语100字
已结贴√问题点数:10 回复次数:21关于田忌赛马问题.。。帮忙看下。。谢谢了。。题目描述Here is a famous story in Chinese history."That was about 2300 years ago. General Tian Ji was a high official in the country Qi. He likes to play h…...