背景:遇到了部分代码执行时间长的问题,想要通过类似异步执行的策略将耗时长的代码块在后台单独执行。在网上搜索了解决方案,使用celery的比较多,于是开始引入celery。但由于大部分教程不是工厂模式的情景没有配置成功(也问了chatgpt但他一通乱答)。会遇到循环引入之类的问题。最终谷歌找到了完整教程转载在这里。原文后续还有socket配置,后面尝试。
框架:Flask
前提:你已经配置好了Redis
原文链接:testdriven.io/courses/fla…
**这是转载!这是转载!这是转载!这是转载!这是转载!
**
这是我的项目根目录下的文件结构,本次涉及到的有
- app/config/init.py
- app/utils/celerySetup.py
- app/task.py
- app/init.py
- run.py
安装Celery和Flask-CeleryExt
pip install celery
pip install flask-celeryext==0.4.3
我的版本是
celery==5.4.0 Flask-CeleryExt==0.4.3
加入Celery配置项到配置文件
app/config/init.py
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://127.0.0.1:6379/0")
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://127.0.0.1:6379/0")
为避免后面配置引入有问题,附上我的配置文件格式
import os
class Config:
## 你的内容
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://127.0.0.1:6379/1")
CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://127.0.0.1:6379/2")
## 你的内容
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
## 填开发环境下的配置项或pass(略过)
class TestingConfig(Config):
pass
class ProductionConfig(Config):
Celery应用文件
app/utils/celerySetup.py
from celery import current_app as current_celery_app
def make_celery(app):
celery = current_celery_app
celery.config_from_object(app.config, namespace="CELERY")
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
return celery
原文:
make_celery是一个工厂函数,它配置并返回一个 Celery 应用程序实例。- 我们没有创建新的 Celery 实例,而是使用current_app,以便共享任务按预期工作。
celery.config_from_object(app.config, namespace="CELERY")表示所有与 Celery 相关的配置键都应该大写并以 为前缀CELERY_。例如,要配置broker_url,您应该使用CELERY_BROKER_URL。- 注意这边新版也推荐配置键小写例如celery_broker_url
创建异步任务
app/task.py
from celery import shared_task
import time
from celery.result import AsyncResult
# 异步任务
@shared_task
def add_together(a, b):
print('------------------------------------------')
time.sleep(5)
return a + b
# 根据异步任务的id读取结果
def task_result(id):
result = AsyncResult(id)
return {
"ready": result.ready(),
"successful": result.successful(),
"value": result.result if result.ready() else None,
}
原文:
- 网络上的许多资源都建议使用
celery.task。在某些情况下,这可能会导致循环导入,因为您必须导入 Celery 实例。- 我们过去常常
shared_task让我们的代码可重复使用,这同样需要current_appinmake_celery而不是创建新的 Celery 实例。现在,我们可以将此文件复制到应用程序中的任何位置,它将按预期工作。
工厂函数create_app的修改
app/init.py 以下是涉及部分的代码,其中test_config,ProductionConfig,DevelopmentConfig是我配置文件中的变量引入其他配置的,Celery的两个配置项直接写在配置文件的基础配置中就好,之后有需要在根据运行环境变更
from flask import Flask
import redis
from .config import *
from flask_celeryext import FlaskCeleryExt
from project.celery_utils import make_celery #引入第二步创建的celery文件
## 你自己的内容
ext_celery = FlaskCeleryExt(create_celery_app=make_celery)
## 你自己的内容
## 注意这边才是工厂函数,ext_celery在外部才能被其他文件引用
def create_app(test_config=None, **kwargs):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
# Select configuration
env_config = os.getenv('FLASK_ENV', 'development')
if test_config:
app.config.from_object(test_config)
elif env_config == 'production':
app.config.from_object(ProductionConfig)
else:
app.config.from_object(DevelopmentConfig)
# Load instance config 其中的配置项会覆盖当前
app.config.from_pyfile('config.py', silent=True)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# Initialize Redis
app.redis = redis.StrictRedis.from_url(app.config['REDIS_URL'])
ext_celery.init_app(app)
from app.api.test import bp as test_bp
app.register_blueprint(test_bp)
return app
原文: 当我们实例化时
ext_celery,我们将自定义应用程序工厂传递make_celery给它。如果我们没有这样做,它FlaskCeleryExt会自动为我们创建 Celery 应用程序,但由于我们将运行共享任务,因此不建议这样做。
修改run.py
run.py(和app文件夹同级!!)
这是我的flask项目启动文件
from app import create_app,ext_celery
app = create_app()
celery = ext_celery.celery
if __name__ == '__main__':
app.run(debug=True)
测试
1. 设置测试接口调用异步函数
测试我这边用了两个测试接口,这是我的测试接口文件
文件目录:app/api/test.py
在create_app()中记得注册蓝图(前文代码包含)
from flask import (
Blueprint,request
)
from ..service.function import random_int_list
from app.task import add_together, task_result # 引入前文task.py中的异步函数和读取函数
bp = Blueprint('test', __name__, url_prefix='/api/test')
@bp.route('/')
def index():
return random_int_list(0, 100, 100)
@bp.route('/a')
def celery_test():
res = add_together.delay(1, 2)
return {"result_id": res.id}
@bp.route('/b', methods=['GET'])
def celery_get():
task_id = request.args.get('task_id')
res = task_result(task_id)
return res
2. 启动flask应用
我这边是执行run.py。无论如何启动切记确保程序执行到了run.py中加入的
from app import create_app,ext_celery
celery = ext_celery.celery
启动成功
4. 启动celery应用
在新终端执行一下命令启动celery
celery -A run.celery worker --loglevel=info -P threads
启动成功截图如下,可能会有一些警告,注意判别是否影响运行
注意这边原文和大部分教程都建议用
celery -A run.celery worker --loglevel=info
但在这里会造成无法确定线程导致无法进行任务(如下图),所以加了-P thread指定线程
5. 调用测试接口
调用app/api/test.py中定义的接口测试,在浏览器访问
127.0.0.1:5000/api/test/a获得返回值'result_id'
再使用这个result_id作为异步任务的task_id访问b接口查看异步任务执行结果。如图返回格式与我们在app/task.py中定义的返回格式一致。这里的ready代表任务是否结束。
异步任务成功在celery中运行
这边有个问题,从截图中看任务被执行了好几次,没找到原因,有无大神解答。
看其他项目有配置全局调用接口时判断是否异步,之后研究一下。以上,请大家指正1