Python 全栈系列48 - celery + flask 异步调用任务

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

说明

有一些非常耗时的任务,无法实现实时的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
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值