文章目录
celery
转载地址:https://www.cnblogs.com/pyedu/p/12461819.html
视频讲解:https://www.bilibili.com/video/BV1Pa4y1Y7QN?p=1
一、什么是celery
1、celery是什么:
-
celery是一个分布式队列的管理工具
-
celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调用
-
-
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker),任务执行结果存储(task result store)
2、使用场景
- celery是一个强大的分布式任务队列的异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)
- 异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
- 定时任务:定时执行某件事情,比如每天数据统计
3、Celery的优点
- Simple简单:因为Celery作为一个对并发技术做了封装的框架,给别人使用和维护都不能做的很复杂,别人也不用太去了解底层的东西。
- Highly Available高可用:在分布式系统下,一定要做到高可用,保证分布式服务器中的程序对外供给的时间有一个最大的利用率,还要做到稳定
- Fast快速:单个Celery进程每分钟可以处理百万级的任务
- Flexible灵活:Celery几乎每个部分都可以扩展使用,自定义池实现、序列化、压缩方案、日志记录、调度器、消费者、生产者、broker传输等等。由于语法上调用比较简单,比如要将消息中间件由RabbitMQ换成Redis,则只需要去配置文件修改一下对应的IP和端口即可,其他配置都不太需要更改
4、Celery的安装
-
pip install -U Celery
-
sudo easy_install Celery
二、Celery执行异步任务
1、创建异步任务执行文件(消费者)
-
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
-
2、创建生产者文件
生产者文件才是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)
-
3、创建result文件去获取任务函数的返回值,从而查看任务执行结果
-
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('任务已经开始被执行')
-
4、多目录结构
-
个人理解:多目录结构其实就是执行多个任务的意思,执行多个任务自然需要把对应的逻辑代码进行解耦,下面所说的单目录结构其实没有这种说法,只是个人为了与多目录结构多比较才这样说的,大概意思是把所有逻辑代码文件都放在一个目录下,这样耦合程度高,不好做代码的维护。
-
多目录结构相对于单目录结构的优势在于解耦程度高,代码发生错误时好维护
-
多目录结构中的代码和单目录结构是一样的,注意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('任务已经开始被执行')
-
5、异步任务和定时任务
-
异步任务是在同一时刻以异步的方式去执行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)
-
6、多目录结构下celery执行
-
单目录结构下的执行步骤应该是:
- 1、创建对应的包,里面编写celery的配置文件,celery任务的文件,使用
celery worker -A proj -l info -P eventlet
命令去启动消费者文件,去监听消息队列。 - 2、在另外一个目录下创建生产者文件(主函数),生产者文件作用是将celery任务放到消息队列里面,然后执行文件监听到消息队列有celery任务的话则就会去执行消息队列中的celery任务
- 1、创建对应的包,里面编写celery的配置文件,celery任务的文件,使用
-
多目录结构下定时任务的执行方式不同,不是通过生产者去将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
注意点
- 1、celery不支持python3.7
- 2、celery4.x已经不支持windows了,celery==3.1.25还是支持的
- 3、常见错误:https://blog.youkuaiyun.com/cn_1937/article/details/91992075
概念介绍
-
Django需要引入celery包,django充当生产者,celery包充当消费者
-
django职责:
- 只需要在views函数中去调用celery包封装好的函数
-
celery包职责:
- 1、编写配置文件:指定消息队列和任务结果存储的数据库
- 2、编写主函数:实例化celery对象并给对象添加一些属性,例如指定加载的配置、执行加载的celery任务有哪些、
- 3、编写celery任务函数
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDLoyLjJ-1596387123648)(C:\Users\dujun\AppData\Roaming\Typora\typora-user-images\image-20200803003440969.png)]
总体流程
- 1、定义好celery包
- 2、django中写好调用celery的代码
- 3、消费者开启监听:使用命令行启动,建议切换目录到mycelery根目录下启动,
celery -A mycelery.main worker --loglevel=info
- 4、开启django项目
- 5、去访问对应的url触发异步任务
文件对应的代码
-
配置文件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')
-