首先先把项目文件上传到服务器制定目录, 然后在终端用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)。
对于进程管理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