一、Celery介绍及基本使用
Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery, 举几个实例场景中可用的例子:
- 你想对100台机器执行一条批量命令,可能会花很长时间 ,但你不想让你的程序等着结果返回,而是给你返回 一个任务ID,你过一段时间只需要拿着这个任务id就可以拿到任务执行结果, 在任务执行ing进行时,你可以继续做其它的事情。
- 你想做一个定时任务,比如每天检测一下你们所有客户的资料,如果发现今天 是客户的生日,就给他发个短信祝福
Celery 在执行任务时需要通过一个消息中间件来接收和发送任务消息,以及存储任务结果, 一般使用rabbitMQ or Redis,后面会讲
1.1 Celery有以下优点:
- 简单:一单熟悉了celery的工作流程后,配置和使用还是比较简单的
- 高可用:当任务执行失败或执行过程中发生连接中断,celery 会自动尝试重新执行任务
- 快速:一个单进程的celery每分钟可处理上百万个任务
- 灵活: 几乎celery的各个组件都可以被扩展及自定制
Celery基本工作流程图
1.2 Celery安装使用
Celery的默认broker是RabbitMQ, 仅需配置一行就可以
1 broker_url = 'amqp://guest:guest@localhost:5672//'
rabbitMQ 没装的话请装一下,安装看这里 http://docs.celeryproject.org/en/latest/getting-started/brokers/rabbitmq.html#id3
使用Redis做broker也可以(本文主讲Ubuntu下的celery)
linux下安装celery:
1 pip3 install celery
使用redis做broker需安装redis模块,另服务器需有安装redis软件包:
1 $sudo apt-get install redis-server # linux安装redis-server软件包 2 3 4 $pip3 install credis # 安装redis模块,执行celery worker工作模式时,如未安装会提示安装redis
安装好之后就可以开始使用celery啦
1、创建一个celery application 用来定义你的任务列表 :
在Ubuntu系统中vim celery_test.py
# 文件名:celery_test.py from celery import Celery # 安装celery # broker:工作的地方 # backend: 存放结果的位置 app = Celery('tasks', broker='redis://:nan123456@192.168.1.145/0', backend='redis://:nan123456@192.168.1.145/1') @app.task def add(x, y): print("running...", x, y) return x + y
2、启动Celery Worker来开始监听并执行任务
在celery_test.py当前目录下执行:
$ celery -A celery_test worker --loglevel=info $ celery -A celery_test worker -l info #简写
3、调用任务
另起终端,在celery_test.py当前目录进入python3环境执行:
>>> from celery_test.py import add >>> add.delay(4, 4) #结果:<AsyncResult: 4c079d93-fd5f-47f0-8b93-c77a0112eb4e> #此时worker终端会显示收到 一个任务
调用指令:
t = add.delay(4, 4) #执行该任务 t.get() #获取任务结果
注:当任务执行过程太久,可通过用指令:t.ready(),查看是否完成,如果完成返回True,否则返回False 。如果直接执行:t.get(),程序会被阻塞住,直到任务执行完成返回结果
二、在项目中使用Celery
可以把celery配置成一个应用
目录格式如下:
项目文件proj
/__init__
.py
/celery
.py
/tasks
.py
1、创建项目:mkdir proj
2、创建__init__.py文件:touch __init__.py
3、编辑celery.py文件:vim celery.py:
from __future__ import absolute_import, unicode_literals #引入绝对路径及编码方面的 #此时,引用python celery模块用:from celery #引用自定义celery.py文件用:from .celery from celery import Celery app = Celery('proj', #app名 broker='redis://:nan123456@192.168.1.145/0', backend='redis://:nan123456@192.168.1.145/1', include=['proj.tasks']) # 结果数据存放有效期,1个小时 app.conf.update( result_expires=3600, ) if __name__ == '__main__': app.start()
4、编辑tasks.py文件:vim tasks.py:
from __future__ import absolute_import, unicode_literals from .celery import app #从自定义celery文件引人app @app.task def add(x, y): return x + y @app.task def mul(x, y): return x * y @app.task def xsum(numbers): print('12345') return sum(numbers)
5、到proj上级目录中执行:
celery -A proj worker -l info #worker工作 #此时broker中还没有任务,worker相当于待命状态
6、另起一个终端,到proj上级目录中,进入python3环境执行:
$ python3 >>> from proj import tasks >>> t=tasks.mul.delay(3,6) >>> t.get() #文件目录: #proj: #init文件 #celery.py 配置及执行操作在此文件中 #tasks.py 任务都在此文件中
调用任务过程:
后台启动worker:
#后台执行: $ celery multi start w1 -A proj -l info #开启worker1监控并等待执行任务 $ celery multi start w2 -A proj -l info #开启worker2监控并等待执行任务 $ celery multi stop w1 -A proj -l info #停止/关闭worker1监控并执行任务 $ celery multi stopwait w1 -A proj -l info #等待任务执行完成时,停止/关闭worker1的任务
eg:
三、Celery定时任务
celery支持定时任务,设定好任务的执行时间,celery就会定时自动帮你执行, 这个定时任务模块叫celery beat
在上面proj项目中,再写入一个periodic_task.py文件,做定时任务
periodic_task.py:
from __future__ import absolute_import, unicode_literals #拒绝隐式导入,因自定义文件与系统celery模块同名冲突,需导入absolute_import,用于区分路径 from .celery import app #从proj中的celery文件引人app from celery.schedules import crontab #用于定时任务 @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 20 seconds sender.add_periodic_task(20.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) # add_periodic_task: 添加一条定时任务
启动Celery worker监听并等待执行任务:
celery -A proj worker -l info
启动任务调度器:
celery -A proj.periodic_task beat -l debug
任务调度器:
celery会单独启动一个进程来定时发起这些任务, 注意, 这里是发起任务,不是执行,这个进程只会不断的去检查你的任务计划, 每发现有任务需要执行了,就发起一个任务调用消息,交给celery worker去执行 。
上面是通过调用函数添加定时任务,也可以像写配置文件 一样的形式添加, 下面是每30s执行的任务
app.conf.beat_schedule = { 'add-every-30-seconds': { 'task': 'tasks.add', 'schedule': 30.0, 'args': (16, 16) }, } app.conf.timezone = 'UTC'
更多定时配置方式如下:
Example | Meaning |
crontab() |
Execute every minute. |
crontab(minute=0, hour=0) |
Execute daily at midnight. |
crontab(minute=0, hour='*/3') |
Execute every three hours: midnight, 3am, 6am, 9am, noon, 3pm, 6pm, 9pm. |
|
Same as previous. |
crontab(minute='*/15') |
Execute every 15 minutes. |
crontab(day_of_week='sunday') |
Execute every minute (!) at Sundays. |
|
Same as previous. |
|
Execute every ten minutes, but only between 3-4 am, 5-6 pm, and 10-11 pm on Thursdays or Fridays. |
crontab(minute=0,hour='*/2,*/3') |
Execute every even hour, and every hour divisible by three. This means: at every hour except: 1am, 5am, 7am, 11am, 1pm, 5pm, 7pm, 11pm |
crontab(minute=0, hour='*/5') |
Execute hour divisible by 5. This means that it is triggered at 3pm, not 5pm (since 3pm equals the 24-hour clock value of “15”, which is divisible by 5). |
crontab(minute=0, hour='*/3,8-17') |
Execute every hour divisible by 3, and every hour during office hours (8am-5pm). |
crontab(0, 0,day_of_month='2') |
Execute on the second day of every month. |
|
Execute on every even numbered day. |
|
Execute on the first and third weeks of the month. |
|
Execute on the eleventh of May every year. |
|
Execute on the first month of every quarter. |
四、 最佳实践之与django结合
Windows环境下,使用pycharm新建Django项目
项目:Celery_test
APP名称:app01
截图:
关于celery需求相关的操作:
1)在项目下Celery_test文件夹中创建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. #环境配置,使用的是Celery_test下的setting os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Celery_test.settings') #Celery_test:项目名 app = Celery('Celery_test') # 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. app.config_from_object('django.conf:settings', namespace='CELERY') # Load task modules from all registered Django app configs. app.autodiscover_tasks() #自动发现tasks任务 @app.task(bind=True) def debug_task(self): print('Request: {0!r}'.format(self.request))
2)在Celery_test文件夹中的init.py文件添加celery相关启动数据:
from __future__ import absolute_import, unicode_literals # This will make sure the app is always imported when # Django starts so that shared_task will use this app. #确保app能被正常引入,同时装饰器@shared_task能找到并使用app from .celery import app as celery_app __all__ = ['celery_app']
3)在Celery_test文件夹中的setting.py添加celery相关配置信息:
#for celery CELERY_BROKER_URL = 'redis://:nan123456@192.168.1.145/0' CELERY_RESULT_BACKEND = 'redis://:nan123456@192.168.1.145/1'
4)在app01中创建tasks.py文件:
用于制作任务
from __future__ import absolute_import, unicode_literals from celery import shared_task import time @shared_task # 确保其他app同样能调用app01中,被shared_task装饰的函数 def add(x, y): print("running task add, ") # time.sleep(10) return x + y @shared_task def mul(x, y): return x * y @shared_task def xsum(numbers): return sum(numbers)
5)页面访问:
首先url配置:
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), path('task_res/', views.task_res), ]
view页面操作:
前端刷新页面或请求数据,假如请求到的某些数据需要花费较长时间,此时可以使用celery异步处理任务,当用户访问index页面时,返回给用户一个任务id(此时不阻塞,页面正常访问),
同时在前端页面用ajax实现一个定时任务(每隔xx时间想后台请求之前任务结果,如果返回success则说明任务完成,可以提取数据给前端。
index:
from django.shortcuts import render,HttpResponse from app01 import tasks from celery.result import AsyncResult def index(request): res = tasks.add.delay(5, 999) #执行tasks中add函数(任务) print("res:", res) # res:8759c00e-8d22-4e80-a65d-20d999bce200之类 return HttpResponse(res.task_id)
task_res:
from django.shortcuts import render,HttpResponse from app01 import tasks from celery.result import AsyncResult def task_res(request): """ 用于前端ajax定时访问,任务完成时,result.status = success, result.get():获得任务结果 """ result = AsyncResult(id="8759c00e-8d22-4e80-a65d-20d999bce200") # #return HttpResponse(result.get()) return HttpResponse(result.status)
将Celery_test项目打包压缩成zip格式,传给虚拟机linux系统,
传送文件需install lrzsz: apt install lrzsz
安装成功后输入指令:rz,会跳出上传文件对话框 ,或者可以直接从本地拉文件到虚拟机中
解压zip压缩包:unzip Celery_test.zip
操作:
1)cd Celery_test:进入项目中
2)celery -A Celery_test worker -l info :启动worker监控并等待执行任务
因为window环境中测试出现问题,故到linux环境中开启worker任务监控
此时,pycharm中运行Celery_test项目,浏览器访问:http://127.0.0.1:8000/index/,可获取结果:
此时,linux端中的worker执行了一次任务:
当你不停刷新浏览器页面,task_id会一直变化,worker会一直执行任务。
浏览器再访问:http://127.0.0.1:8000/task_res/ ,可获取一个结果:
如果是:SUCCESS,表示任务执行完成,可以先后端请求任务结果,否则,表示任务未完成
Django+Celery 简单测试异步发送邮件
window环境下,在pycharm操作项目,完成再转发到Ubuntu环境中开启worker监控任务,测试发送邮件
基于上面 Celery_test 项目,在setting中添加发送邮件相关配置、tasks.py中添加发送邮件任务、view.py、url.py添加注册操作
1)setting.py添加相关配置:
celery:
#for celery CELERY_BROKER_URL = 'redis://:nan123456@192.168.1.145/0' CELERY_RESULT_BACKEND = 'redis://:nan123456@192.168.1.145/1' CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_ENABLE_UTC = True CELERY_TIMEZONE = TIME_ZONE
email:
#for email EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = "smtp.163.com" #以163邮箱为例,SMTP服务器(邮箱需要开通SMTP服务) EMAIL_HOST_PASSWORD = '******' #SMTP服务授权码 DEFAULT_FROM_EMAIL = EMAIL_HOST_USER = "name@163.com" #我的163邮箱帐号 EMAIL_PORT = 25 #163邮箱SMTP服务端口 EMAIL_USE_TLS = True # 163、qq邮箱此值为True,aliyun此值为False,163可以忽略此值 EMAIL_SUBJECT_PREFIX = '[yshblog.com]' #邮件标题前缀,默认是'[django]'
2)tasks.py 添加发送邮件任务:
@shared_task #异步邮箱验证 def send_active_email(to_email, user_name, token): """发送激活邮件""" subject = "博客园用户激活" # 标题 body = "" # 文本邮件体 sender = settings.DEFAULT_FROM_EMAIL # 发件人 receiver = [to_email] # 接收人 html_body = '<h1>尊敬的用户 %s, 感谢您注册博客园!</h1>' \ '<br/><p>请点击此链接激活您的帐号<a href="http://127.0.0.1:8000/register_validation/%s">' \ 'http://127.0.0.1:8000/register_validation/%s</a></p>' % (user_name, token, token) send_mail(subject, body, sender, receiver, html_message=html_body)
3)url.py 注册url 、邮箱验证url:
path('register/', views.register), #用于注册 re_path(r'^register/(\w+)/$', views.register), #用于异步邮件发送后,验证邮箱
4)view.py:
注册:
def register(request): """ 注册 """ if request.method == "POST": # 获取注册请求参数 username = request.POST.get('username') password = request.POST.get('pwd') email = request.POST.get('email') # 参数校验:缺少任意一个参数,就不要在继续执行 if not all([username, password, email]): return redirect('/register/') # 判断邮箱 if not re.match(r"^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$", email): return render(request, 'register.html', {'errmsg': '邮箱格式不正确'}) # 保存数据到数据库 #try: # user = models.User.objects.create(username, email, password) # except db.IntegrityError: #except Exception: # return render(request, 'register.html', {'errmsg': '用户已注册'}) # 生成激活token token = 1 # celery发送激活邮件:异步完成,发送邮件不会阻塞结果的返回 tasks.send_active_email.delay(email, username, token) # 返回结果:比如重定向到首页 return HttpResponse("邮件已发送到您邮箱,请打开您邮箱进行验证") return render(request,"register.html")
邮箱验证:
def register_validation(request,task_id): """邮箱验证 ,待完善""" return render(request,"register_validation.html")
到此,代码操作便完成了,将Celery_test项目打包成zip格式,发送到Ubuntu环境中。
1)在Ubuntu环境中通过命令:unzip Celery_test.zip 解压
2)cd Celery_test ,进入项目
3)执行命令:
celery -A Celery_test worker -l info
注:Ubuntu中如果没安装sendmail,需提前安装:apt-get install sendmail
4)window环境,浏览器打开网址:http://127.0.0.1:8000/register/,输入信息,并提交
5)浏览器post提交信息后,Ubuntu worker会监控到任务并执行,即往该邮箱(name@qq.com)发送一封验证邮件:
打开邮箱会收到验证的邮件:
至此,简单的Django+celery发送邮件测试demo就完成啦!