一、介绍
1、简介
Celery是一个功能完备即插即用的任务队列。它使得我们不需要考虑复杂的问题,使用非常简单。 celery适用异步处理问题,当发送邮件、或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。 celery的特点是:
- 简单,易于使用和维护,有丰富的文档。
- 高效,单个celery进程每分钟可以处理数百万个任务。
- 灵活,celery中几乎每个部分都可以自定义扩展。
2、概念
2.1 Brokers
brokers 中文意思为中间人,在这里就是指任务队列本身,Celery 扮演生产者和消费者的角色,brokers 就是生产者和消费者存放/拿取产品的地方(队列)
常见的 brokers 有 rabbitmq、redis、Zookeeper 等
2.2 Result Stores / backend
顾名思义就是结果储存的地方,队列中的任务运行完后的结果或者状态需要被任务发送者知道,那么就需要一个地方储存这些结果,就是 Result Stores 了
常见的 backend 有 redis、Memcached 甚至常用的数据都可以。
2.3 Workers
就是 Celery 中的工作者,类似与生产/消费模型中的消费者,其从队列中取出任务并执行。Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
2.4 Tasks
就是我们想在队列中进行的任务咯,一般由用户、触发器或其他操作将任务入队,然后交由 workers 进行处理。
理解以上概念后我们就可以快速实现一个队列的操作:
3、安装
首先需要安装包,celery 是 4.0 及以上版本请确保 python 的 redis 库版本在 2.10.4 及以上,否则会出现 redis 连接 timeout 的错误。最好使用Linux环境,我再windows下感觉一直报错就换Linux了。python3.6的话celery我装最新的结果吧python给我更新成3.7的了,后面装了celery4.2的 版本。
pip install Celery
pip install redis
二、简单使用
1、实例化Celery和 定义任务函数。
新建一个包,然后新建一个tasks.py文件。这里我们用 redis 当做 celery 的 broker 和 backend。
#tasks.py
from celery import Celery
app = Celery('tasks', broker='redis://127.0.0.1:6379/0', backend='redis://127.0.0.1:6379/0')
@app.task #普通函数装饰为 celery task
def add(x, y):
print("任务函数正在执行....")
return x + y
2、运行celery服务。
在 tasks.py 所在目录下运行:
创建一个worker,意思是运行 tasks.py 中任务集合的 worker 进行工作(当然此时broker中还没有任务,worker此时相当于待命状态)。
这里,-A 表示我们的程序的模块名称,worker 表示启动一个执行单元,-l 是批 -level,表示打印的日志级别。可以使用 celery –help 命令来查看celery命令的帮助文档。
celery -A tasks worker --loglevel=info
可以看到启动成功了
当一个worker启动时,它会产生一定数量的子进程.这些进程的默认数量等于该计算机上的核心数nproc --all
,你可以自己指定,例如:
celery -A proj worker --loglevel=INFO --concurrency=2
如果要启动多个worker,可以另开窗口执行多次的启动命令,多个worker会竞争执行任务
3、调用任务
新建一个文件trigger.py,
#trigger.py
from tasks import add
if __name__ == '__main__':
result = add.delay(4, 4) #不要直接 add(4, 4),这里需要用 celery 提供的接口 delay 进行调用
print(result.ready())#判断是否执行完成,如果执行完成返回True,未完成返回False
print(result.id) #获取执行的任务id
print(result.get()) #获取执行的结果
然后执行trigger.py,查看celery启动的页面,可以看到有接收到任务并执行celery-task-meta-78fe3bc6-58d9-4173-836c-e69bdef7a7b2
然后查看redis中有执行结果
在某些特定情境下,我们会先发起celery任务,过一段时间之后再获取其执行结果,而不用一直等待。
代码如下:
from celery.result import AsyncResult
AsyncResult(task_id).get()
如果有引入tasks文件函数失败的可以看下面
工作中经常会碰见在pycharm中写python代码,送到服务器上去运行的情况,如果要调用同级目录的文件,linux服务器上直接import就可以了,但是在pycharm中会报错。
实际上只需要在pycharm中简单配置一下,就可以解决这个问题,右击调用文件所在的包 然后选择:
Mark Directory as->Sources Root
4、配置信息也可以直接在app中设置
from celery import Celery
app = Celery('tasks')
app.conf.update(
result_backend='redis://127.0.0.1:6379/0',
broker_url='redis://127.0.0.1:6379/0',
)
@app.task
def add(x, y):
print("任务函数正在执行....")
return x + y
5、配置信息也可以通过专有的配置模块来配置
在tasks.py模块 同级目录下创建配置模块celeryconfig.py
result_backend = 'redis://:password@127.0.0.1:6379/0'
broker_url = 'redis://:password@127.0.0.1:6379/0'
tasks.py文件修改为:
from celery import Celery
import celeryconfig
# 我们这里案例使用redis作为broker
app = Celery('tasks')
# 从单独的配置模块中加载配置
app.config_from_object('celeryconfig')
三、Celery创建多个队列,将任务分配至不同队列
参考 https://www.dazhuanlan.com/2019/12/17/5df83d3ff2c96/
-
Celery 默认使用名为 celery 的队列 (可以通过 CELERY_DEFAULT_QUEUE 修改) 来存放任务., 因为在进行任务发送时, 如果没有指明队列, 将默认发送至队列名称为celery的队列中。
-
我们可以使用 优先级不同的队列 来确保高优先级的任务优先执行.
1、定义一个queuetasks.py文件,实例化一个Celery app ,以及定义一些Task函数
from celery import Celery
import time
app = Celery('queuetasks')
app.config_from_object('celeryconfig')
@app.task
def task1(x, y):
print("任务task1函数正在执行....")
time.sleep(10)
return x + y
@app.task
def task2(x, y):
print("任务task2函数正在执行....")
time.sleep(20)
return x - y
@app.task
def task3(x, y):
print("任务task3函数正在执行....")
time.sleep(20)
return x * y
2、定义一个celeryconfig.py文件,用于配置一些路由、队列等信息。
-
在
task_queues
中定义队列信息,在task_routes
中设置任务对应的执行队列,(在有些版本参数可能是CELERY_QUEUES和CELERY_ROUTES) -
例如下面指定了
queuetasks.task1
这个方法由app_task1
这个队列执行,queuetasks.task2
由app_task2
队列执行
result_backend = 'redis://127.0.0.1:6379/0'
broker_url = 'redis://127.0.0.1:6379/0'
from kombu import Exchange, Queue
#定义队列
task_queues = (
#Queue('default', exchange=Exchange('default'), routing_key='default'),
Queue('app_task1', exchange=Exchange('app_task1'), routing_key='app_task1'),
Queue('app_task2', exchange=Exchange('app_task2'), routing_key='app_task2'),
)
#指定了`queuetasks.task1`这个方法由`app_task1`这个队列执行,`queuetasks.task2`由`app_task2`队列执行
task_routes = {
'queuetasks.task1': {'queue': 'app_task1', 'routing_key': 'app_task1'},
'queuetasks.task2': {'queue': 'app_task2', 'routing_key': 'app_task2'}
}
3、定义queuetrigger.py执行前面定义的三个函数任务
#queuetrigger.py
from queuetasks import task1,task2,task3
if __name__ == '__main__':
result1 = task1.delay(40, 20)
print(result1.id)
result2 = task2.delay(40, 20)
print(result2.id)
result3 = task3.delay(40, 20)
print(result3.id)
4、在启动worker时指定该worker执行哪一个queue中的任务。如果没有加-Q参数,启动的worker会监听task_queues 中配置的所有队里中的任务,但是不会监听默认的celery队列,所以默认的celery队列还是需要独立起的。
如下前两个worker,分别执行app_task1和app_task2队列中的任务,第三个执行celery默认队列中的任务。
celery -A queuetasks worker -l info -Q app_task1
celery -A queuetasks worker -l info -Q app_task2
celery -A queuetasks worker -l info -Q celery
5、执行queuetrigger.py中的任务,多执行几次观察
我这里执行了四次,每次都能返回taskid
查看启动的页面,有执行任务
task1在app_task1队列中执行
task2在app_task2队列中执行
task3在默认的celery队列中执行
注意:如果没有启动celery的woker就直接执行task函数,也会可以返回taskid,celery会将任务先存放在队列中,等worker一启动就开始消费。
四、Django中使用Celery
在celerydemo这个django项目中创建一个新的app djangocelerydemo
python manage.py startapp djangocelerydemo
1、在根项目中新建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', 'celerydemo.settings')
app = Celery('celery_task')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
# 可以将celery的配置信息放在django的setting.py文件中,要以CELERY_为开头
app.config_from_object('django.conf:settings', namespace='CELERY')
# Load task modules from all registered Django app configs.
#自动发现django应用里的celery任务
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
2、在setting.py中设置配置信息
#for celery
CELERY_BROKER_URL = 'redis://:password@127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://:password@127.0.0.1:6379/0'
#默认celery的时区为UTC,如果要在django项目中将celery定时任务配置为根据本地时区触发,则需要修改
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = False
3、在根项目/__init__.py
文件中增加如下内容,确保django启动的时候这个app能够被加载到
from __future__ import absolute_import
# 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']
4、在djangocelerydemo应用中创建tasks.py模块,注意tasks.py必须建在各app的根目录下,且只能叫tasks.py,不能随意命名。
- 如果使用了多个装饰器那么需要task装饰器在最后即在最上面,一般情况使用的是从celeryapp中引入的app作为的装饰器:app.task()
- 如果是django那种在app中定义的task则需要使用@shared_task,由shared_task装饰的任务可以被任何app调用
import time
from celery import shared_task
# 加上app对象的task装饰器
# 此函数为任务函数
@shared_task()
def my_task():
print("任务开始执行....")
time.sleep(5)
print("任务执行结束....")
5、在views.py模块中创建视图index,引用使用这个tasks异步处理
from django.http import HttpResponse
from djangocelerydemo.tasks import my_task
def index(request):
# 将my_task任务加入到celery队列中
# 如果my_task函数有参数,可通过delay()传递
# 例如 my_task(a, b), my_task.delay(10, 20)
my_task.delay()
return HttpResponse("<h1>服务器返回响应内容!</h1>")
6、在celeydemo/urls.py配置视图路由:
from djangocelerydemo.views import index
from django.conf.urls import re_path
urlpatterns = [
# url(r'^admin/', admin.site.urls),
re_path(r'^celerytest', index),
]
7、创建worker等待处理celery队列中任务, 在最外层目录终端执行命令,这里-A后面跟的项目名
#如果Ansible作为celery任务时,Celery执行Ansible的task任务时没有结果,需要export
export PYTHONOPTIMIZE=1
celery -A celerydemo worker --loglevel=info
8、启动django测试服务器
9、访问路由进行测试
10、后台启动多个worker,用下面的命令可以在后台启动,
celery multi start w1 -A celerydemo --loglevel=info
celery multi start w2 -A celerydemo --loglevel=info
celery multi start w3 -A celerydemo --loglevel=info
如果要停止可以将start改为stop
五、Celery定时任务
1、新建一个scheduletasks.py文件
from celery import Celery
from celery.schedules import crontab
app = Celery('tasks', broker='redis://127.0.0.1:6379/0', backend='redis://127.0.0.1:6379/0')
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
# Calls test('hello') every 10 seconds.
sender.add_periodic_task(10.0, test.s('hello'), name='add every 10')
# Calls test('world') every 30 seconds
sender.add_periodic_task(30.0, test.s('world'), expires=10)
# Executes every Monday morning at 7:30 a.m.
sender.add_periodic_task(
crontab(hour=7, minute=30, day_of_week=1),
test.s('Happy Mondays!'),
)
@app.task
def test(arg):
print(arg)
注意:如果只想在django中简单使用定时任务,可以把定时任务的配置setup_periodic_tasks和执行函数放在主项目的celery.py中,然后在setup_periodic_tasks中引用执行函数,最后再启动worker和调度器beat进程。如果执行函数没有放一起,启动后虽然定时任务能执行,但异步任务执行不了。如果是要复杂一下的界面化,可以看下面的django-celery-beat一节。
2、启动worker和任务调度器beat进程
启动celery worker进程
celery -A scheduletasks worker --loglevel=info
启动定时任务Celery Beat进程
celery -A scheduletasks beat --loglevel=info
可以看到两个进程中都有输出任务执行的信息
3、上面是通过调用函数添加定时任务,也可以像写配置文件 一样的形式添加, 下面是每30s执行的任务
用@app.on_after_configure.connect装饰器,是把计划写死在一个函数里了。似乎无法动态添加新任务。不过好处是结构比较清晰。
而后一种方法,只要更新一下 app.conf.beat_schedule 这个字典里的配置信息,然后重启Beat就能生效了。
app.conf.beat_schedule = {
'add-every-30-seconds': {
'task': 'tasks.add',
'schedule': 30.0,
'args': (16, 16)
},
}
app.conf.timezone = 'UTC'
4、更复杂的定时配置
上面的定时任务比较简单,只是每多少s执行一个任务,但如果你想要每周一三五的早上8点给你发邮件怎么办呢?哈,其实也简单,用crontab功能,跟linux自带的crontab功能是一样的,可以个性化定制任务执行时间
下面这条意思是每周1的早上7.30执行tasks.add任务
from celery.schedules import crontab
app.conf.beat_schedule = {
# Executes every Monday morning at 7:30 a.m.
'add-every-monday-morning': {
'task': 'tasks.add',
'schedule': crontab(hour=7, minute=30, day_of_week=1),
'args': (16, 16),
},
}
六、Django结合Celery定时任务(使用django-celery-beat及配套界面)
参考django-celery-beat的使用
1、安装django-celery-beat
pip install django-celery-beat
2、将django_celery_beat模块添加到INSTALLED_APPSDjango项目中settings.py
INSTALLED_APPS = [
...
'django_celery_beat',
]
3、应用Django数据库迁移,以便创建必要的表:
python manage.py migrate
迁移之后生产几个表:
django_celery_beat.models.PeriodicTask # 此模型定义要运行的单个周期性任务。
django_celery_beat.models.IntervalSchedule # 以特定间隔(例如,每5秒)运行的计划。
django_celery_beat.models.CrontabSchedule
# 与像在cron项领域的时间表 分钟小时日的一周 DAY_OF_MONTH month_of_year
django_celery_beat.models.PeriodicTasks # 此模型仅用作索引以跟踪计划何时更改
django_celery_beat_solarschedule # 根据太阳升起降落定制任务
django_celery_beat_clockedschedule # 此模型存放已经关闭的任务
4、分别启动worker与beat
celery -A celerydemo worker -l info
celery -A celerydemo beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
注意:这里如果是分布式部署celery,有多台机器的话,worker每台机器都起,beat只需要在一台机器上起,不然定时任务会执行多次
如果django中要后台启动或关闭celery,使用如下命令
启动:celery multi start w1 -A celerydemo -l info --logfile = celerylog.log --pidfile = celerypid.pid
停止:celery multi stop w1 -A celerydemo -l info
重启:celery multi restart w1 -A celerydemo -l info
5、创建一个admin用户,然后登陆
python manage.py createsuperuser
可以看到有几张表
6、新建一个定时任务测试
我这里选了前面的my_task这个任务,5s执行一次,
然后查看worker输出,有执行
beat调度器也有输出