说明
有一些非常耗时的任务,无法实现实时的RPC调用。因此计划使用celery + flask提供异步任务调度服务。
一个请求的服务过程是这样:
- 1 服务器接到一个请求(一个几k到几百k的文本)
- 2 服务器计算摘要作为键值,将其加入异步任务。
- 3 服务器将摘要返回,状态为calculating。
- 4 异步任务执行耗时计算,结果有两个副本。一个存在本地(pkl),一个发往目标服务器。这样如果目标服务器没收到,服务就把本地的副本读取再发送,避免重新计算。
1 流程与项目结构
大体上是这样的结构,整个服务存在训练任务和预测任务。预测结果保存在本地和目标数据库。

celery和flask结合使用的基本结构主要参考这篇文章, 一目了然,很适合初次使用者。
以下这部分引用自上面的文章
from flask import Flask
from celery import Celery
from celery.result import AsyncResult
import time
app = Flask(__name__)
# 用以储存消息队列
app.config['CELERY_BROKER_URL'] = 'redis://127.0.0.1:6379/0'
# 用以储存处理结果
app.config['CELERY_RESULT_BACKEND'] = 'redis://127.0.0.1:6379/0'
celery_ = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery_.conf.update(app.config)
@celery_.task
def my_background_task(arg1, arg2):
# 两数相加
time.sleep(10)
return arg1+arg2
@app.route("/sum/<arg1>/<arg2>")
def sum_(arg1, arg2):
# 发送任务到celery,并返回任务ID,后续可以根据此任务ID获取任务结果
result = my_background_task.delay(int(arg1), int(arg2))
return result.id
@app.route("/get_result/<result_id>")
def get_result(result_id):
# 根据任务ID获取任务结果
result = AsyncResult(id=result_id)
return str(result.get())
启动的时候分别在三个终端输入命令:
# 运行flask服务
gunicorn entry_ner_cpu:app -b 0.0.0.0:5001
# 使用celery执行异步任务
celery -A entry_ner_cpu.celery_ worker
# 使用flower监督异步任务
flower --basic_auth=admin:admin --broker=redis://127.0.0.1:6379/0 --address=0.0.0.0 --port=5556
作用分别是启动flask服务(基本web服务),启动celery服务(异步任务),启动flower服务(监控任务)。
-
1 flower的监控面板

-
2 执行任务接口
调用计算任务的接口。(该计算任务计算1+2)

-
3 查询任务结果的接口
使用任务ID获取结果。

2 测试案例
有些文章也介绍了celery的配置和flask是有不同的,单独配置。
2.1 Celery 项目结构
先把celery包装成一个类似函数包的方式,项目结构类似,内容参考这篇文章

实际我做了一个小的项目包测试,和原文略有不同。(这个算是尝试版,可以用,但可能不是最优雅的方式)
test_project
├── entry_test_celery1.py
├── test_celery1
│ ├── __init__.py
│ ├── task1.py
│ └── tasks.py
其中entry_test_celery1.py作为入口程序和实际存放异步任务的文件夹test_celery1平级。如果是函数包的话,我习惯把所有的函数收拢到__init__.py下面,在这个case下所有的函数(任务taskx)收拢到tasks.py下面。
- 1
__init__.py初始化celery实例,指定本地的broker、backend(结果)和任务列表
from celery import Celery
app = Celery('test_celery',
broker='redis://127.0.0.1:6379/0',
backend='redis://127.0.0.1:6379/0',
include=['test_celery1.tasks'])
- 2
task1.py放了一个简单的求和任务,耗时10秒
from . import app
import time
@app.task
def my_background_task(arg1, arg2):
# 两数相加
time.sleep(10)
return arg1+arg2
- 3
tasks.py任务就是把所有的任务收拢起来(当前只有一个)
from . task1 import my_background_task
以上三个文件组成的包完成了一个最简单的celery任务定义。
- 4 使用
entry_test_celery1.py调用celery任务。原文本来使用ready和result两个函数来进行轮询,我使用了AsyncResult来直接获取。
from test_celery1.tasks import my_background_task
import time
from celery.result import AsyncResult
# 先启动test_celery1
# celery -A test_celery1.app worker --loglevel=info
if __name__ == '__main__':
task = my_background_task.delay(1, 2)
task_id = task.id
#此时,任务还未完成,它将返回False
# print('Task finished? ', result.ready())
# print('Task result: ', result.result)
print('Task finished? ', task_id)
print('Task result: ', AsyncResult(id=task_id).get())
# 延长到10秒以确保任务已经完成
time.sleep(10)
# 现在任务完成,ready方法将返回True
print('Task finished? ', task_id)
print('Task result: ', AsyncResult(id=task_id).get())
使用时先启动celery,切换到入口函数的当前目录,执行
celery -A test_celery1.app worker --loglevel=info
出现了熟悉的芹菜

然后执行python
python3 entry_test_celery1.py

2.2 和Flask结合
将入口函数改为一个简易的flask服务。
from test_celery1.tasks import my_background_task
import time
from celery.result import AsyncResult
from flask import Flask
app = Flask(__name__)
@app.route('/test_add/<x>/<y>', methods=['GET','POST'])
def test_add(x,y):
# 将请求塞入任务
task = my_background_task.delay(int(x),int(y))
# 返回任务信息
return 'Task Commited' + str(task.id)
@app.route('/get_result/<task_id>', methods=['GET','POST'])
def get_result(task_id):
print('Accompolished Status', AsyncResult(id=task_id).status)
return 'Accompolished Status' + AsyncResult(id=task_id).status
if __name__ == '__main__':
app.run(debug=True)
调用接口计算

查看任务状态:第一次看还没有完成,过几秒就完成了


注意,启动服务的时候要用gunicorn启动
gunicorn entry_test_celery1_flask:app -b 0.0.0.0:5000
如果直接启动会报如下错误
python3 entry_test_celery1_flask.py

对应的要重新声明一个celery实例来处理
tem_celery = Celery(backend='redis://127.0.0.1:6379/0')
@app.route
Celery+Flask异步任务调度服务搭建

文章计划使用celery + flask提供异步任务调度服务,介绍了流程与项目结构、测试案例,搭建了docker-compose项目进行应用。原计划使用celery调用神经网络模型失败,采用以RabbitMQ为队列的备用方案,还提及了超时、重试、回调等功能的实现思路。
最低0.47元/天 解锁文章
553





