背景:最近在做的全域事件項目,快要靠近尾聲了,需要用到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對象不會出現問題。