这其实是公司安排的一个任务,对 Sanic 框架进行简单的研究,并对比一下目前的性能。例子中我们使用 wrk 进行简单的压力测试,对简单的 HTTP 请求进行分析。
Sanic
安装 Sanic
编写简单的 Sanic 异步 view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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 配置大致如下:
1 2 3 4 5 6 7 8 9
| 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'
|
镜像
发布脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #!/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
|
启动
输出日志如下:
1 2 3 4 5 6 7 8 9 10
| 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 发布成功!!!
|
查看容器状态:
1 2 3
| 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
并发测试
1
| wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/async
|
这条命令的意思是用4个线程来模拟 1000 个并发连接,整个测试持续 30 秒,连接超时 30 秒,打印出请求的延迟统计信息。需要注意的是 wrk 使用异步非阻塞的 io,并不是用线程去模拟并发连接,因此不需要设置很多的线程,一般根据 CPU 的核心数量设置即可。另外 -c 参数设置的值必须大于 -t 参数的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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, 测试结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
编写简单的 Flask view
1 2 3 4 5 6 7 8 9 10 11 12
| 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 差不对,这里就直接进行压力测试了。
压力测试
并发测试
1
| wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/flask
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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, 测试结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
编写简单的 Tornado view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 差不对,这里就直接进行压力测试了。
压力测试
并发测试
1
| wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8673/tornado
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 低很多。