uwsgi部署flask,flask_apscheduler任務遇到各種問題解決
背景:最近在做的全域事件專案,快要靠近尾聲了,需要用到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.py6 # 回撥的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物件不會出現問題。