解決多進程中APScheduler重複運行的問題

轉自:http://blog.csdn.net/raptor/article/details/69218271python

問題

在一個Python web應用中須要定時執行一些任務,因此用了APScheduler這個庫。又由於是用flask這個web框架,因此用了flask-apscheduler這個插件(本質上與直接用APScheduler同樣,這裏不做區分)。web

在開發中直接測試運行是沒有問題的,可是用gunicorn部署之後發生了重複運行的問題:docker

每一個任務在時間到的時刻會同時執行好幾遍。flask

注意了一下重複的數量,偏偏是gunicorn裏配置的worker進程數量,顯然是每一個worker進程都啓動了一份scheduler形成。多線程

解決

能夠想到的方案有幾個:app

  • --preload啓動gunicorn,確保scheduler只在loader的時候建立一次
  • 另外建立一個單獨的定時任務項目,單獨以一個進程運行
  • 用全局鎖確保scheduler只運行一次

通過實踐,只有第三個方案比較好。框架

preload的問題:socket

雖然這樣可使用scheduler建立代碼只執行一次,可是問題也在於它只執行一次,從新部署之後若是用kill -HUP重啓gunicorn,它並不會重啓,甚至整個項目都不會更新。這是preload的反作用,除非重寫部署腳本,徹底重啓應用。函數

單獨進程的問題:測試

也是由於部署麻煩,須要多一套部署方案,雖然用Docker會比較方便,但仍然不喜歡,並且同時維護兩個項目也多出不少沒必要要的事情。

全局鎖是一個較好的方案,但問題在於找一個合適的鎖。

python自帶的多進程多線程鎖方案都須要一個共享變量來維護,可是由於worker進程是被gunicorn的主進程啓動的,並不方便本身維護,因此須要一個系統級的鎖。

在Stackoverflow上看到有人是用了一個socket端口來作鎖實現這個方案,可是我也不喜歡這樣浪費一個寶貴的端口資源。不過這倒給了我一個啓發:

能夠用文件鎖!

因而有了這個解決方案:

import atexit
import fcntl
from flask_apscheduler import APScheduler

def init(app):
    f = open("scheduler.lock", "wb")
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler = APScheduler()
        scheduler.init_app(app)
        scheduler.start()
    except:
        pass
    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)

  

原理

init函數爲flask項目初始化所調用,這裏爲scheduler模塊的初始化部分。

首先打開(或建立)一個scheduler.lock文件,並加上非阻塞互斥鎖。成功後建立scheduler並啓動。

若是加文件鎖失敗,說明scheduler已經建立,就略過建立scheduler的部分。

最後註冊一個退出事件,若是這個flask項目退出,則解鎖並關閉scheduler.lock文件的鎖。

相關文章
相關標籤/搜索