Dajngo学习
Django Celery
前言
- Celery是一个基于python开发的分布式任务队列,而做python WEB开发最为流行的框架莫属Django,但是Django的请求处理过程都是同步的无法实现异步任务,若要实现异步任务处理需要通过其他方式(前端的一般解决方案是ajax操作),而后台Celery就是不错的选择。倘若一个用户在执行某些操作需要等待很久才返回,这大大降低了网站的吞吐量。
- kombu : 支持将不同的消息中间件以插件的方式进行灵活配置。Kombu中使用transport这个术语来表示一个具体的消息中间件(后续均用broker指代)
https://blog.csdn.net/weixin_37947156/article/details/76372427
定义
- Celery是一个简单、灵活和可靠的分布式系统,可以处理大量的消息,同时提供维护这样一个系统所需的工具。
- Celery是一个任务队列,关注实时处理,同时支持任务调度。
模块
字段 | 说明 |
---|---|
Task(任务模块) | 包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,定时任务由 Celery Beat 进程周期性地将任务发往任务队列 |
Broker(消息中间键) | 即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等 |
Worker(任务执行单元) | 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它。 |
Backend(任务结果储存) | 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, Redis 和 MongoDB 等。 |
Celery定时任务配置
在进行配置前先来看看项目结构:
├── test_celery
│ ├── celery.py
│ ├── init.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── art
│ ├── admin.py
│ ├── apps.py
│ ├── init.py
│ ├── migrations
│ ├── models
│ ├── tasks.py
│ ├── tests.py
│ └── views
└── start-celery.sh
其中art是我们的app,test_celery则是我们的project。我们需要关心的主要是 celery.py , settings.py , tasks.py 和 start-celery.sh 。
1. celery.py
首先是celery.py,想让celery执行任务就必须实例化一个celery app,并把settings.py里的配置传入app:
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# 设置django环境
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_celery.settings')
broker = 'redis://127.0.0.1:6379/7'
app = Celery('art_project', broker=broker)
# 使用CELERY_ 作为前缀,在settings中写配置
app.config_from_object('django.conf:settings', namespace='CELERY')
# 发现任务文件每个app下的task.py
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
2. init.py
#test_celery/init.py
import pymysql
# from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ['celery_app']
pymysql.install_as_MySQLdb()
3. tasks.py
它应该位于你的app目录中,前面我们配置了自动发现,所以celery会自动找到这些tasks,我们的tasks将写在这一模块中,代码涉及了一些orm的使用:
from __future__ import absolute_import, unicode_literals
import datetime
from celery import shared_task
from test_celery.celery import app
@app.task(name='add')
def add(x, y):
'''触发任务'''
return x + y
@shared_task(name='art.tasks.tell')
def tell():
print(123)
print('task test,[%s],args=(%s)' % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), None))
'''定时任务'''
name = '郭志航'
age = '36'
# add.delay(1, 2)
return '姓名是:' + name + ',年龄是:' + age
4. setting.py
'''
1、当djcelery.setup_loader()运行时,Celery便会去查看INSTALLD_APPS下包含的所有app目录中的tasks.py文件,
找到标记为task的方法,将它们注册为celery task
2、BROKER_URL:broker是代理人,它负责分发任务给worker去执行。我使用的是Redis作为broker
3、 没有设置 CELERY_RESULT_BACKEND,默认没有配置,此时Django会使用默认的数据库(也是你指定的orm数据库)。
4、CELERY_IMPORTS:是导入目标任务文件
5、CELERYBEAT_SCHEDULER:使用了django-celery默认的数据库调度模型,任务执行周期都被存在默认指定的orm数据库中.
6、CELERYBEAT_SCHEDULE:设置定时的时间配置, 可以精确到秒,分钟,小时,天,周等。
'''
#############################
# celery 配置信息 start
#############################
import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/7' # 设置broker为代理人
# CELERY_IMPORTS = ('art.tasks')
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
# celery settings
# 这两项必须设置,否则不能正常启动celery beat
CELERY_ENABLE_UTC = True
CELERY_TIMEZONE = 'Asia/Shanghai'
# 任务队列配置
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/7' # 设置broker为代理人
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/7'
from celery.schedules import crontab
from celery.schedules import timedelta
# CELERYBEAT_SCHEDULE = { # 定时器策略
# # 定时任务一: 每隔30s运行一次
# u'测试定时器1': {
# "task": "art.tasks.tell",
# # "schedule": crontab(minute='*/2'), # or 'schedule': timedelta(seconds=3),
# "schedule": timedelta(seconds=30),
# "args": (),
# },
# }
'''
task:任务函数所在的模块,模块路径得写全,否则找不到将无法运行该任务
schedule:定时策略,一般使用 celery.schedules.crontab ,上面例子为每小时的0分执行一次任务,具体写法与linux的crontab类似可以参考文档说明
args:是个元组,给出任务需要的参数,如果不需要参数也可以不写进配置,就像例子中的一样
其余配置项较少用,可以参考文档
'''
CELERY_BEAT_SCHEDULE = {
u'测试定时器1': {
'task': 'art.tasks.tell',
# 'schedule': crontab(minute=0, hour='*/1'),
'schedule': timedelta(seconds=10),
}
}
#############################
# celery 配置信息 end
#############################
5. Views.py
触发任务
art/views.py
def add_handler(request):
x = request.GET.get('x', '1')
y = request.GET.get('y', '1')
res = add.delay(int(x), int(y))
res = {'code': 200, 'num': res.task_id, 'data': [{'x': x, 'y': y}]}
return HttpResponse(json.dumps(res))
6. start-celery.sh
-A 表示app所在的目录,-B表示启动celery beat运行定时任务。
触发任务: celery -A art.tasks worker -l info
定时任务: celery -A art.tasks.tell beat -l info
定时任务和触发任务同时进行: celery -A art.tasks worker -l info -B
定时任务和触发任务同时进行:
export REDIS_ADDR=127.0.0.1
celery -A test_celery worker -l info -B -f /path/to/log
[2018-12-21 13:00:00,022: INFO/MainProcess] Received task: news.tasks.fetch_all_news[e4566ede-2cfa-4c19-b2f3-0c7d6c38690d]
[2018-12-21 13:00:00,046: INFO/MainProcess] Received task: news.tasks.fetch_news[583e96dc-f508-49fa-a24a-331e0c07a86b]
[2018-12-21 13:00:00,051: INFO/ForkPoolWorker-2] Task news.tasks.fetch_all_news[e4566ede-2cfa-4c19-b2f3-0c7d6c38690d] succeeded in 0.02503809699555859s: None
[2018-12-21 13:00:00,052: INFO/MainProcess] Received task: news.tasks.fetch_news[c61a3e55-dd3c-4d49-8d6d-ca9b1757db25]
[2018-12-21 13:00:00,449: INFO/ForkPoolWorker-5] Task news.tasks.fetch_news[c61a3e55-dd3c-4d49-8d6d-ca9b1757db25] succeeded in 0.39487219898728654s: None
[2018-12-21 13:00:00,606: INFO/ForkPoolWorker-3] Task news.tasks.fetch_news[583e96dc-f508-49fa-a24a-331e0c07a86b] succeeded in 0.5523456179944333s: None
7. 遇见的问题
- 定时任务
@shared_task(name='art.tasks.tell')
要和 settings.py 中"task": "art.tasks.tell"
,一致性 - 启动celery后调用任务后报错:
ERROR/MainProcess Unrecoverable error: AttributeError("'str' object has no attribute 'items'")
解决方案: 将 redis版本降低到 2.10.6 , pip install redis==2.10.6
- 启动celery异步任务时, 报: KeyError: ‘backend’
from . import async, base
^
SyntaxError: invalid syntax
异常原因: Python3.6及其以上的版本已经将async收入关键字中, 而celery4.0版本也用到async,
如果不配置backend的话是不会有这个异常的.
解决方案: 第一种: 将celery的版本将为2.10.6, 第二种: 将celery依赖中的async更名为其他名称即可
Celery 发送邮件
1. settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# 发送邮件配置
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_SSL = False
# smpt服务地址
EMAIL_HOST = '127.0.0.1'
EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_FROM = 'Gavin Z.H. Guo/CEN/FOXCONN'
tasks.py
# 定义任务函数
@app.task(name='send_mail')
def send_register_active_email(username,to_email):
'''发送激活邮件'''
# 组织邮件信息
subject = 'test_type'
message = 'luke is ugly'
sender = settings.EMAIL_FROM
receiver = [to_email, ]
html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>收到邮件告诉我一声,Thanks <br/>' % (username)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(5)
return '发送成功'
views.py
def index(request, *args, **kwargs):
emial = 'Gavin Z.H. Guo/CEN/FOXCONN'
name = 'luke'
print('发送邮件中---')
send_register_active_email.delay(name,emial)
print('发送成功-----')
res = {'status': 200, 'message': 'ok'}
return HttpResponse(json.dumps(res))