背景:最近在做的全域事件项目,快要靠近尾声了,需要用到uwsgi部署至生产环境,由于之前是debug模式,运行项目也是通过命令 python manager.py runserver (manage是通过flask_script创建的脚本管理,用于类似django的数据库初始化、迁移和管理app等操作)。现项目由uwsgi管理,由于uwsgi一些特性造成项目运行中出现的一系列问题,顺便记录下解决方案。
uwsgi.ini
1 [uwsgi] 2 # 项目文件夹 3 chdir = xxx/xxx/xxx 4 # wsgi文件路经 5 wsgi-file = xxx/xxx/xxx/manage.py 6 # 回调的app对象 7 callback = manager 8 # 虚拟环境路经 9 home = /home/xxx/.virtualenvs/xxx 10 # 主进程 11 master = true 12 # 最大输了的工作进程 13 processes = 2 14 # 项目中使用的IP:端口 15 http = xxx.xxx.xxx.xxx:5000 16 # 退出的时候是否清理环境 17 vacuum = true 18 # uwsgi日志文件路经 19 daemonize = /home/xxx/xxx/xxx/uwsgi.log 20 # 进程pid文件 21 pidfile = /home/xxx/xxx/xxx/uwsgi.pid
一、uwsgi通过callback回调app,manager虽管理app,但并不能提供uwsgi所用到的回调的app,直接如上配置callback=manager,接口请求在uwsgi.log中会看到__callback__错误,因为数据库迁移后manager没什么用,因此将manager换成原app,如下:
manager.py
1 from xxx import create_app 2 3 # 创建flask应用对象 4 app = create_app("product") 5 6 7 if __name__ == '__main__': 8 9 app.run()
uwsgi.ini修改callback
1 # 回调的app对象 2 callback = app
二、flask_apscheduler定时任务不启动
uwsgi启动后在没有请求的时候,部分进程会被挂起,需在uwsgi.ini中增加如下配置:
1 # flask_apscheduler配置 2 enable-threads = true 3 preload = true 4 lazy-apps = true
三、uwsgi启动后,定时任务启动两次,重复启动
原因uwsgi中开启了两个进程,进程独享一份资源,因此两个进程都启动了各自的scheduler,网上找了许久解决方案后,最终确定用文件锁的方式解决
create_app函数中
1 from xxx.tasks import scheduler 2 import os 3 import atexitcc 4 import fcntl 5 6 def create_app(config_name): 7 app = Flask(__name__, template_folder="static/") 8 。。。 9 10 # 使用app初始化任务 11 # 使用文件锁,解决flask_apscheduler定时任务重复启动问题 12 f = open(os.path.join(BASE_DIR, "xxx/xxx/scheduler.lock"), "wb") 13 try: 14 fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) 15 scheduler.init_app(app) 16 scheduler.start() 17 except: 18 pass 19 20 def unlock(): 21 fcntl.flock(f, fcntl.LOCK_UN) 22 f.close() 23 24 # 注册退出事件,如果flask项目退出,则解除scheduler.lock文件锁并关闭文件 25 atexit.register(unlock)
四、uwsgi启动项目后,config中配置的定时任务正常运行且不会重复启动,uwsgi.log中也可以看到scheduler正常启动;但通过api启动,使用scheduler.add_job()方式启动的任务,从uwsgi.log中发现是等待scheduler start状态,由此发现还是创建了两个scheduler对象。看了许久代码后发现了问题
因为项目中需要运行的任务较多,任务函数也都是需要区分开写在不同py文件中的,因此为了方便管理,我在项目中创建了tasks文件夹(python包),在tasks中的__init__.py文件中定义了scheduler对象,tasks中的各任务文件引用此scheduler并使用
tasks.__init__.py
1 from flask_apscheduler import APScheduler 2 from apscheduler.schedulers.background import BackgroundScheduler 3 from multiprocessing import Queue 4 5 # APScheduler任务对象 6 scheduler = APScheduler(BackgroundScheduler(timezone="Asia/Shanghai")) 7 8 # 任务流 9 event_info_q = Queue() 10 11 # 处理的任务中间件列表 12 middleware_li = []
因此在uwsgi开启多进程情况下,scheduler还是因多进程创建了多次,加上文件锁的原因,仅一个scheduler被启动,于是改动如下:
tasks.__init__.py
1 # APScheduler任务对象 2 scheduler = None 3 4 # 任务流 5 event_info_q = Queue() 6 7 # 处理的任务中间件列表 8 middleware_li = []
create_app函数
1 from flask_apscheduler import APScheduler 2 from apscheduler.schedulers.background import BaskgroundScheduler 3 from xxx import tasks 4 import os 5 import atexit 6 import fcntl 7 8 def create_app(config_name): 9 app = Flask(__name__, template_folder="static/") 10 。。。 11 12 # 使用app初始化任务 13 # 使用文件锁,解决flask_apscheduler定时任务重复启动问题 14 f = open(os.path.join(BASE_DIR, "xxx/xxx/scheduler.lock"), "wb") 15 try: 16 fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) 17 tasks.scheduler = APScheduler(BaskgroundScheduler(timezone="Asia/Shanghai")) 18 tasks.scheduler.init_app(app) 19 tasks.scheduler.start() 20 except: 21 pass 22 23 def unlock(): 24 fcntl.flock(f, fcntl.LOCK_UN) 25 f.close() 26 27 # 注册退出事件,如果flask项目退出,则解除scheduler.lock文件锁并关闭文件 28 atexit.register(unlock)
因为项目启动后首先调用的是create_app,create_app中将tasks.__init__.py中的scheduler重置为了APScheduler对象,并初始化app和启动,因此tasks文件夹内的各任务文件引用scheduler对象不会出现问题。
Comments