Django框架学习-Celery的使用
celery用户文档:https://docs.celeryq.dev/en/v5.3.1/userguide/index.html
1、Celery的提出
用户需要在网站填写注册信息,发给用户一封注册激活邮件到邮箱,如果由于各种原因,这封邮件发送所需时间较长,那么客户端将会等待很久,造成不好的用户体验。——> 将耗时任务放到后台异步执行,从而不影响用户其他操作。
功能:
- 异步执行任务(发送邮件、调用第三方接口、批量处理文件)
- 定时处理任务(清除缓存、备份数据库)
工作原理:
基于分布式消息传递的作业队列,
通常使用 broker(中间人)来协调 client(任务的发出者)和 worker(任务的处理者),clients发出消息到队列中,broker将队列中的信息派发给worker来处理。celery本身不提供消息服务,它支持的消息服务broker有RocketMQ和Redis。
2、Celery的执行流程
celery的运行由三部分组成,消息队列(message broker),任务执行单元(worker)和任务执行结果存储组成。
3、文件配置
目录结构
1 myproject/ # 服务端项目根目录 2 └── myproject/ # 主应用目录 3 ├── apps/ # 子应用存储目录 4 ├ └── users/ # django的子应用 5 ├ └── tasks.py # [新增]分散在各个子应用下的异步任务模块 6 ├── settings/ # [修改]django的配置文件存储目录[celery的配置信息填写在django配置中即可] 7 ├── __init__.py # [修改]设置当前包目录下允许外界调用celery应用实例对象 8 └── celery.py # [新增]celery入口程序,相当于上一种用法的main.py
celery.py文件,主目录下创建celery入口程序
1 import os 2 from celery import Celery 3 4 # 为 celery 程序设置默认的 Django 配置 5 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 6 7 app = Celery('myproject') 8 9 # 表示从 Django 的配置中加载 celery 的配置,namespace='CELERY' 表示 celery 的配置必须是以 'CELERY' 为前缀 10 app.config_from_object('django.conf:settings', namespace='CELERY') 11 12 app.conf.update( 13 task_ignore_result=True 14 ) 15 16 # 自动根据配置查找django的所有子应用下的tasks任务文件 17 app.autodiscover_tasks()
setting.py,新增celery相关配置信息。
1 # Celery异步任务队列框架的配置项[注意:django的配置项必须大写,所以这里的所有配置项必须全部大写] 2 # 任务队列 3 CELERY_BROKER_URL = 'redis://:123456@127.0.0.1:6379/14' 4 # 结果队列 5 CELERY_RESULT_BACKEND = 'redis://:123456@127.0.0.1:6379/15' 6 # 时区,与django的时区同步 7 CELERY_TIMEZONE = TIME_ZONE 8 # 防止死锁 9 CELERY_FORCE_EXECV = True 10 # 设置并发的worker数量 11 CELERYD_CONCURRENCY = 200 12 # 设置失败允许重试[这个慎用,如果失败任务无法再次执行成功,会产生指数级别的失败记录] 13 CELERY_ACKS_LATE = True 14 # 每个worker工作进程最多执行500个任务被销毁,可以防止内存泄漏,500是举例,根据自己的服务器的性能可以调整数值 15 CELERYD_MAX_TASKS_PER_CHILD = 500 16 # 单个任务的最大运行时间,超时会被杀死[慎用,有大文件操作、长时间上传、下载任务时,需要关闭这个选项,或者设置更长时间] 17 CELERYD_TIME_LIMIT = 10 * 60 18 # 任务发出后,经过一段时间还未收到acknowledge, 就将任务重新交给其他worker执行 19 CELERY_DISABLE_RATE_LIMITS = True 20 # celery的任务结果内容格式 21 CELERY_ACCEPT_CONTENT = ['json', 'pickle'] 22 23 # 之前定时任务(定时一次调用),使用了apply_async({}, countdown=30); 24 # 设置定时任务(定时多次调用)的调用列表,需要单独运行SCHEDULE命令才能让celery执行定时任务:celery -A mycelery.main beat,当然worker还是要启动的 25 # https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html 26 from celery.schedules import crontab 27 CELERY_BEAT_SCHEDULE = { 28 "user-add": { # 定时任务的注册标记符[必须唯一的] 29 "task": "add", # 定时任务的任务名称 30 "schedule": 10, # 定时任务的调用时间,10表示每隔10秒调用一次add任务 31 # "schedule": crontab(hour=7, minute=30, day_of_week=1),, # 定时任务的调用时间,每周一早上7点30分调用一次add任务 32 } 33 }
为了确保 celery 的 app 在 Django 运行的时候被加载,需要在 myproject/__init__.py 中引入 celery_app。
1 from .celery import app as celery_app 2 3 __all__ = ('celery_app',)
redis和celery配置完成后,就可以编写任务了。
3、task定义
task.py文件可以位于myproject目录下,也可位于各个app的目录下。专属于某个Celery实例化项目的task可以使用@app.task装饰器定义,各个app目录下可以复用的task建议使用@shared_task定义。
users/tasks.py
1 from celery import shared_task 2 from ronglianyunapi import send_sms as sms 3 import logging 4 logger = logging.getLogger("django") 5 6 @shared_task(name="send_sms") 7 def send_sms(tid, mobile, datas): 8 """异步发送短信""" 9 try: 10 return sms(tid, mobile, datas) 11 except Exception as e: 12 logger.error(f"手机号:{mobile},发送短信失败错误: {e}") 13 14 @shared_task(name="send_sms1") 15 def send_sms1(): 16 print("send_sms1执行了!!!")
users/views.py
1 import random 2 from django_redis import get_redis_connection 3 from django.conf import settings 4 # from ronglianyunapi import send_sms 5 # from mycelery.sms.tasks import send_sms 6 from .tasks import send_sms 7 8 """ 9 /users/sms/(?P<mobile>1[3-9]\d{9}) 10 """ 11 class SMSAPIView(APIView): 12 """ 13 SMS短信接口视图 14 """ 15 def get(self, request, mobile): 16 """发送短信验证码""" 17 redis = get_redis_connection("sms_code") 18 # 判断手机短信是否处于发送冷却中[60秒只能发送一条] 19 interval = redis.ttl(f"interval_{mobile}") # 通过ttl方法可以获取保存在redis中的变量的剩余有效期 20 if interval != -2: 21 return Response({"errmsg": f"短信发送过于频繁,请{interval}秒后再次点击获取!", "interval": interval},status=status.HTTP_400_BAD_REQUEST) 22 23 # 基于随机数生成短信验证码 24 # code = "%06d" % random.randint(0, 999999) 25 code = f"{random.randint(0, 999999):06d}" 26 # 获取短信有效期的时间 27 time = settings.RONGLIANYUN.get("sms_expire") 28 # 短信发送间隔时间 29 sms_interval = settings.RONGLIANYUN["sms_interval"] 30 # 调用第三方sdk发送短信 31 # send_sms(settings.RONGLIANYUN.get("reg_tid"), mobile, datas=(code, time // 60)) 32 # 异步发送短信 33 send_sms.delay(settings.RONGLIANYUN.get("reg_tid"), mobile, datas=(code, time // 60)) 34 35 # 记录code到redis中,并以time作为有效期 36 # 使用redis提供的管道对象pipeline来优化redis的写入操作[添加/修改/删除] 37 pipe = redis.pipeline() 38 pipe.multi() # 开启事务 39 pipe.setex(f"sms_{mobile}", time, code) 40 pipe.setex(f"interval_{mobile}", sms_interval, "_") 41 pipe.execute() # 提交事务,同时把暂存在pipeline的数据一次性提交给redis 42 43 return Response({"errmsg": "OK"}, status=status.HTTP_200_OK)
celery中异步任务发布的2个方法的参数如下:
1 异步任务名.delay(*arg, **kwargs) 2 异步任务名.apply_async((arg,), {'kwarg': value}, countdown=60, expires=120)