异步方案Celery机制
-
场景
-
发送短信验证码
我们的代码是自上而下同步执行的,发送短信是耗时的操作。如果短信被阻塞住,用户响应将会延迟,响应延迟会造成用户界面的倒计时延迟。异步发送短信,发送短信和响应分开执行,将发送短信从主业务中解耦出来。
-
发送邮箱链接激活邮箱
发送邮箱验证邮件是耗时的操作,所以需要异步发送邮件,使用Celery实现异步任务。
-
并发下单问题
在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常,将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。
-
总结:只要有可能发生耗时延迟的情况都可以考虑异步方案celery。
-
-
生产者消费者设计模式介绍
-
为了将发送短信从主业务中解耦出来,我们引入生产者消费者设计模式。
-
它是最常用的解耦方式之一,寻找**中间人(broker)**搭桥,保证两个业务没有直接关联。
总结: -
生产者生成消息,缓存到消息队列中,消费者读取消息队列中的消息并执行。
-
由服务器生成发送短信消息,缓存到消息队列中,消费者读取消息队列中的发送短信消息并执行。
-
-
Celery介绍和使用
-
场景
- 消费者取到消息之后,要消费掉(执行任务),需要我们去实现。
- 任务可能出现高并发的情况,需要补充多任务的方式执行。
- 耗时任务很多种,每种耗时任务编写的生产者和消费者代码有重复。
- 取到的消息什么时候执行,以什么样的方式执行。
-
解决办法
- 实际开发中,我们可以借助成熟的工具
Celery
来完成。 - 有了
Celery
,我们在使用生产者消费者模式时,只需要关注任务本身,极大的简化了程序员的开发流程。
- 实际开发中,我们可以借助成熟的工具
-
Celery介绍
- 一个简单、灵活且可靠、处理大量消息的分布式系统,可以在一台或者多台机器上运行。
- 单个 Celery 进程每分钟可处理数以百万计的任务。
- 通过消息进行通信,使用消息队列(broker)在客户端和消费者之间进行协调。
-
在虚拟环境中安装Celery
pip install -U Celery
-
创建Celery实例并加载配置
步骤:
-
定义Celery包:
在项目工程目录下创建python包celery_tasks
在celery_tasks包下创建main.py入口文件
在celery_tasks包下创建config.py配置文件
-
加载Celery配置
在config.py配置文件中指定消息队列的位置
# 指定消息队列的位置 broker_url = "redis://127.0.0.1/10"
-
创建Celery实例:
在main.py入口文件中创建实例
# celery启动文件 from celery import Celery # 创建celery实例,名称可以不传 celery_app = Celery('名称') # 加载celery配置 celery_app.config_from_object('celery_tasks.config')
-
-
定义Celery任务,例:发送短信任务
步骤:先在Celery包创建任务包,在任务包中创建tasks.py(名字固定)文件
-
在tasks.py中定义任务
from celery import Task from celery_tasks.sms.yuntongxun.ccp_sms import CCP from celery_tasks.sms import constants from celery_tasks.main import celery_app # 判断是否执行任务成功 class MyTask(Task): def on_success(self, retval, task_id, args, kwargs): # 注意自己定义的任务 print('调用异步任务完成') print('retval', retval) print('task_id', task_id) return super(MyTask, self).on_success(retval, task_id, args, kwargs) def on_failure(self, exc, task_id, args, kwargs, einfo): print('调用异步任务失败') print('retval', exc) print('task_id', task_id) return super(MyTask, self).on_failure(exc, task_id, args, kwargs) # 使用装饰器装饰异步任务,保证celery识别任务 @celery_app.task(name='send_sms_code') def send_sms_code(mobile, sms_code): """ 发送短信验证码的异步任务 :param mobile: 手机号 :param sms_code: 短信验证码 :return: 成功:0,失败:-1 """ # 发送短信验证码 send_ret = CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], constants.SEND_SMS_TEMPLATE_ID) return send_ret
-
注册任务
# celery启动文件 from celery import Celery # 创建celery实例 celery_app = Celery('名称') # 加载celery配置 celery_app.config_from_object('celery_tasks.config') # 自动注册celery任务 celery_app.autodiscover_tasks(['celery_tasks.sms']) # 注册多个任务只要在后面添加任务即可 # celery_app.autodiscover_tasks(['celery_tasks.sms', 'celery_tasks.email'])
-
-
启动Celery服务
# 进入项目工程所在目录 celery -A celery_tasks.main worker -l info # -A指对应的应用程序, 其参数是项目中 Celery实例的位置。 # worker指这里要启动的worker。 # -l指日志等级,比如info等级。
-
在要发送短信的位置调用发短信任务
# Celery异步发送短信验证码 ccp_send_sms_code.delay(mobile, sms_code)
-
-
celery错误重试机制
# bind:保证task对象会作为第一个参数自动传入 # name:异步任务别名 # retry_backoff:异常自动重试的时间间隔 第n次(retry_backoff×2^(n-1))s # max_retries:异常自动重试次数的上限 import logging from django.conf import settings from django.core.mail import send_mail from celery_tasks.main import celery_app logger = logging.getLogger('django') @celery_app.task(bind=True, name='send_verify_email', retry_backoff=3) def send_verify_email(self, to_email, verify_url): """ 发送验证邮箱邮件 self:当前任务对象 :param to_email: 收件人邮箱 :param verify_url: 验证链接 :return: None """ subject = "美多商城邮箱验证" html_message = '<p>尊敬的用户您好!</p>' \ '<p>感谢您使用美多商城。</p>' \ '<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \ '<p><a href="%s">%s<a></p>' % (to_email, verify_url, verify_url) try: send_mail(subject, "", settings.EMAIL_FROM, [to_email], html_message=html_message) except Exception as e: logger.error(e) # 有异常自动重试三次 raise self.retry(exc=e, max_retries=3)
-
celery worker的工作模式(增加celery的并发量)
-
默认是进程池方式,进程数以当前机器的CPU核数为参考,每个CPU开四个进程。
-
如何自己指定进程数:celery worker -A proj --concurrency=4 or celery worker -A proj --c 20
-
如何改变进程池方式为协程方式:celery worker -A proj --concurrency=1000 -P eventlet -c 1000
# 安装eventlet模块 $ pip install eventlet # 启用 Eventlet 池 $ celery -A celery_tasks.main worker -l info -P eventlet -c 1000
-