PythonPython学习web框架开发

Flask服务器部署:使用Docker+Gunicorn+gev

2021-01-17  本文已影响0人  xiaogp

摘要:FlaskgunicornnginxdockergeventWSGI

整理一下Flask的部署相关代码,以及各个组件的使用和一些原理


Flask部署.png

为什么需要Gunicorn

在开发时flask的run命令可以直接启动提供web服务,实际上是由Werkzeug提供的WSGI服务器,相当于Flask内置了一个WSGI服务器,只适合在开发调试的时候使用;在生产环境中需要一个更强健,性能更高的WSGI服务器,WSGI服务器也被称为独立的WSGI容器,主流的WSGI容器有GunicornuWSGI


什么是WSGI服务器

Web Server Gateway Interface 的缩写,即 Web 服务器网关接口。Python web开发中,服务端程序分为两个部分

服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时就需要一个标准,让服务器程序和应用程序都支持这个标准,这样二者就能很好的配合了,这个标准就是WSGI,是python web开发的标准,类似于协议,是web服务器程序与应用程序解耦的规范,这样服务器程序和应用程序就可以随意组合实现自己的web应用。它是服务器程序和应用程序的一个约定,规定了各自使用的接口和功能,以便二和互相配合。


为什么需要Nginx

Nginx是Web服务器,流行的Web服务器还有Apache,Tengine等,Web服务器主要负责和客户端交换数据处理请求和响应,像Gunicorn这类WSGI服务器内置了Web服务器,但是内置的Web服务器不够强健,更流行的部署方式是采用一个常规的Web服务器运行在前端,为WSGI服务器提供反向代理。在Gunicorn之后再加一层Nginx有以下好处:


使用Gunicorn作为容器启动Flask

安装gunicorn,使用pip下载安装

pip install gunicorn

如果以gevent模式运行gunicorn,需要安装gevent,版本20.9.0以上

pip install gevent==20.9.0

编写gunicorn配置文件

root@ubuntu:~/myproject/pira_score_web_application# cat gun.conf.py 
# gun.conf
bind = '0.0.0.0:5000'
workers = 5 
backlog = 2048
worker_class = "gevent"
debug = False
proc_name = 'gunicorn.proc'
pidfile = './gunicorn.pid'
#accesslog = '/var/log/gunicorn/pira_score_web/detail.log'
#access_log_format = '%(h)s %(l)s %(u)s %(t)s'
#loglevel = 'info'

Gunicorn配置详解

编写gunicorn启动脚本

root@ubuntu:~/myproject/pira_score_web_application# cat run.sh 
#! /bin/bash
cd /home/gp/myproject/pira_score_web_application
gunicorn -c gun.conf.py -D app:app

查看后台gunicorn进程pid

root@ubuntu:~/myproject/pira_score_web_application# cat gunicorn.pid 
30322
root@ubuntu~/myproject/pira_score_web_application# ps -ef|grep `cat gunicorn.pid`
root     30322  1104  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     30325 30322  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     30326 30322  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     30327 30322  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     30328 30322  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     30329 30322  0 10:47 ?        00:00:00 /opt/anaconda3/bin/python /opt/anaconda3/bin/gunicorn -c gun.conf.py -D app:app
root     31611 21931  0 11:01 pts/3    00:00:00 grep --color=auto 30322

可见后台一共有一个父进程和5个子进程,和5个workers对应,可以使用父进程pid直接关闭gunicorn的所有进程

root@ubuntu:~/myproject/pira_score_web_application# kill -9 `cat gunicorn.pid`

gevent和协程

gevent:是一个基于协程的python网络库,在遇到IO阻塞时,程序会自动进行切换,可以让开发者用同步的方式写异步IO代码。
协程:是单线程下的并发,又称微线程,是一种并发编程模式,协程并发的本质是切换+保存状态

测试使用gevent运行两个阻塞IO任务,分别阻塞3秒,4秒;gevent使用spawn定义一个协程任务,接受任务名入参,是一个异步任务,使用join等待协程执行完毕退出,也可以调用joinall方法传入一个任务列表。

from gevent import monkey
import gevent
import time

monkey.patch_all()  # 合并成一行,专门用于打标记


def eat(name):
    print("%s is eating 1" % name)
    # gevent.sleep(3)  # gevent.sleep()和 time.sleep()效果一样
    time.sleep(3)
    print("%s is eating 2" % name)


def play(name):
    print("%s play 1" % name)
    # gevent.sleep(4)
    time.sleep(4)
    print("%s play 2" % name)


start = time.time()

g1 = gevent.spawn(eat, "aaa")  # 提交任务  #  spawn()第一个参数写任务名,后面直接参数就行(位置参数或关键字参数都可以)
g2 = gevent.spawn(play, "bbb")  # gevent.spawn()是异步提交任务

g1.join()
g2.join()  # 保证上面提交的两个任务都执行完毕了  # 协程是单线程的,需要再线程结束前等待g1和g2,要不然g1和g2还没起来,“主线程”就结束了,此时g1和g2也就不会再执行了
# g1.join()和g2.join()可以合并成:
# gevent.joinall([g1,g2])

stop = time.time()
print(stop - start)  # 4.002309322357178

执行结果为最大阻塞时间4秒,如果是串行执行为7秒
gevent执行过程分析
(1)先启任务1:g1先起来,执行了第一个print,然后遇到了IO阻塞(gevent.sleep(3)),然后立马就切到了 g2 提交的 play任务
(2)任务1阻塞切换任务2:执行 play中的第一个print,接着又遇到了IO阻塞(gevent.sleep(4)),然后就又切到了 g1的eat任务
(3)来回切换:此时g1的eat还是处于阻塞状态,接着就在两个任务之间来回切
(4)分别等待协程执行就绪:直到 g1的eat 又处于就绪状态,打印 eat的第2个print;执行完 eat之后,g2的play还处于阻塞状态,然后等其阻塞结束后执行 play的第2个print;
gevent监测了多个任务之间的IO阻塞,遇到IO阻塞就切走


Gunicorn前后压测对比

使用压测工具siege模拟并发,先修改根目录下.siege目录下的配置文件siege.conf设置最大并发量为1000,先对比400并发下,循环测试2次,请求之间无间隔的统计结果
flask自带的WSGI服务器

root@ubuntu:~/.siege# siege -c 400 -r 2 -b "http://192.168.67.72:5000/北京优胜辉煌教育科技有限公司.html"
** SIEGE 4.0.4
** Preparing 400 concurrent users for battle.
The server is now under siege...
Transactions:               4800 hits
Availability:             100.00 %
Elapsed time:              30.62 secs
Data transferred:         744.21 MB
Response time:              1.77 secs
Transaction rate:         156.76 trans/sec
Throughput:            24.30 MB/sec
Concurrency:              276.91
Successful transactions:        4800
Failed transactions:               0
Longest transaction:           29.01
Shortest transaction:           0.04

Gunicorn作为WSGI服务器
Gunicorn开启5个进程启动Flask,工作模式使用gevent

root@ubuntu:~/.siege# siege -c 400 -r 2 -b "http://192.168.61.100:5000/北京优胜辉煌教育科技有限公司.html"
** SIEGE 4.0.4
** Preparing 400 concurrent users for battle.
The server is now under siege...
Transactions:               4800 hits
Availability:             100.00 %
Elapsed time:              13.72 secs
Data transferred:         744.21 MB
Response time:              0.83 secs
Transaction rate:         349.85 trans/sec
Throughput:            54.24 MB/sec
Concurrency:              291.27
Successful transactions:        4800
Failed transactions:               0
Longest transaction:           12.04
Shortest transaction:           0.00

测试结果Gunicorn的处理速度和性能是Flask的2倍多,并且在模拟并发达到500及以上时,请求Flask服务报错,而Gunicorn运行良好


Docker容器化部署服务器

在项目目录下创建requirements.txt,指定项目需要的Python包和版本

# requirements.txt
flask==1.1.1
Flask-SQLAlchemy==2.4.4
gevent==20.9.0
gunicorn==20.0.4
numpy==1.19.5
pymysql==1.0.0
SQLAlchemy==1.3.13
python-dotenv==0.15.0
Flask-Caching==1.9.0

在项目根目录向创建Dockerfile,指定镜像源,在基础镜像中安装requirements.txt包

FROM python:3.7
ENV PIPURL "https://pypi.tuna.tsinghua.edu.cn/simple"

ADD ./requirements.txt /home/
WORKDIR /home
RUN pip install --no-cache-dir -i ${PIPURL} -r requirements.txt
#CMD gunicorn -c gun.conf.py app:app

构建镜像

root@ubuntu:~/myproject/pira_score_web_application# docker build . -t=pira_score_web:latest

挂载flask项目根目录下的所有文件到容器内部启动

root@ubuntu:~/myproject/pira_score_web_application# docker run --rm -d -v `pwd`:/home -p 5001:5000 pira_score_web:latest

Nginx配置反向代理

ubuntu下安装nginx

sudo apt-get install nginx

查看nginx运行状态

root@ubuntu:~# service nginx status
● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-01-18 10:13:15 CST; 2s ago
     Docs: man:nginx(8)
  Process: 1141 ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid (code=exited, status=0/SUCCESS)
  Process: 1231 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
  Process: 1218 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
 Main PID: 1232 (nginx)
    Tasks: 5 (limit: 4915)
   CGroup: /system.slice/nginx.service
           ├─1232 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
           ├─1237 nginx: worker process
           ├─1238 nginx: worker process
           ├─1239 nginx: worker process
           └─1240 nginx: worker process

开启,停止,重启nginx服务

root@ubuntu:~# service nginx start
root@ubuntu:~# service nginx stop
root@ubuntu:~# service nginx restart
image.png

给Flask应用构建Nginx配置文件,通常在/etc/nginx/sites-enabled或者/etc/nginx/conf.d目录下创建单独的配置文件,而不直接在全局配置文件/etv/nginx/nginx.conf直接创建,在nginx.conf中已经引入这两个路径,这两个路径下的配置会被插入到全局配置文件中

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
Nginx配置文件的基本结构

Nginx配置文件为nginx.conf,结构如下

...              #全局块


events {         #events块
   ...
}

http      #http块
{
    ...   #http全局块
    server        #server块
    { 
        ...       #server全局块
        location [PATTERN]   #location块
        {
            ...
        }
        location [PATTERN] 
        {
            ...
        }
    }
    server
    {
      ...
    }
    ...     #http全局块
}

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
worker_connections 768;
# multi_accept on;
}

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

前缀匹配
精确前缀匹配: location = uri {...}
优先前缀匹配: location ^~ uri {...}
普通前缀匹配: location uri {...}
正则匹配
大小写敏感: location ~ uri {...}
大小写不敏感: location ~* uri {...}

location的匹配顺序以及优先级

1.首先匹配=
2.其次匹配^~
3.再其次按照配置文件的顺序进行正则匹配
4.最后是交给/进行通用匹配,通用匹配记录下最长的匹配作为命中的规则

server配置
server {
    listen 80;
    server_name 127.0.0.1;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_redirect    off;

        proxy_set_header    Host                $host;
        proxy_set_header    X-Real_IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for ;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        #deny 127.0.0.1;  #拒绝的ip
    }

    location /PiraScore/static {
        alias /home/gp/myproject/pira_score_web_application/static/;
        expires 30d;
        add_header wall  "use nginx cache";
    }
}

server参数配置详解

语法:proxy_set_header field value;
field :为要更改的项目,也可以理解为变量的名字,比如host
value :为变量的值

proxy_set_header    Host                $host;
proxy_set_header    X-Real_IP           $remote_addr;
proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for ;  
proxy_set_header    X-Forwarded-Proto   $scheme;
nginx启动

使用命令检查Nginx配置是否有语法错误

root@ubuntu:/etc/nginx/sites-enabled# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

重启Nginx

root@ubuntu:/etc/nginx/sites-enabled# service nginx restart

访问url http:127.0.0.1:80/PiraScore成功,访问静态文件的url查看是否被对应location处理成缓存,在响应头中查看是否有自定义内容,存在!

静态文件访问地址的响应头.png
也可以在日志中查看,日志中不应该存在请求静态文件的url,因为静态文件走了Nginx缓存
上一篇 下一篇

猜你喜欢

热点阅读