Sanic Flask Tornado 性能测试

这其实是公司安排的一个任务,对 Sanic 框架进行简单的研究,并对比一下目前的性能。例子中我们使用 wrk 进行简单的压力测试,对简单的 HTTP 请求进行分析。

Sanic

安装 Sanic

pip install sanic

编写简单的 Sanic 异步 view

from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text

app = Sanic('sanic_demo')


class SimpleAsyncView(HTTPMethodView):

    async def get(self, request):
        return text('I am async get method')

    async def post(self, request):
        return text('I am async post method')

    async def put(self, request):
        return text('I am async put method')


app.add_route(SimpleAsyncView.as_view(), '/async')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8673)

测试

介绍

这里的 web 压力测试使用 wrk, 具体使用方法可参考 wrk 。另外因为每个 web 框架都自带 Server(Sanci, Tornado 二者本身可以作为 Web Server,Flask 自带 WSGI 服务),为了保证每个 web 框架的统一性,这里都统一使用 Gunicorn 部署服务,并且 worker 数量统一为16。另外也保证环境的统一,我们使用同一个镜像来进行测试。

gunicorn 配置大致如下:

bind = '0.0.0.0:8673'
workers = 16
backlog = 2048
#worker_class = "gevent"
debug = False
proc_name = 'gunicorn.pid'
pidfile = 'gunicorn.pid'
logfile = 'debug.log'
loglevel='debug'
镜像
docker pull python-3.6
发布脚本
#!/bin/sh

# 启动类型,支持uwsgi 或 gunicorn
start_type="gunicorn"

# 容器名称
container_name="sanic_demo"

# 运行端口
run_port="8673"

# 容器内部运行端口
container_run_port="8673"

#宿主机代码地址
code_path="/Users/rex/Documents/Projects/Python/sanic_demo/sanic_part/"

# 映射到容器内部的代码地址
container_code_path="/app/docker/www/sanic_demo/"

# 镜像名称
image="sanic_demo:3.6"



kill_container(){
    # 停止并且删除待发布正在运行的容器
    echo "**INFO**: " 停止旧服务: $container_name
    docker rm -f $container_name
    echo "**INFO**: " 停止 $container_name 服务成功!: 
}

pull_new_image(){
    # 拉取本次最新的镜像
    echo "**INFO**: " 拉取所需镜像: $image
    docker pull $image
    echo "**INFO**: " 拉取镜像 $image 成功! 
}


publish(){
    # 启动服务
    echo "**INFO**: " 启动服务, 运行类型为 $start_type:

    if [ "$start_type" == "uwsgi" ]
        then
            docker run -d -p $run_port:$container_run_port -v $code_path:$container_code_path -e SERVICE_NAME=$container_name -e SERVICE_TAGS=$container_name -w $container_code_path --name=$container_name $image nohup uwsgi --ini uwsgi.ini --wsgi-disable-file-wrapper
    elif [ "$start_type" == "gunicorn" ]
        then
            docker run -d -p $run_port:$container_run_port -v $code_path:$container_code_path -e SERVICE_NAME=$container_name -e SERVICE_TAGS=$container_name -w $container_code_path --name=$container_name $image nohup gunicorn -c gunicorn.conf -t 60 server:app
    else
        echo "**INFO**: " 目前只支持 uwsgi 或 gunicorn 发布 Python 项目!
    fi
}

get_publish_status(){
    # 获取本次发布状态:
    grep_container_name=`docker ps --format "{{.Names}}" | grep $container_name` 
    if [ "$grep_container_name" == "$container_name" ]
        then
            echo "**INFO**: " $container_name 发布成功!!!
    else
        echo "**INFO**: " $container_name 发布失败!!!
    fi
}

get_all_container_status(){
    # 查看所有容器状态
    docker ps
}


main(){
    kill_container
    pull_new_image
    publish
    get_publish_status
    # get_all_container_status
}

main
启动

输出日志如下:

192:docker rex$ sh publish.sh 
**INFO**:  停止旧服务: sanic_demo
sanic_demo
**INFO**:  停止 sanic_demo 服务成功!:
**INFO**:  拉取所需镜像: sanic_demo:3.6
Error response from daemon: pull access denied for sanic_demo, repository does not exist or may require 'docker login'
**INFO**:  拉取镜像 sanic_demo:3.6 成功!
**INFO**:  启动服务, 运行类型为 gunicorn:
2a2ce354f08b4d62d86f600f19f691ff8062798d00308cd0e7abb683010b77ae
**INFO**:  sanic_demo 发布成功!!!

查看容器状态:

192:~ rex$ docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2a2ce354f08b        sanic_demo:3.6      "nohup gunicorn -c g…"   5 seconds ago       Up 3 seconds        0.0.0.0:8673->8673/tcp   sanic_demo

访问 http://127.0.0.1:8673/async 却得到一个 Internal Server Error 后来查询官方文档,才知道若 sanic 使用 gunicorn 启动,那么需要加上 –worker-class sanic.worker.GunicornWorker 参数。加上后正常启动,访问页面,就能看到返回的内容了。

压力测试

安装 wrk

brew install wrk

并发测试

wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/async

这条命令的意思是用4个线程来模拟 1000 个并发连接,整个测试持续 30 秒,连接超时 30 秒,打印出请求的延迟统计信息。需要注意的是 wrk 使用异步非阻塞的 io,并不是用线程去模拟并发连接,因此不需要设置很多的线程,一般根据 CPU 的核心数量设置即可。另外 -c 参数设置的值必须大于 -t 参数的值。

192:~ rex$ wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/async
Running 30s test @ http://127.0.0.1:8673/async
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   190.17ms  137.14ms   1.31s    78.88%
    Req/Sec   311.78     97.57   700.00     69.82%
  Latency Distribution
     50%  163.99ms
     75%  248.01ms
     90%  336.62ms
     99%  646.97ms
  37275 requests in 30.05s, 4.98MB read
  Socket errors: connect 751, read 361, write 1, timeout 0
Requests/sec:   1240.28
Transfer/sec:    169.57KB

主要关注的几个数据是

  • Socket errors socket 错误的数量
  • Requests/sec 每秒请求数量,也就是并发能力
  • Latency 延迟情况及其分布

由此可以看到使用 gunicorn 部署 Requests/sec 为 1240.28

若不使用 gunicorn 部署,而是直接启动 server.py, 测试结果如下:

192:~ rex$ wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/async
Running 30s test @ http://127.0.0.1:8673/async
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    25.00ms    9.13ms  43.74ms   61.46%
    Req/Sec     2.41k   289.07     4.85k    74.20%
  Latency Distribution
     50%   19.40ms
     75%   34.88ms
     90%   36.38ms
     99%   39.80ms
  288839 requests in 30.10s, 38.56MB read
  Socket errors: connect 751, read 222, write 0, timeout 0
Requests/sec:   9595.17
Transfer/sec:      1.28MB

由此可以看到不使用 gunicorn 部署 Requests/sec 为 9595.17 刚开始我也不相信,所以这里做了两遍的测试,结果出来结果都是一样。特意去官网上查了一下,sanic 官方说明了两种部署方式,一种是直接运行,另一种使用时 gunicorn,但是并没有说明两种方式在性能上的差异性。

接下来我们可以对 Flask 进行测试

Flask

安装 Flask

pip install flask

编写简单的 Flask view

from flask import Flask

app = Flask(__name__)


@app.route("/flask")
def hello():
    return "I am flask get method"


if __name__ == "__main__":
    app.run(host="127.0.0.1", port="8673")

有了 Sanic 的部署和设置,Flask 部署起来很轻松。只需将 publish.sh 中的宿主机代码地址改为 /Users/rex/Documents/Projects/Python/sanic_demo/sanic_part/ 即可。同时去除 –worker-class sanic.worker.GunicornWorker 参数。容器启动之后就能通过浏览器访问到页面了。

测试

其余准备工作和测试 Sanic 差不对,这里就直接进行压力测试了。

压力测试

并发测试

wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/flask
192:~ rex$ wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/flask
Running 30s test @ http://127.0.0.1:8673/flask
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   398.12ms  833.40ms  20.42s    91.13%
    Req/Sec   156.88     38.44   306.00     73.70%
  Latency Distribution
     50%  225.86ms
     75%  242.44ms
     90%  302.70ms
     99%    3.90s 
  16589 requests in 30.10s, 2.86MB read
  Socket errors: connect 751, read 446, write 2, timeout 0
Requests/sec:    551.04
Transfer/sec:     97.40KB

由此可以看到使用 gunicorn 部署 Requests/sec 为 551.04 比sanci 低了一倍多。

若不使用 gunicorn 部署,而是直接启动 server.py, 测试结果如下:

192:~ rex$ wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/flask
Running 30s test @ http://127.0.0.1:8673/flask
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    63.42ms   10.12ms  93.95ms   92.39%
    Req/Sec   470.13    147.27   777.00     77.49%
  Latency Distribution
     50%   65.15ms
     75%   65.61ms
     90%   66.20ms
     99%   79.72ms
  16574 requests in 30.05s, 2.77MB read
  Socket errors: connect 763, read 833, write 9, timeout 0
Requests/sec:    551.48
Transfer/sec:     94.25KB

由此可以看到不使用 gunicorn 部署 Requests/sec 为 551.48 二者方式却别不大。

Tronado

安装 Tornado

pip install tornado
编写简单的 Tornado view
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("I am tornado get method")


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


if __name__ == "__main__":
    app = make_app()
    app.listen(8673)
    tornado.ioloop.IOLoop.current().start()

因为 Tornado 不使用 gunicorn 启动,Tornado 本身就是一个 HTTP Server,所以我们直接运行 server.py 进行测试就行。

测试

其余准备工作和测试 Sanic 差不对,这里就直接进行压力测试了。

压力测试

并发测试

wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/tornado
192:~ rex$ wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/tornado
Running 30s test @ http://127.0.0.1:8673/tornado
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    78.63ms    4.31ms  88.15ms   91.13%
    Req/Sec   785.93    179.12     1.19k    60.40%
  Latency Distribution
     50%   79.25ms
     75%   79.93ms
     90%   80.71ms
     99%   85.43ms
  93959 requests in 30.07s, 19.53MB read
  Socket errors: connect 751, read 181, write 0, timeout 0
Requests/sec:   3124.51
Transfer/sec:    665.18KB

由此可以看到不使用 gunicorn 部署 Requests/sec 为 3124.51 比 Flask 高很多,比 Sanic 低很多。


 上一篇
Flask Sanic 数据库操作对比 Flask Sanic 数据库操作对比
Sanicsanic 既然是使用了 io 异步,那么我们就使用 aiomysql 和 sqlalchemy 来对数据进行处理 获取 enginefrom aiomysql.sa import create_engine DB_SETT
2019-04-24
下一篇 
Sanic 维护及使用情况分析 Sanic 维护及使用情况分析
介绍Sanic 是一个 Python Web 服务器和 Web 框架,可以快速进行开发。它允许使用 Python 3.5 中添加的 async / await 语法,这使您的代码无阻塞且快速。 Sanic 项目由社区维护 ,目标是提供一种易
2019-04-24
  目录