Celery Task Custom跟踪方法

时间:2019-02-27 02:23:26

标签: python flask celery celery-task

我的主要问题取决于我需要知道某个任务是否仍在排队,已开始或已撤销的事实。

我无法使用芹菜和Redis进行此操作,因为结果在Redis中被删除24小时。

我有一些想法,但是我认为最可靠的想法是让数据库跟踪并手动添加用户正在运行的任务所需的关键信息。

有一些方法可以在任务开始之前运行,并且我也可以在创建任务或撤销它们时手动使用数据库?我不会为每个任务创建一个新行,而是为每个用户更新一个行,因为我只对每个用户的最后一个任务感兴趣。

1 个答案:

答案 0 :(得分:0)

您可能必须结合多种方法。如果您的结果在后端过期(这是合理的),则必须使用不同的存储(例如数据库)来长期归档任务状态。首先,您可以启用task_track_started,以便任务在工作程序开始执行时报告STARTED的状态)。然后定期检查结果后端,以查看未处于就绪状态(SUCCESSFAILUREREVOKED)的任务的状态更新。如果它们处于最终状态,请使用forget()方法从后端删除结果。

唯一的问题是已撤销的任务。如果没有可用的工作程序,则撤消任务无效(这就是为什么在调用撤消时应始终等待答复)的原因。如果工作人员很忙,因此该任务仍留在消息队列中,那么工作人员只需要注意,当他们从队列中拿起该任务时,应该将其丢弃,但它只能以工作人员的状态存储。一旦接受,他们将放弃任务,结果最终将包含REVOKED状态。关键是要注意,撤消的任务仅保持在工作人员状态下,因此如果工作人员崩溃,则应使用--statedb参数来保持状态。否则,已经撤销的任务将很乐意由同一个或另一个工作人员处理。

我猜您最好的选择是调用revoke命令,如果您收到工作人员的答复,请将数据库中任务的内部状态设置为FLAGGED_REVOKED之类。在状态更新循环中,仅当其不是PENDING时,才更新已撤销任务的状态。

我有一个简单的作业调度应用程序,它使用APScheduler作为调度程序,并使用Celery作为执行层。有关作业,作业运行和计划的信息保存在MongoDB中。这是我用来取消工作的代码:

database = scheduler._jobstores['default'].collection.database
collection = database['runs']

run = collection.find_one({'job_id': job_id, '_id': run_id})
if run.get('task_state') in ('PENDING', 'RECEIVED', 'STARTED', 'RETRY'):
    reply = celery.control.revoke(run['task_id'], terminate=terminate, reply=True)
    if reply:
        collection.update_one({'_id': run['_id']},
                              {'$set': {'task_state': 'FLAGGED_REVOKED'}})
    else:
        raise Exception('Failed to revoke the task (no reply received)')
else:
    raise Exception('Job execution cannot be canceled')

这是我的状态更新代码(作为内部APScheduler作业每隔几秒钟运行一次):

database = scheduler._jobstores['default'].collection.database
collection = database['runs']

runs = collection.find({
    'task_id': {'$exists': True},
    'task_state': {'$nin': ['SUCCESS', 'FAILURE', 'REVOKED']}
})
for run in runs:
    result = AsyncResult(run['task_id'],
                         backend=celery.backend, app=celery)
    if run['task_state'] == 'FLAGGED_REVOKED' and result.state == 'PENDING':
        update = {'task_state': 'FLAGGED_REVOKED'}
    else:
        update = {'task_state': result.state}
    if result.state == 'FAILURE':
        update['exception'] = str(result.result)
        update['traceback'] = result.traceback
    elif result.state == 'SUCCESS':
        update['result'] = result.result
    if result.date_done:
        date_done = dateparser.parse(result.date_done) \
            if isinstance(result.date_done, str) else result.date_done
        update['finish_time'] = date_done
    try:
        collection.update_one({'_id': run['_id']}, {'$set': update})
    except Exception as e:
        print('Failed to update task status: %s', str(e))
    else:
        if result.state in ['SUCCESS', 'FAILURE', 'REVOKED']:
            result.forget()