Linix环境下 Gunicorn + Supervisor + Nginx 部署 Flask 项目、Celry定时任务

首先先把项目文件上传到服务器制定目录, 然后在终端用ssh username@ip的方式访问服务器,flask项目一般都是再虚拟环境下运行的,常规启动web服务都是先进入虚拟环境,再执行对应的启动文件。默认只启用单线程来处理web交互数据,一旦用户较多,就会出现阻塞导致页面卡住,很显然用户体验不佳,因此为了能够尽可能的提高服务器处理数据,希望能开启多个线程处理web交互,可以采用Gunicorn控制运行项目。

1.安装gunicorn

可以进入虚拟欢迎安装该插件

pip install gunicorn
2.启动项目
  • 常规启动:
(venv) [root@docker test]# python manage.py runserver --host 0.0.0.0 --port 8000
  • 基于gunicorn管理启动项目
(venv) [root@docker test]# gunicorn -w 4 -b 0.0.0.0:8000 manage:app
[2020-06-02 22:23:54 -0400] [34828] [INFO] Starting gunicorn 20.0.4
[2020-06-02 22:23:54 -0400] [34828] [INFO] Listening at: http://0.0.0.0:8000 (34828)
[2020-06-02 22:23:54 -0400] [34828] [INFO] Using worker: sync
[2020-06-02 22:23:54 -0400] [34831] [INFO] Booting worker with pid: 34831
[2020-06-02 22:23:54 -0400] [34832] [INFO] Booting worker with pid: 34832
[2020-06-02 22:23:54 -0400] [34833] [INFO] Booting worker with pid: 34833
[2020-06-02 22:23:54 -0400] [34834] [INFO] Booting worker with pid: 34834

参数介绍:

  • -w:指定fork的worker进程数
  • -b:指定绑定的端口
  • manage:python文件名,启动文件
  • app:falsk实例化对象app

这样就做到了使用gunicorn管理启动flask项目
上面的这种方式一般都会指定一个端口开启flask服务,这也导致前台页面访问的URL也必须用这个端口,用户体验不佳,所以我们可以使用nginx反向代理,把我们自定义端口映射到80端口,也就是用户访问ip或者域名经过反向代理就可以映射到自定义端口,如上面开的是8000端口,用户访问时必须是ip:8000才能访问,经过反向代理直接访问ip即可。

3.使用nginx代理

安装nginx需先退出虚拟环境,执行以下命令:

安装nginx的源
rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
安装nginx
yum install -y nginx

安装好nginx,直接去浏览器访问虚拟机或者服务器的ip地址即可访问nginx的默认欢迎页:welcome to nginx
接下来就需要做反向代理,把上面开的8000端口映射到80的配置!
查看nginx的安装路径:

[root@docker]# whereis nginx
nginx: /usr/sbin/nginx /usr/lib64/nginx /etc/nginx /usr/share/nginx /usr/share/man/man8/nginx.8.gz

默认配置放在/etc/nginx目录下

[root@docker conf.d]# cd /etc/nginx
[root@docker nginx]# ls
conf.d          koi-utf  mime.types  nginx.conf   uwsgi_params
fastcgi_params  koi-win  modules     scgi_params  win-utf
[root@docker nginx]# cat nginx.conf 

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    
    
    worker_connections  1024;
}


http {
    
    
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    add_header X-Frame-Options sameorigin always;
    include /etc/nginx/conf.d/*.conf; # 指定自定义配置文件路径,一般不在这里直接改,而是以引入的方式加入配置 
}

修改默认配置文件(default.conf)

[root@docker nginx]# cd conf.d/
[root@docker conf.d]# ls
default.conf
[root@docker conf.d]# cat default.conf
server {
    
    
 listen 80;
 server_name localhost;
 location / {
    
    
	root   /usr/share/nginx/html;
        index  index.html index.htm;
        proxy_pass http://localhost:8000;
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_connect_timeout 500s;
        proxy_read_timeout 500s;
        proxy_send_timeout 500s;
 }    
}

这里的8000端口需要跟前面web开启的端口保持一致才能形成映射关系!!!
修改后保存退出,然后重启nginx:

先检查一下nignx配置文件有没有问题
nginx -t
出现
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
这样就是说明没有问题,然后重启nginx
service nginx restart

如果重启报以下错误:

[root@docker ~]# /usr/sbin/nginx -s reload
nginx: [error] invalid PID number "" in "/var/run/nginx.pid"

解决方案:

nginx -c /etc/nginx/nginx.conf
# 其中nginx.conf文件的路径可以从nginx -t的返回中找到。
nginx -s reload

经过以上测配置,现在再次访问ip地址,居然可以直接访问到我们开的8000端口的flask项目啦!!
以上只是手动执行,如果想让网站持久化,就必须开着控制台,这样显然不是最佳的,反而不方便,万一控制台卡了,或者不小心误关了,那么前台的网站也就崩了!!!
所以我们得想个办法把该命令放到系统内部或者放在后台自动跑,不再需要每次都得开个控制台手动输入这么一大串启动命令,那么就可以用上一个神器—————超级进程管理(supervisor)。

扫描二维码关注公众号,回复: 12346844 查看本文章

对于进程管理Supervisor:就是管理服务端的多个进程,具有对于多进程的操作方便,包括单线程,多线程,线程组等等。但是有一个不好的地方是它仅支持python2.4到python3(不包括)之间的版本,不支持python3或以上版本。在操作系统方便,我了解到Supervisor在window操作系统是完全不支持,其他大部门都兼容。

前面说到Supervisor对于python的兼容问题,那么就是在操作系统默认要装python2.x版本,(我的做法是系统装了python2.79和python3.6),然后要把系统的默认python改为python2.7,需要执行以下命令来更改:

#查看系统装了哪些python
1、ls /usr/bin/python* 
#查看相应的版本
2、python --version
如果版本为python 2.x,用pip install supervisor又安装不上的话,用pip安装报以下错:
Supervisor requires Python 2.4 or later but does not work on any version of Python 3.  You are using version 3.6.6 (default, Dec 19 2018, 00:11:02)
    [GCC 4.9.2 20150212 (Red Hat 4.9.2-6)].  Please install using a supported version.
可以采用这种方式安装:
easy_install supervisor(亲测可行)
#试图更新版本,有时候可以
3、update-alternatives --list python
#我就是报这个错误
4、update-alternatives: error: no alternatives for python
#报错只需执行以下命令即可,最后的python2.7 1则是你安装python的路径
5、update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1

如果用easy_install安装报错:

[root@docker ~]# easy_install supervisor
-bash: easy_install: command not found

解决方式:

wget https://bootstrap.pypa.io/ez_setup.py -O - | python

安装好后,查看安装路径可用:

[root@docker]# whereis supervisord
supervisord: /usr/bin/supervisord

配置supervisord来管理进程

1.生成配置文件

在etc文件夹下创建supervisor文件夹,并且通过命令生成supervisor的配置文件

mkdir /etc/supervisor
echo_supervisord_conf > /etc/supervisor/supervisord.conf
2.修改supervisord.conf

修改文件最后两行的内容,取消注释,并修改文件后缀名

[root@docker supervisor]# ls
supervisord.conf
[root@docker supervisor]# vi supervisord.conf
# 修改这两行保存退出
[include]
files = /etc/supervisor/conf.d/*.conf
3.新建子配置文件
[root@docker supervisor]# mkdir conf.d
[root@docker supervisor]# cd conf.d
[root@docker conf.d]# vi default.conf

把启动flask项目的配置写入default.conf中:

[program:default]
command = /home/www/XXX/venv/bin/gunicorn -b 127.0.0.1:8000 manage:app
directory = /home/www/XXX
stdout_logfile = /home/www/XXX/supervisord/stdout.log
stderr_logfile = /home/www/XXX/supervisord/stderr.log
4.把配置更新至supervisor
[root@docker]# supervisorctl update # 更新配置
[root@docker]# supervisorctl reload # 重启进程
[root@docker]# supervisorctl stop all # 重启进程
[root@docker]# supervisorctl # 查看正在守候的进程
default                          RUNNING   pid 35743, uptime 0:46:10
supervisor> 

现在就可以通过ip访问flask项目了,且可以通过超级进程管理项目!!!
如果刚安装好supervisor,配置文件也都修改了,然后兴致勃勃得想启动管理控制台,结果报以下错误:

第一种错误:

[root@docker supervisor]# supervisorctl 
unix:///tmp/supervisor.sock no such file
supervisor> 

原因在于把初始化得配置文件修改后,没有更新配置!!!

解决方案:
supervisord -c /etc/supervisor/supervisord.conf

第二种错误:

在根目录下无法进入进程管理,而在supervisor目录下却可以

[root@docker ~]# supervisorctl 
Error: .ini file does not include supervisorctl section
For help, use /usr/bin/supervisorctl -h
[root@docker ~]# cd /etc/supervisor
[root@docker supervisor]# supervisorctl 
fastapi                          RUNNING   pid 11177, uptime 0:00:41
supervisor> 

如果出现以上这种情况,可能是由于你安装了两次supervisor,导致在根目录下启用supervisorctl调用的配置跟进入/etc/supervisor调用的配置不一样导致的,/etc/supervisor下的配置是通过easy_install安装的,而根目录下的配置则是我一开始采用pip install基于python3环境下安装的,显然不兼容,再加上没去修改配置文件,所以会一直报.ini文件不存在错误!!!

解决方案

卸载supervisor,重新安装即可,具体操作如下:

[root@docker /]# supervisord -v
4.2.0
[root@docker /]# whereis supervisord
supervisord: /usr/bin/supervisord /etc/supervisord.conf
[root@docker /]# rm -rf /usr/bin/supervisord
[root@docker /]# rm -rf /etc/supervisord.conf
[root@docker /]# supervisord -v
-bash: /usr/bin/supervisord: No such file or directory
[root@docker /]# supervisord -v
-bash: /usr/bin/supervisord: No such file or directory
[root@docker /]# supervisorctl
http://localhost:9001 refused connection
supervisor> exit()
[root@docker /]# supervisord -v
-bash: /usr/bin/supervisord: No such file or directory
[root@docker /]# whereis supervisorctl
supervisorctl: /usr/bin/supervisorctl /usr/local/python3/bin/supervisorctl
[root@docker /]# rm -rf /usr/bin/supervisorctl
[root@docker /]# rm -rf /usr/local/python3/bin/supervisorctl

重新安装步骤可参考前面的安装方式并设置配置文件!!
安装好,启动进程管理结果如下:

# 根目录进入管理
[root@docker /]# supervisorctl 
fastapi                          RUNNING   pid 13481, uptime 0:10:06
supervisor> 
# 进入配置路径进入管理
[root@docker /]# cd /etc/supervisor/
[root@docker supervisor]# supervisorctl 
facebook                         STOPPED   Jun 09 10:12 PM
fastapi                          RUNNING   pid 13481, uptime 0:11:08
supervisor> 

然后再把刚刚新增的项目启动程序更新到进程组里面去:

[root@docker supervisor]# supervisorctl update
fastapi: added process group
[root@docker supervisor]# supervisorctl 
fastapi                          RUNNING   pid 92364, uptime 0:00:14
supervisor> 

执行完以上命令可以看到,程序已经不再报错了,O了,如果有遇到以上两种错误都能得到解决,亲测有效!!

Celery定时执行任务

有时候需要定时来执行一些事务工作,比如常见的订单获取、汇率获取等,接下来就来介绍下关于flask使用celery进行定时任务(队列执行异步任务)。

1.安装必要插件
1. pip install Celery
2. pip install Flask-Celery-Helper
3. pip install celery-with-redis

还要安装redis,如果是Ubuntu:

sudo apt-get install redis-server

如果是linux的docker版本,安装方式如下:

+ 1.查看可用版本
docker search redis
  • 2.拉去最新镜像
docker pull redis:latest
+ 3.查看本地镜像
docker images
  • 4.运行redis容器
docker run -itd --name redis-test -p 6379:6379 redis

参数说明:
-p 6379:6379:映射容器服务的6379端口到宿主机的6379端口。外部可以直接通过宿主机ip:6379访问到Redis的服务。

  • 5.查看容器的运行信息
docker ps
  • 6.测试redis服务
[root@docker]# docker exec -it redis-test /bin/bash
root@66b8e7f6e86d:/data# redis-cli
127.0.0.1:6379> 

2.项目蓝图

project //项目文件
├── app //app
├── auth //各子模块
├── extensions.py //引入celery,中交桥接文件
├── __ init __.py //初始化文件
└── … //其他模块、模板、静态等
├── venv //虚拟环境
├── logs //日志文件
├── supervisord //超级进程日志
├── venv //虚拟环境
├── manage.py //启动文件
├── periodic_task.py //定时配置文件-celery的核心代码
├── requirements.txt //安装插件文件
└── config.py //配置文件

定时任务重要文件如下
  • 1.配置文件config
# -*- coding: utf-8 -*-

import os
BASE_PATH = os.getcwd()

class Config(object):
    SECRET_KEY = '98Cct2oNSlnHDdTl8'

    @staticmethod
    def init_app(app):
        # staticmethod可以在类不需要实例化时调用方法
        pass

class ProConfig(Config):
    CELERY_BROKER_URL = 'redis://localhost:6379/0'
    CELERY_BACKEND_URL = 'redis://localhost:6379/0'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
         "mysql+pymysql://username:password@ip:port/db"


class DevConfig(Config):
    # DEBUG = True
    # SQLALCHEMY_ECHO = True
    CELERY_BROKER_URL = 'redis://localhost:6379/0'
    CELERY_BACKEND_URL = 'redis://localhost:6379/0'
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://username:password@ip:port/db"
    # 连接多库
    SQLALCHEMY_BINDS = {
    
    
    'test':'mysql+pymysql://username:password@ip:port/db01'
    }
config = {
    
    'default': ProConfig, 'development': DevConfig}
  • 2.初始化文件__init__
'''
财务系统调度中心
'''

from flask import Flask, redirect, url_for
from config import config
from flask_sqlalchemy import SQLAlchemy
from .extensions import celery
from flask_login import LoginManager, login_required, current_user

# 实例化数据库对象
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'


def create_app(config_name='default'):
    '''
    1: 创建一个财务系统的app应用程序
    2: 把配置对象传递给所创建的app
    '''
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    app.jinja_env.add_extension('jinja2.ext.do')
    app.jinja_env.add_extension('jinja2.ext.loopcontrols')

    # 将app中的配置文件应用到db中
    db.init_app(app)
    celery.init_app(app) # celery 核心初始化
    login_manager.init_app(app)
    login_manager.login_message = '请先登录'

    from app.auth import auth as auth_bp
    app.register_blueprint(auth_bp, url_prefix='/auth')


    @app.route('/')
    @login_required
    def index():
        return redirect(url_for('main.home'))

    return app

  • 3.拓展文件extensions
from flask_celery import Celery
celery = Celery()
  • 4.定时配置文件 periodic_task
import os
from app import create_app
from celery import Celery, platforms
from celery.schedules import crontab
from app.auth.task import test # 需要执行的函数


def make_celery(app):
    celery = Celery(
        app.import_name,
        broker=app.config['CELERY_BROKER_URL'],
        backend=app.config['CELERY_BACKEND_URL']
    )

    celery.conf.update(app.config)
    TaskBase = celery.Task

    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)

    celery.Task = ContextTask

    return celery


app = create_app("default")
celery = make_celery(app)
platforms.C_FORCE_ROOT = True


# from __future__ import absolute_import, unicode_literals
# from celery.schedules import crontab

# celery worker -A periodic_task.celery --loglevel=info
# celery -A periodic_task.celery beat

@celery.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(crontab(minute="*/1"), test.s(), name="测试")

  • 5.定时需要执行的函数task.py
from app.extensions import celery
from datetime import datetime
import json
import os

@celery.task()
def test():
	print('这是定时任务====', datetime.now())
测试定时任务

进入项目虚拟环境

celery worker -A periodic_task.celery --loglevel=info

celery -A periodic_task.celery beat

这样手动执行显然是不可行的,这又回到上面提到的超级进程,那么定时任务也可以放到超级进程里进行管理

使用supervisord来管理celery

同样的进入supervisor的配置路径:

[root@docker]# cd /etc/supervisor/conf.d
[root@docker conf.d]# vi task.conf

把以下配置写入task.conf中:

[program:facebook_task]
command=/home/www/XXX/venv/bin/celery worker --app=periodic_task.celery --beat -l INFO
process_name=%(program_name)s
numprocs=1
directory=/home/www/XXX
autostart=true 
autorestart=unexpected 
stdout_logfile=/home/www/XXX/logs/celery/stdout.log
stderr_logfile=/home/www/XXX/logs/celery/stderr.log

把配置更新至supervisor

[root@docker]# supervisorctl update # 更新配置
[root@docker]# supervisorctl 
default                          RUNNING   pid 90400, uptime 1:29:36
task                             RUNNING   pid 90402, uptime 0:20:01

经过上面的配合,就可以实现了超级进程管理定时任务、flask项目了

注意事项

有时候因为单次执行任务耗时较长,且大于预设得执行间隔时间,那么如果就按以上得方式去跑的话,会出现数据交叉,如果有操作数据库还有可能出现死锁,如下图

在这里插入图片描述

执行测试代码如下:
task.py

@celery.task()
def get_count():
    for i in range(1, 100):
        print(i, '----------------', datetime.now())
        time.sleep(1)

定时配置:
periodic_task.py

import os
from app import create_app
from celery import Celery, platforms
from celery.schedules import crontab
from app.account.task import get_count


def make_celery(app):
    celery = Celery(
        app.import_name,
        broker=app.config['CELERY_BROKER_URL'],
        backend=app.config['CELERY_BACKEND_URL']
    )
    
    # 执行一次,防止间隔时间不足以执行一次
    celery.conf.ONCE = {
    
    
      'backend': 'celery_once.backends.Redis',
      'settings': {
    
    
        'url': 'redis://localhost:6379/0',
        'default_timeout': 60 * 60
      }
    }

    celery.conf.update(app.config)
    TaskBase = celery.Task

    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)

    celery.Task = ContextTask

    return celery


app = create_app("default")
celery = make_celery(app)
platforms.C_FORCE_ROOT = True

@celery.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(crontab(minute="*/1"), get_count.s(), name="测试")

以上得配置很显然,执行单次得task耗时已经大于1分钟了,所以导致以上截图得情况!!

解决方案

经过万能的google,终于找到解决方案:celery_one

安装库

pip install -U celery_once

其中U代表更新库到最新版本
修改celery配置,新增celery_once相关配置,源码如下:

import os
from app import create_app
from celery import Celery, platforms
from celery.schedules import crontab
from app.account.task import get_count


def make_celery(app):
    celery = Celery(
        app.import_name,
        broker=app.config['CELERY_BROKER_URL'],
        backend=app.config['CELERY_BACKEND_URL']
    )
    
    # 执行一次,防止间隔时间不足以执行一次
    celery.conf.ONCE = {
    
    
      'backend': 'celery_once.backends.Redis',
      'settings': {
    
    
        'url': 'redis://localhost:6379/0',
        'default_timeout': 60 * 60
      }
    }

    celery.conf.update(app.config)
    TaskBase = celery.Task

    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)

    celery.Task = ContextTask

    return celery


app = create_app("default")
celery = make_celery(app)
platforms.C_FORCE_ROOT = True

@celery.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(crontab(minute="*/1"), get_count.s(), name="测试")

最后task那边也需要做点修改:

from celery_once import QueueOnce

@celery.task(base=QueueOnce, once={
    
    'graceful': True})
def get_count():
    for i in range(1, 100):
        print(i, '----------------', datetime.now())
        time.sleep(1)

最后测试结果:

在这里插入图片描述

OK!那种交叉执行的情况已经得到解决!!!

注意

有时候安装celery的默认版本,是由于版本过低导致的,运行任务时会报以下错误

在这里插入图片描述
解决: 升级下celery的版本即可

pip install --upgrade celery

猜你喜欢

转载自blog.csdn.net/Lin_Hv/article/details/106543536
今日推荐