最近准备学习 Go 的 Web 框架 Gin,然后之前在学习 Python 的框架 FastAPI 的时候经常会听说 FastAPI 的性能可以跟 Go 的框架比一比,因此,为了验证一下这个说法,我把这些框架都拿出来进行了一个简单的压力测试,看看各自性能怎么样。

压测条件

服务器配置

环境 CPU 核数 内存 系统
服务器 2 2G CentOS 7.8
本地 8 16G macOS 14.1.2

启动服务的方式

所有的服务都需要提供相同的接口,实现一个简单的 GET 请求,并且请求可以读取请求参数中的值并返回。

Flask 服务

Flask 的代码 flask-server.py:

from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/items/<int:item_id>')
def get_item(item_id):
    q = request.args.get('q', '')
    return jsonify({
        "item_id": item_id,
        "q": q
    })


if __name__ == '__main__':
    app.run(debug=False)

启动方式(需要启动2个进程,充分利用 CPU):

gunicorn flask-server:app -b 0.0.0.0:8012 -w 2

FastAPI 服务

FastAPI 服务代码 fastapi-server.py:

from typing import Union
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

启动方式(需要启动2个进程,充分利用 CPU):

gunicorn fastapi-server:app -b 0.0.0.0:8011 -k uvicorn.workers.UvicornWorker -w 2

Tornado 服务

Tornado 服务 tornado-server.py:

import sys
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self, item_id):
        q = self.get_argument('q', '')
        self.write({
            "item_id": item_id,
            "q": q
        })


def make_app():
    return tornado.web.Application([
        (r"/items/(\d+)", MainHandler),
    ])


if __name__ == "__main__":
    # python tornado-server.py 8013 2
    port, process_num = sys.argv[1:3]
    app = make_app()
    # 启动多个进程
    server = tornado.httpserver.HTTPServer(app)
    server.bind(int(port))
    server.start(num_processes=int(process_num))
    tornado.ioloop.IOLoop.current().start()

启动方式(需要启动2个进程,充分利用 CPU):

python tornado-server.py 8013 2

Sanic 服务

Sanic 服务 sanic-server.py:

import sys

from sanic import Sanic
from sanic.response import json

app = Sanic("server")


@app.route('/items/<item_id:int>')
async def get_item(request, item_id):
    q = request.args.get('q', '')
    return json({
        "item_id": item_id,
        "q": q
    })


if __name__ == "__main__":
    # python sanic-server.py 8014 2
    port, process_num = sys.argv[1:3]
    app.run(host="0.0.0.0", port=int(port), workers=int(process_num))

启动方式(需要启动2个进程,充分利用 CPU):

python sanic-server.py 8014 2

Gin 服务

Gin 服务代码:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    // 创建一个默认的 Gin 引擎
    router := gin.Default()

    // 定义路由处理程序
    router.GET("/items/:id", func(c *gin.Context) {
        // 从路由参数中获取item_id
        itemID := c.Param("id")

        // 从查询参数中获取q
        q := c.Query("q")

        // 返回JSON响应
        c.JSON(http.StatusOK, gin.H{
            "item_id": itemID,
            "q":       q,
        })
    })

    router.Run(":8022")
}

Gin 服务启动方式:编译成二进制文件直接运行即可。

测试方案

压测工具为 jmeter,压测设置500线程,Ramp-Up设置为0,循环次数设置为永久,每个接口持续压测两分钟,并且记录每个服务的服务器性能状态。

jmeter 聚合报告

jmeter-500

Flask 服务器压力

flask-500

FastAPI 服务器压力

fastapi-500

Tornado 服务器压力

tornado-500

Sanic 服务器压力

sanic-500

Gin 服务器压力

gin-500

这里可以看到,Gin 对于服务器的压力很低,CPU 都还没有利用完,还有很大的利用空间。

结论

  1. 从这个聚合报告可以看出,flask 的性能是最差的,吞吐量低就算了,异常率也高,而且响应时间也是最大,跟其他几个框架没法比。
  2. 号称可以跟 go 的框架刚一下的 fastapi 也没有达到预期效果,性能还不如 tornado,而 sanic 倒是真的可以跟 gin 媲美。
  3. 除了gin 以外,其他框架已经把 CPU 消耗完了,所以数据已经到了瓶颈,而 gin 还有很大的资源空间空闲,这份数据并不是 gin 的真实数据。
  4. 从报告数据可以看到 sanic 和 gin 的各项数据非常接近,所以在 Python 里面如果真的要追求性能的话,应该选择 sanic 框架。