Celery #4 結合Flask和apscheduler使用

weixin_33782386發表於2018-05-20

更進一步地,我們希望可以用一個web服務呼叫Celery,這樣我們可以改通過web服務檢視任務的執行。

Celery本身可以用beat定時,也提供了一定的狀態監視手段。但我們需要動態更改任務計劃和持久化任務記錄時,自建一套邏輯也許更加方便。

具體方案

思路:
實現一個Flask例項作為web服務,通過apscheduler在web程式中實現定時計劃。
任務執行前後,將相關時間、狀態、異常、結果等寫入mongoDB儲存。
通過web服務檢視celery轉檯、任務、任務執行記錄等。

  • 由於不再依賴訊息佇列獲取結果,可以不設定backend。
  • 這樣設計的問題是,不能捕捉到celery併發限制後,位於排隊序列裡的任務。記錄的是任務實際開始執行的時間。

現在目錄下有檔案:

tasks.py
admin.py

其內容:

# tasks.py
import time
import functools
from pymongo import MongoClient
from celery import Celery

conn = MongoClient()
log = conn.tasklog.log

app = Celery('tasks', broker='redis://localhost:6379/9')

def task_log(func):
    '任務裝飾器。向mongo寫入狀態資訊'
    @functools.wraps(func) 
    def wrapper(*args, **kargs):
        task_name = func.__name__
        log_id = log.insert({
            'name': task_name,
            'start_time': int(time.time() * 1000),
            'end_time': 0,
            'status': 0,
        })
        try:
            result = func(*args, **kargs)
            log.update({
                '_id': log_id
            }, {
                '$set': {
                    'end_time': int(time.time() * 1000),
                    'status': 1,
                    'result': str(result)
                }
            })
            return result
        except Exception as e:
            print(e)
            log.update({
                '_id': log_id
            }, {
                '$set': {
                    'end_time': int(time.time() * 1000),
                    'status': 2,
                    'err': str(e)
                }
            })
    return wrapper

@app.task
@task_log
def add(x, y):
    print('{} add {} equals {}'.format(x, y, x+y))
    return x + y
# admin.py
from celery.local import PromiseProxy
from flask import Flask, request
from apscheduler.schedulers.background import BackgroundScheduler
from pymongo import MongoClient

from utils.mkresp import mkresp
import tasks

conn = MongoClient()
log = conn.tasklog.log

# 讀取所有任務
TASKS = {}
for attrname in dir(tasks):
    attr = getattr(tasks, attrname)
    if type(attr) == PromiseProxy:
        TASKS[attrname] =  attr

# 定時任務計劃
sched = BackgroundScheduler()
#每分鐘執行一個add任務
sched.add_job(tasks.add.delay, 'cron', minute='*', args=(5,5))
sched.start()

# Flask app
app = Flask(__name__)

@app.route('/')
def r_index():
    return 'admin'

@app.route('/tasks')
def r_tasks():
    return mkresp(msg=list(TASKS.keys()))

@app.route('/tasklogs')
def r_tasklogs():
    docs = list(log.find())
    return mkresp(msg=docs)

@app.route('/taskrun/<string:taskname>')
def r_task_run(taskname):
    params = request.values.get('params')
    params = json.loads(params)
    print(params)
    task = TASKS.get(taskname)
    if not task:
        return mkresp(code=500, msg='不存在指定任務')
    task.delay(*params)
    return mkresp()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=9000)

使用celery -A tasks worker --loglevel=info -P gevent啟動celery,再執行admin.py。

現在,訪問http://localhost:9000/tasks即可看到所有任務。
訪問http://localhost:9000/tasklogs即可看到所有任務執行記錄。
訪問http://localhost:9000/taskrun/add?params=[1,2]即可立即執行add任務,引數1,2。

相關文章