说在前面,本文只是对Celery的简单介绍,细节方面有很多不足,大致知道Celery有什么用,应该怎么用,常见的配置怎么配,就行了,具体的还是多看看官方文档或者大神们的文章。
1. Celery是什么?
Celery是一款异步消息队列工具。
使用Celery的常见场景如下:
-
Web应用。当用户触发的一个操作需要较长时间才能执行完成时,可以把它作为任务交给Celery去异步执行,执行完再返回给用户。这段时间用户不需要等待,提高了网站的整体吞吐量和响应时间。
-
定时任务。生产环境经常会跑一些定时任务。假如你有上千台的服务器、上千种任务,定时任务的管理很困难,Celery可以帮助我们快速在不同的机器设定不同种任务。
-
同步完成的附加工作都可以异步完成。比如发送短信/邮件、推送消息、清理/设置缓存等。
Celery的架构如下图所示(网上找的):
其中的消息中间件
常用的就是RabbitMQ
和Redis
。
2. 安装Celery
# -U即--upgrade
$ pip install -U celery
# 安装Celery和捆绑包
pip install "celery[librabbitmq]"
pip install "celery[librabbitmq,redis,auth,msgpack]"
# 源码安装
https://PyPI.org/project/celery/
tar zxvf celery-0.0.0.tar.gz
cd celery-0.0.0
python setup.py build
python setup.py install
3. 使用Celery
3.1 Django和Celery
从Django 3.1以后,就已经集成了Celery,不需要我们在额外安装Celery了。
常见的Django项目结构:
- proj/
- manage.py
- proj/
- __init__.py
- settings.py
- urls.py
# proj/proj/celery.py
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')
app = Celery('proj')
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
# proj/proj/__init__.py:
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)
使用 @shared_task 装饰器添加任务
# Create your tasks here
from __future__ import absolute_import, unicode_literals
from celery import shared_task
@shared_task
def add(x, y):
return x + y
@shared_task
def mul(x, y):
return x * y
@shared_task
def xsum(numbers):
return sum(numbers)
在Django中配置Celery
如果要使用Django ORM或Django Cache框架提供results后端,可能需要安装django-celery-results
$ pip install django-celery-results
# proj/proj/settings.py
# 在Django项目的settings.py中将django_celery_results添加到INSTALLED_APPS:
INSTALLED_APPS = (
...,
'django_celery_results',
)
# 配置Celery以使用django-celery-results后端。 假设您使用Django的settings.py来配置Celery,请添加以下设置:
CELERY_RESULT_BACKEND = 'django-db'
# 对于缓存后端,可以使用:
CELERY_CACHE_BACKEND = 'django-cache'
# 通过执行数据库迁移来创建Celery数据库表:
$ python manage.py migrate celery_results
启动Celery worker
# -A 指定celery应用程序所在模块名
$ celery -A proj worker -l info
上面只是举了例子,并没有完整的Celery配置等信息,在Django中使用Celery的一些相关配置详情见 Celery中文文档 - Django
3.2 Flask和Celery
异步发送邮件
# views.py
from flask import Flask
from celery import Celery
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
# Flask-Mail configuration
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
app.config['MAIL_DEFAULT_SENDER'] = 'flask@example.com'
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html', email=session.get('email', ''))
email = request.form['email']
session['email'] = email
# send the email
msg = Message('Hello from Flask',
recipients=[request.form['email']])
msg.body = 'This is a test email sent from a background Celery task.'
if request.form['submit'] == 'Send':
# send right away
send_async_email.delay(msg)
flash('Sending email to {0}'.format(email))
else:
# send in one minute
send_async_email.apply_async(args=[msg], countdown=60)
flash('An email will be sent to {0} in one minute'.format(email))
return redirect(url_for('index'))
@celery.task
def send_async_email(msg):
"""Background task to send an email with Flask-Mail."""
with app.app_context():
mail.send(msg)
4. 配置Celery
在大型项目中,通常将所有配置集中放到某一个文件中,如celeryconfig.py
# celeryconfig.py
broker_url = "pyamqp://"
result_backend = "rpc://"
task_serializer = "json"
result_serializer = "json"
accept_content = ["json"]
timezone = "Asia/Shanghai"
enable_utc = True
通过x.config_from_object
加载配置
app = Celery('tasks', broker='pyamqp://test:test123456@localhost/myrabbitmq', backend="rpc://")
app.config_from_object('celeryconfig')
通过x.conf.update()
更新配置
app = Celery('tasks', broker='pyamqp://test:test123456@localhost/myrabbitmq', backend="rpc://")
app.conf.update(
task_serializer='json',
accept_content=['json'], # Ignore other content
result_serializer='json',
timezone='Europe/Oslo',
enable_utc=True,
)
记录日志
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
# TODO
# 然后像使用logging模块中的logger实例一样用就可以了。
任务绑定
通过任务绑定(bind=True),在我们编写的任务函数中可以访问到任务的属性和方法。示例如下:
@app.task(bind=True)
def dump_context(self, x, y):
print('Executing task id {0.id}, args: {0.args!r} kwargs: {0.kwargs!r}'.format(
self.request))
5. 总结
Celery其实并不复杂,通过Celery的装饰器将需要异步处理的任务以消息的形式写入消息中间件
(如RabbitMQ
/Redis
中),然后通过启动Celery worker
来消费这些消息,并执行这些异步任务,然后将异步任务的执行结果写入到Celery中所配置的结果存储中即可。
6. 参考文章
[1] Celery官方网站
[2] Celery 中文手册
[3] 任务调度利器:Celery
[4] Python 并行分布式框架 Celery 详解
[5] celery 简要概述
[6] python celery 任务队列
[7] 基于 Celery 的后台任务
[8] 在 Flask 中使用 Celery