转载地址:https://www.cnblogs.com/pyedu/p/12461819.html
视频讲解:https://www.bilibili.com/video/BV1Pa4y1Y7QN?p=1
celery是一个分布式队列的管理工具
celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调用
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker),任务执行结果存储(task result store)
pip install -U Celery
sudo easy_install Celery
celery_task.py
import celery
import time
# backend(后台),就是task result store
backend = 'redis://127.0.0.1:6379/1'
# broker 消息中间件
broker = 'redis://127.0.0.1:6379/2'
# 实例化一个celery对象,test只是个名字,可以任意改
cel = celery.Celery('test', backend=backend, broker=broker)
@cel.task # 使用celery中的装饰器将函数装饰成celery任务(可能是异步任务,可能是定时任务)
def send_mail(name):
print('向%s 发送邮件...'%name)
time.sleep(5)
print('向%s 发送邮件完成'%name)
return 'ok'
@cel.task
def send_msg(name):
print('向%s 发送短信...' % name)
time.sleep(5)
print('向%s 发送短信完成' % name)
return 'ok'
在命令行中使用命令去启动异步任务执行文件,使worker去监听消息队列,若消息队列中有celery任务,则会去执行celery任务
celery worker -A clery_task -l info
生产者文件才是celery框架的主文件,当运行对应的文件时,生产者会将celery任务放入消息队列中。
produce_task.py
from celery_task import *
result = send_mail.delay('hhh')#这里的result的值并不是send_mail函数的返回值ok,而是该函数的对象,返回值ok存储在backend中
print(result.id)
result2 = send_msg.delay('ggg')
print(result2.id)
result.py
from celery.result import AsyncResult
from celery_task import cel
#id为函数对象中的id,要查询哪一个函数的结果就输入哪一个函数的对象对应的id
async_result = AsyncResult(id='531248c5-75ac-46af-8f01-889da554d7d6', app=cel)
if async_result.successful():
result = async_result.get()
print(result)
# result.forget() # 将结果删除
elif async_result.failed():
print('执行失败')
elif async_result.status == 'PENDING':
print('任务等待中被执行')
elif async_result.status == 'RETRY':
print('任务异常后正在重启')
elif async_result.status == 'STARTED':
print('任务已经开始被执行')
个人理解:多目录结构其实就是执行多个任务的意思,执行多个任务自然需要把对应的逻辑代码进行解耦,下面所说的单目录结构其实没有这种说法,只是个人为了与多目录结构多比较才这样说的,大概意思是把所有逻辑代码文件都放在一个目录下,这样耦合程度高,不好做代码的维护。
多目录结构相对于单目录结构的优势在于解耦程度高,代码发生错误时好维护
多目录结构中的代码和单目录结构是一样的,注意celery_tasks是一个包结构
celery.py:编写一些配置选项
import celery
backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/2'
cel = celery.Celery('test',
backend=backend,
broker=broker,
include=['celery_tasks.task01','celery_tasks.task02'])
# 时区
# cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
# cel.conf.enable_utc = False
task01.py:任务1
from celery_tasks.celery import cel
import time
@cel.task
def send_mail(name):
print('发送邮件 %s' % name)
time.sleep(5)
return '邮件发送成功'
task02.py:任务2
from celery_tasks.celery import cel
import time
@cel.task
def send_msg(name):
print('发送短信 %s'%name)
time.sleep(5)
return '短信发送成功'
produce_task.py:主函数(生产者函数)
from celery_tasks.task01 import send_mail
from celery_tasks.task02 import send_msg
result = send_mail.delay('hhh')
print(result.id)
result2 = send_msg.delay('ggg')
print(result2.id)
check_result.py:检查结果的函数
from celery.result import AsyncResult
from celery_tasks.celery import cel
async_result = AsyncResult(id='4e4ad94a-5dc2-4939-b461-830ed168a0a0', app=cel)
if async_result.successful():
result = async_result.get()
print(result)
# result.forget() # 将结果删除
# async_result.revoke(terminate=True) # 无论现在是什么时候,都要终止
# async_result.revoke(terminate=False) # 如果现在还没有开始执行呢,那么就可以种植
elif async_result.failed():
print('执行失败')
elif async_result.status == 'PENDING':
print('任务等待中被执行')
elif async_result.status == 'RETRY':
print('任务异常后正在重启')
elif async_result.status == 'STARTED':
print('任务已经开始被执行')
异步任务是在同一时刻以异步的方式去执行celery任务,而定时任务是指定好了具体时间或者指定延迟去执行celery任务
异步任务使用delay
,定时任务使用apply_async
,两者函数区别在于,apply_async
中有eta参数,可以将时间对象传入,从而达到定时执行任务
单目录结构都是写在生产者函数中
# 异步任务
from celery_tasks.task01 import send_mail
from celery_tasks.task02 import send_msg
result = send_mail.delay('hhh')
print(result.id)
result2 = send_msg.delay('ggg')
print(result2.id)
# 定时任务:相对于异步任务来说只是添加了一个定时的时间和用apply_async替换delay方法
from datetime import datetime, timedelta
from celery_tasks.task01 import send_mail
from celery_tasks.task02 import send_msg
# 方式一:指定具体的时间去定时执行celery任务
# dt为指定的具体时间对象
dt = datetime(year=2020, month=8, day=2, hour=15, minute=8,second=0)
# 默认使用utc时间
dt_utc = dt.utcfromtimestamp(dt.timestamp())
result = send_mail.apply_async(args=['Bob'], eta=dt_utc)
print(result.id)
# 方式二:指定一个delay时间,使celery任务延迟多久才去执行
current_time = datetime.now()
ct_utc = current_time.utcfromtimestamp(current_time.timestamp())
time_delay = timedelta(seconds=10)
task_time = ct_utc + time_delay
result2 = send_msg.apply_async(args=['Michael'], eta=task_time)
print(result2.id)
单目录结构下的执行步骤应该是:
celery worker -A proj -l info -P eventlet
命令去启动消费者文件,去监听消息队列。多目录结构下定时任务的执行方式不同,不是通过生产者去将celery任务插入到消息队列中,而是通过使用命令celery beat -A proj
去将celery任务定时的插入到消息队列中,生产者不参与,定时任务或异步任务的调度代码都会在消费者的配置文件中去编写
多目录结构中的celery.py文件:
import crontab
import celery
from datetime import timedelta, datetime
backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/2'
cel = celery.Celery('celery_test',
backend=backend,
broker=broker,
include=['celery_tasks.task01','celery_tasks.task02'])
# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False
# 使用celery beat -A proj 命令执行定义好的定时任务
cel.conf.beat_schedule = {
# 名字随意命名
'add-every-5-seconds':{
# 执行task01下的send_email函数
'task': 'celery_tasks.task01.send_mail',
# 每隔6秒执行一次
# 'schedule': 6.0,
# 每隔6分钟
# 'schedule': crontab(minute="*/6"),
'schedule':timedelta(seconds=6),
'args': ('Bob',)
},
'add-every-special-date':{
'task':'celery_tasks.task02.send_msg',
# 'schedule':crontab(minute=42,hours=8,day_of_month=11, month_of_year=11),
'schedule':timedelta(seconds=7),
'args':('Michael',)
}
}
顺序(看个人):先开启监听,后将任务放入到消息队列,所以是先celery worker后celery beat
注意点:
如果关闭了监听,beat命令还开着的话,还是会继续将celery任务不断的插入到broker中(这里使用redis),即使后面关闭了beat,其实redis中还是存放了celery任务。会导致下次worker开启监听的时候,会执行redis上一次beat遗留下来的celery任务,我们可以手动删除redis中的任务,可以通过pycharm去连接redis并删除对应的key
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=2)
# 删除beat历史遗留下来的celery任务
r.delete('celery')
# 查看还有没有历史遗留任务
for i in r.lrange('celery',0,-1):
print(i)
Django需要引入celery包,django充当生产者,celery包充当消费者
django职责:
celery包职责:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDLoyLjJ-1596387123648)(C:\Users\dujun\AppData\Roaming\Typora\typora-user-images\image-20200803003440969.png)]
celery -A mycelery.main worker --loglevel=info
配置文件config.py:
broker_url = 'redis://127.0.0.1:6379/15'
result_backend = 'redis://127.0.0.1:6379/14'
主程序main.py
# 主程序
import os
from celery import Celery
# 创建celery实例对象
app = Celery("sms")
# 把celery和django进行组合,识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celeryPros.settings.dev')
# 通过app对象加载配置
app.config_from_object("mycelery.config")
# 加载任务
# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
# app.autodiscover_tasks(["任务1","任务2"])
app.autodiscover_tasks(["mycelery.sms",])
# 启动Celery的命令
# 强烈建议切换目录到mycelery根目录下启动
# celery -A mycelery.main worker --loglevel=info
任务文件tasks.py
# celery的任务必须写在名为tasks.py的文件中,因为主程序中加载celery任务的函数会自动识别名为tasks.py的文件
from mycelery.main import app
import time
import logging
log = logging.getLogger('django')
@app.task
def send_sms(name): # name表示设置任务的名称,如果不填写,则默认为函数名
print('邮件内容:%s'%name)
time.sleep(5)
return 'send_sms:ok'
@app.task
def send_sms2(name):
print('邮件2内容:%s'%name)
time.sleep(5)
return 'send_sms2:ok'
Django视图调用views.py
from django.shortcuts import render, HttpResponse
from mycelery.sms.tasks import send_sms, send_sms2
# Create your views here.
from datetime import datetime, timedelta
def test(request):
# 异步方式
result = send_sms.delay('hi Bob')
result2 = send_sms2.delay('hi Mechael')
print(result.id)
print(result2.id)
# send_sms.delay() 如果调用的任务函数没有参数,则不需要填写任何内容
################################# 定时任务
# ctime = datetime.now()
# # 默认用utc时间
# utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
# time_delay = timedelta(seconds=10)
# task_time = utc_ctime + time_delay
# result = send_sms.apply_async(["hi boy", ], eta=task_time)
# print(result.id)
return HttpResponse('OK')