最近在写接口自动化平台的时候,一个功能是批量执行测试用例,即对一个测试集操作。
测试集的执行时间可能会较长,所以想做成一个异步任务。python 实现异步任务最常见的工具是celery,此次使用flask和celery完成了功能,将实践结果进行记录。
Celery是Python开发的分布式任务调度模块,可以执行异步任务和定时任务。
详细内容可以查看官网的文档
pip install celery
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。
celery支持的消息中间件很多,包括消息队列、关系型数据库和非关系型数据库。最常见的有RabbitMQ和Redis。RabbitMQ是官方推荐使用的,相比较于redis的一个好处是消息可以持久保存。
我使用的是RabbitMQ作为消息中间件。celery作为生产者将任务保存在消息队列中,之后由消费者者将队列中的任务进行消费。
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
此处的worker其实就是一个消费者,从消息队列中获取消息并进行执行。
worker是需要单独启动的,并不是跟随flask一起启动的。
分布式的意思就是可以启动多个worker,像多个工人并发的进行工作。
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等。
由于在业务上我执行任务的功能有保存对应的执行结果在业务数据库表中,此处没有用到结果存储。
github地址
为了更加方便的flask进行集成,使用了flask-celery-helper这个库。
下面的例子也是基于这个库编写的。
使用这个库的原因除了更加方便的使用配置信息,更重要的是可以在celery中使用flask 上下文对象。
pip install Flask-Celery-Helper
以下只显示了celery相关的代码
config.py
from app.config.setting import BaseConfig
class DevelopmentSecure(BaseConfig):
"""
开发环境安全性配置
"""
CELERY_BROKER_URL = 'amqp://admin:[email protected]:8083/my_vhost'
CELERY_BROKER_URL 是消息中间件RbbitMq的连接信息
init.py
from flask_celery import Celery
celery = Celery()
实例化celery对象
app.py
from app.libs.init import mongo, socket_io, celery
def create_app(register_all=True, environment='production'):
app = LinFlask(__name__, static_folder='./assets')
app.config['ENV'] = environment
env = app.config.get('ENV')
if env == 'production':
app.config.from_object('app.config.setting.ProductionConfig')
app.config.from_object('app.config.secure.ProductionSecure')
elif env == 'development':
app.config.from_object('app.config.setting.DevelopmentConfig')
app.config.from_object('app.config.secure.DevelopmentSecure')
app.config.from_object('app.config.log')
if register_all:
register_blueprints(app)
from app.models.user import User
Lin(app, user_model=User)
register_before_request(app)
register_after_request(app)
apply_cors(app)
# 创建所有表格
create_tables(app)
mongo.init_app(app)
celery.init_app(app)
socket_io.init_app(app, cors_allowed_origins='*')
return app
创建app对象 celery.init_app(app) 的作用是将celery注册到app上
starter.py
from app.app import create_app
app = create_app(environment='development')
from app.libs.init import celery
celery.conf.update(imports='app.libs.tasks')
if __name__ == '__main__':
app.run(debug=True)
starter.py是项目的启动文件。
由于是通过这个文件的celery来启动worker,此处celery.conf.update需要引入celery任务的路径。
在这个文件的目录下启动worker
celery -A starter.celery worker -l info --pool=solo
tasks.py
from app.libs.init import celery
from app.models.project import Project
from celery.utils import log
sys.path.append('../../')
celery.conf.update(imports='app.libs.tasks')
@celery.task
def execute_test(pid):
# 初始化工程进度
project = Project.query.filter_by(id=pid, delete_time=None).first_or_404()
project.update_progress(progress=0, running=True)
try:
project.batch()
log.logger.info('任务执行完成')
finally:
time.sleep(3)
project.update_progress(progress=0, running=False)
return True
execute_test 是需要异步执行的任务
task.py
@task_api.route('/' , methods=['GET'])
def execute_project(pid):
project = Project.query.filter_by(id=pid, delete_time=None).first_or_404()
project.is_running()
from app.libs.tasks import execute_test
execute_test.delay(pid)
return Success(msg='启动成功')
execute_test.delay(pid) 进行celery任务调用
在celery的任务中无法使用socketio(flask-socketio)
功能需求是在任务执行完毕后向所有连接的客户端广播是哪个任务执行完成了。
查询了一些资料,应该是不支持在celery中直接使用socketio进行websocket消息发送。
只能曲线救国了,在celery中调用自己服务的api,在被调用的api中进行socketio的emit。