目录
celery的使用(最新详细解析)
一. Celery简介
二. Celery的使用
2.1 安装celery及配置Redis
2.2 Celery执行异步任务发送邮件
2.3 Celery执行定时任务
三. django中使用celery的模块
3.1 django-celery基本使用
Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。
消息中间件:Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等。
任务执行单元:Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
任务结果存储:Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等。
Celery多用来执行异步任务,将耗时的操作交由Celery去异步执行,比如发送邮件、短信、消息推送、音视频处理等。还可以执行定时任务,定时执行某件事情,比如Redis中的数据每天凌晨两点保存至mysql数据库,实现Redis的持久化。
更详细的技术文档,请访问官网 http://www.celeryproject.org/。
任务队列
任务队列是一种在线程或机器间分发任务的机制。
消息队列
消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。
Celery 用消息通信,通常使用中间人(Broker)在客户端和职程间斡旋。这个过程从客户端向队列添加消息开始,之后中间人把消息派送给职程,职程对消息进行处理。如下图所示:
Celery 系统可包含多个职程和中间人,以此获得高可用性和横向扩展能力。
在使用 Celery 之前请务必理解以下概念:
a. Celery Beat: 任务调度器,Beat 进程会读取配置文件的内容,周期性的将配置中到期需要执行的任务发送给任务队列
b. Celery Worker: 执行任务的消费者,通常会在多台服务器运行多个消费者来提高运行效率。
c. Broker: 消息代理,也是任务队列本身(通常是消息队列或者数据库),通常称为消息中间件,接收任务生产者发送过来的任务消息,存进队列再按序分发给任务消费方。
d. Producer: 任务生产者,调用 Celery API 的函数或者装饰器而产生任务并交给任务队列处理的都是任务生产者。
配置项说明:
Celery 是通过配置文件中的配置项来定制任务的。
CELERY_IMPORTS: 配置导入哥哥任务的代码模块
CELERY_QUEUES: 定义任务执行的各个任务队列(如按照执行时间分slow、fast等),默认有一个队列,暂称为一般任务队列。
CELERY_ROUTES: 配置各个任务分配到不同的任务队列
CELERY_SCHEDULE: 配置各个任务执行的时机参数
CELERY_TIMEZONE: 设置时区
CELERY_ENABLE_UTC: 是否启动时区设置,默认值是True
CELERY_CONCURRENCY: 并发的worker数量
CELERY_PREFETCH_MULTIPLIER: 每次去消息队列读取任务的数量,默认值是4
CELERY_MAX_TASKS_PRE_CHILD: 每个worker执行多少次任务后会死掉
BROKER_URL: 使用redis作为任务队列
CELERY_TASK_RESULT_EXPIRES: 任务执行结果的超时时间
CELERY_TASK_TIME_LIMIT: 单个任务运行的时间限制,超时会被杀死,不建议使用该参数,而用CELERY_TASK_SOFT_TIME_LIMIT
CELERY_RESULT_TACKEND: 使用redis存储执行结果
CELERY_TASK_SERIALIZER: 任务序列化方式
CELERY_RESULT_SERIALIZER: 任务执行结果序列化方式
CELERY_DISABLE_RATE_LIMITS: 关闭执行限速
这里以用用户注册,然后celery发送激活邮件为例子说明celery的基本用法。
pip install celery
pip install django-redis
# Windows中还需要安装以下模块,用于任务执行单元
pip install eventlet
在项目的配置文件中配置redis:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
# "PASSWORD": "123",
}
}
}
首先在django中,celery要建立以下目录结构:
pro_cel
├── celery_task# celery相关文件夹
│ ├── celery.py # celery连接和配置相关文件,必须叫这个名字
│ └── tasks1.py # 所有任务函数
│ └── tasks2.py # 所有任务函数
├── check_result.py # 检查结果
└── send_task.py # 触发任务
注意,检查结果与触发任务的模块不能写在celery_task模块中,不然会报导入celery的错误。
比如这里建的目录如下:
首先celery.py中生成Celery对象,同时里面演示了一下定时任务,后面还会再提:
from celery import Celery
from celery.schedules import crontab
from datetime import timedelta
# 消息中间件,密码是你redis的密码
# broker='redis://:[email protected]:6379/2' 密码123456
broker = 'redis://127.0.0.1:6379/0' # 无密码
# 任务结果存储
backend = 'redis://127.0.0.1:6379/1'
# 生成celery对象,'task'相当于key,用于区分celery对象
# include参数需要指定任务模块
app = Celery('task', broker=broker, backend=backend, include=[
'celery_task.add_task',
'celery_task.send_email'
])
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 定时执行
app.conf.beat_schedule = {
# 名字随意命名
'add-every-5-seconds': {
# 执行add_task下的addy函数
'task': 'celery_task.add_task.add',
# 每10秒执行一次
'schedule': timedelta(seconds=10),
# add函数传递的参数
'args': (1, 2)
},
'add-every-10-seconds': {
'task': 'celery_task.add_task.add',
# crontab不传的参数默认就是每的意思,比如这里是每年每月每日每天每小时的5分执行该任务
'schedule': crontab(minute=5),
'args': (1, 2)
}
}
然后写send_msg.py发送邮件的任务,首先要去项目的配置文件中配置邮箱:
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com' # 如果是 163 改成 smtp.163.com
EMAIL_PORT = 465
EMAIL_HOST_USER = '[email protected]' # 发送邮件的邮箱帐号
EMAIL_HOST_PASSWORD = '授权码' # 授权码,各邮箱的设置中启用smtp服务时获取
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# 这样收到的邮件,收件人处就会这样显示
# DEFAULT_FROM_EMAIL = '2333<'[email protected]>'
EMAIL_USE_SSL = True # 使用ssl
# EMAIL_USE_TLS = False # 使用tls
# EMAIL_USE_SSL 和 EMAIL_USE_TLS 是互斥的,即只能有一个为 True
然后写发送邮件的任务send_msg.py代码如下:
import os
if __name__ == "celery_task.send_email":
# 因为需要用到django中的内容,所以需要配置django环境
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "do_celery.settings")
import django
django.setup()
# 导入celery对象app
from celery_task.celery import app
from app01 import models
# 导入django自带的发送邮件模块
from django.core.mail import send_mail
import threading
from do_celery import settings
@app.task
def send_email1(id): # 此时可以直接传邮箱,还能减少一次数据库的IO操作
# 此处的id由用户注册的视图函数中传入
user_obj = models.UserInfo.objects.filter(pk=id).first()
email = user_obj.email
# 启用线程发送邮件,此处最好加线程池
t = threading.Thread(target=send_mail, args=(
"激活邮件,点击激活账号", # 邮件标题
'点击该邮件激活你的账号,否则无法登陆', # 给html_message参数传值后,该参数信息失效
settings.EMAIL_HOST_USER, # 用于发送邮件的邮箱地址
[email], # 接收邮件的邮件地址,可以写多个
),
# html_message中定义的字符串即HTML格式的信息,可以在一个html文件中写好复制出来放在该字符串中
kwargs={'html_message': "点击激活gogogo" % id}
)
t.start()
关于发送邮件的更多信息看该博客:https://www.cnblogs.com/liuqingzheng/articles/10072695.html#_label3
直接写查看结果的check_result.py文件:
from celery.result import AsyncResult
from celery_task.celery import app
def check_result(task_id):
async1 = AsyncResult(id=task_id, app=app)
if async1.successful():
result = async1.get()
print(result)
return result
# result.forget() # 将结果删除
# async.revoke(terminate=True) # 无论现在是什么时候,都要终止
# async.revoke(terminate=False) # 如果任务还没有开始执行呢,那么就可以终止。
elif async1.failed():
print('执行失败')
return '执行失败'
elif async1.status == 'PENDING':
print('任务等待中被执行')
return '任务等待中被执行'
elif async1.status == 'RETRY':
print('任务异常后正在重试')
return '任务异常后正在重试'
elif async1.status == 'STARTED':
print('任务已经开始被执行')
return '任务已经开始被执行'
后续在App中建立模型表,然后开路由,写对应的视图函数即可,代码如下:
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 注册路由
url(r'^register/', views.register),
# 用户点击邮件后的激活路由
url(r'^active_user/', views.active_user),
# index路由只是用来测试add任务的
url(r'^index/', views.index),
url(r'^login/', views.login),
]
urls.py
from django.db import models
# Create your models here.
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=32)
email = models.EmailField(null=True)
# 记录激活状态
is_active = models.BooleanField(default=0)
models.py
from django.shortcuts import render, HttpResponse, redirect
from app01 import models
import json
# Create your views here.
from celery_task.add_task import add
from celery_task.send_email import send_email1
from check_result import check_result
def index(request):
ret = add.delay(1, 2)
return HttpResponse(ret.id)
def register(request):
if request.method == 'POST':
dic = json.loads(request.body.decode('utf-8'))
name = dic.get('name')
password = dic.get('password')
email = dic.get('email')
user_obj = models.UserInfo.objects.filter(name=name).first()
if user_obj:
return HttpResponse('用户已存在')
user_obj = models.UserInfo.objects.create(name=name, password=password, email=email)
# 调用celery的发送邮件任务,将其加入消息队列,并将用户id传入
result = send_email1.delay(user_obj.id)
print(check_result(result.id))
return HttpResponse('注册成功,已向你发送一封激活邮件')
return HttpResponse('ok')
def active_user(request):
uid = request.GET.get('id')
models.UserInfo.objects.filter(id=uid).update(is_active=1)
return redirect('/login/')
def login(request):
# 此处写登录的逻辑即可
return HttpResponse('OK')
views.py
注意:delay()是异步调用, func()是同步调用
然后运行程序,先用pycharm启用任务执行单元worker(以windows为例):
celery worker -A celery_task -l info -P eventlet
然后看到界面显示结果如下(下图仅供参考,具体执行的任务不是上面代码的任务,下图为在虚拟机中执行的图片):
我们可以看到Celery正常工作在名称ubuntu的虚拟主机上,版本为3.1.23,在下面的[config]中我们可以看到当前APP的名称tasks,运输工具transport就是我们在程序中设置的中间人redis://127.0.0.1:6379/5,result我们没有设置,暂时显示为disabled,然后我们也可以看到worker缺省使用perfork来执行并发,当前并发数显示为1,然后可以看到下面的[queues]就是我们说的队列,当前默认的队列是celery,然后我们看到下面的[tasks]中有一个任务tasks.add.
使用app.conf.beat_schdule定时任务时,还需要启用beat,用于定时朝消息队列提交任务:
celery beat -A celery_task -l info
之后用postman朝该接口发送信息即可,效果如下:
设定时间让celery执行一个任务:
from celery_app_task import add
from datetime import datetime
# 方式一
# v1 = datetime(2019, 2, 13, 18, 19, 56)
# print(v1)
# v2 = datetime.utcfromtimestamp(v1.timestamp())
# print(v2)
# result = add.apply_async(args=[1, 3], eta=v2)
# print(result.id)
# 方式二
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
from datetime import timedelta
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay
# 使用apply_async并设定时间,这里是10秒后执行任务
result = add.apply_async(args=[4, 3], eta=task_time)
print(result.id)
django中celery使用crontab时,可以写以下格式:
from datetime import timedelta
from celery import Celery
from celery.schedules import crontab
cel = Celery('tasks', broker='redis://127.0.0.1:6379/0', backend='redis://127.0.0.1:6379/1', include=[
'celery_task.tasks1',
'celery_task.tasks2',
])
cel.conf.timezone = 'Asia/Shanghai'
cel.conf.enable_utc = False
cel.conf.beat_schedule = {
# 名字随意命名
'add-every-10-seconds': {
# 执行tasks1下的test_celery函数
'task': 'celery_task.tasks1.test_celery',
# 每隔2秒执行一次
# 'schedule': 1.0,
# 'schedule': crontab(minute="*/1"),
'schedule': timedelta(seconds=2),
# 传递参数
'args': ('test',)
},
# 'add-every-12-seconds': {
# 'task': 'celery_task.tasks1.test_celery',
# 每年4月11号,8点42分执行
# 'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
# 'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
# 'args': (16, 16)
# },
}
然后启动beat与worker,正常执行程序即可:
# 启动一个beat
celery beat -A celery_task -l info
# 启动work执行
celery worker -A celery_task -l info -P eventlet
其实django中使用celery有两种方式,上面建立特定目录结构的是一种,另一种就是利用django-celery模块,不过不推荐使用后者,因为对于django版本有严格的要求,要是项目换了环境,就无法使用了,不过这里也提一下。
安装需要的版本:
celery==3.1.25
django-celery==3.1.20
在项目目录下新建celeryconfig.py:
import djcelery
djcelery.setup_loader()
CELERY_IMPORTS=(
'app01.tasks',
)
#有些情况可以防止死锁
CELERYD_FORCE_EXECV=True
# 设置并发worker数量
CELERYD_CONCURRENCY=4
#允许重试
CELERY_ACKS_LATE=True
# 每个worker最多执行100个任务被销毁,可以防止内存泄漏
CELERYD_MAX_TASKS_PER_CHILD=100
# 超时时间
CELERYD_TASK_TIME_LIMIT=12*30
在App总穿件tasks.py,用于写任务:
from celery import task
@task
def add(a,b):
with open('a.text', 'a', encoding='utf-8') as f:
f.write('a')
print(a+b)
视图函数views.py:
from django.shortcuts import render,HttpResponse
from app01.tasks import add
from datetime import datetime
def test(request):
# result=add.delay(2,3)
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
from datetime import timedelta
time_delay = timedelta(seconds=5)
task_time = utc_ctime + time_delay
result = add.apply_async(args=[4, 3], eta=task_time)
print(result.id)
return HttpResponse('ok')
项目settings.py中还需要注册及配置:
INSTALLED_APPS = [
...
'djcelery',
'app01'
]
...
from djagocele import celeryconfig
BROKER_BACKEND='redis'
BOOKER_URL='redis://127.0.0.1:6379/1'
CELERY_RESULT_BACKEND='redis://127.0.0.1:6379/2'