Closed vieyahn2017 closed 4 years ago
前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务。在同事的指引下接触了Celery这个异步任务队列框架,鉴于网上关于Celery和Django结合的文档较少,大部分也只是粗粗介绍了大概的流程,在实践过程中还是遇到了不少坑,希望记录下来帮助有需要的朋友。
Django Web中从一个http请求发起,到获得响应返回html页面的流程大致如下:http请求发起 -- http handling(request解析) -- url mapping(url正则匹配找到对应的View) -- 在View中进行逻辑的处理、数据计算(包括调用Model类进行数据库的增删改查)--将数据推送到template,返回对应的template/response。
图1. Django架构总览
同步请求:所有逻辑处理、数据计算任务在View中处理完毕后返回response。在View处理任务时用户处于等待状态,直到页面返回结果。
异步请求:View中先返回response,再在后台处理任务。用户无需等待,可以继续浏览网站。当任务处理完成时,我们可以再告知用户。
Celery是基于Python开发的一个分布式任务队列框架,支持使用任务队列的方式在分布的机器/进程/线程上执行任务调度。
图2. Celery架构
图2展示的是Celery的架构,它采用典型的生产者-消费者模式,主要由三部分组成:broker(消息队列)、workers(消费者:处理任务)、backend(存储结果)。实际应用中,用户从Web前端发起一个请求,我们只需要将请求所要处理的任务丢入任务队列broker中,由空闲的worker去处理任务即可,处理的结果会暂存在后台数据库backend中。我们可以在一台机器或多台机器上同时起多个worker进程来实现分布式地并行处理任务。
在实际使用过程中,发现Celery在Django里的实现与其在一般.py文件中的实现还是有很大差别,Django有其特定的使用Celery的方式。这里着重介绍Celery在Django中的实现方法,简单介绍与其在一般.py文件中实现方式的差别。
sudo apt-get install rabbitmq-server 命令执行成功后,rabbitmq-server就已经安装好并运行在后台了。
另外也可以通过命令rabbitmq-server -detached来在后台启动rabbitmq server以及命令rabbitmqctl stop来停止server。
更多的命令可以参考rabbitmq官网的用户手册:https://www.rabbitmq.com/manpages.html
安装django-celery pip install celery pip install django-celery
配置settings.py 首先,在Django工程的settings.py文件中加入如下配置代码:
import djcelery
djcelery.setup_loader()
BROKER_URL= 'amqp://guest@localhost//'
CELERY_RESULT_BACKEND = 'amqp://guest@localhost//'
其中,当djcelery.setup_loader()运行时,Celery便会去查看INSTALLD_APPS下包含的所有app目录中的tasks.py文件,找到标记为task的方法,将它们注册为celery task。BROKER_URL和CELERY_RESULT_BACKEND分别指代你的Broker的代理地址以及Backend(result store)数据存储地址。在Django中如果没有设置backend,会使用其默认的后台数据库用来存储数据。注意,此处backend的设置是通过关键字CELERY_RESULT_BACKEND来配置,与一般的.py文件中实现celery的backend设置方式有所不同。一般的.py中是直接通过设置backend关键字来配置,如下所示:
app = Celery('tasks', backend='amqp://guest@localhost//', broker='amqp://guest@localhost//')
然后,在INSTALLED_APPS中加入djcelery:
INSTALLED_APPS = (
……
'qv',
'djcelery'
……
)
在tasks.py中我们就可以编码实现我们需要执行的任务逻辑,在开始处import task,然后在要执行的任务方法开头用上装饰器@task。需要注意的是,与一般的.py中实现celery不同,tasks.py必须建在各app的根目录下,且不能随意命名。
生产任务 在需要执行该任务的View中,通过build_job.delay的方式来创建任务,并送入消息队列。比如:
启动worker的命令
python manage.py runserver
python manage.py celery worker -c 4 --loglevel=info
Django下要查看其他celery的命令,包括参数配置、启动多worker进程的方式都可以通过python manage.py celery --help来查看:
另外,Celery提供了一个工具flower,将各个任务的执行情况、各个worker的健康状态进行监控并以可视化的方式展现,如下图所示:
Django下实现的方式如下:
pip install flower
python manage.py celery flower
https://blog.csdn.net/bbwangj/article/details/90573640
生产者消费者模式 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据,如下图所示:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过消息队列(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给消息队列,消费者不找生产者要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个消息队列就是用来给生产者和消费者解耦的。
解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
Celery介绍 Celery是基于Python开发的一个分布式任务队列框架,支持使用任务队列的方式在分布的机器/进程/线程上执行任务调度。
上图展示的是Celery的架构,它采用典型的生产者-消费者模式,主要由三部分组成:broker(消息队列)、workers(消费者:处理任务)、backend(存储结果)。
消息中间件:Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis, MongoDB (experimental), Amazon SQS (experimental),CouchDB (experimental), SQLAlchemy (experimental),Django ORM (experimental), IronMQ。 任务执行单元:Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。 任务结果存储:Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, Redis,memcached, MongoDB,SQLAlchemy, Django ORM,Apache Cassandra, IronCache。
实际应用中,用户从Web前端发起一个请求,我们只需要将请求所要处理的任务放入任务队列broker中,由空闲的worker去处理任务即可,处理的结果会暂存在后台数据库backend中。我们可以在一台机器或多台机器上同时起多个worker进程来实现分布式地并行处理任务。
任务队列
任务队列是一种在线程或机器间分发任务的机制。
消息队列
消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。
Celery 用消息通信,通常使用中间人(Broker)在客户端和职程间斡旋。这个过程从客户端向队列添加消息开始,之后中间人把消息派送给职程,职程对消息进行处理。如下图所示:
Celery 系统可包含多个职程和中间人,以此获得高可用性和横向扩展能力。
安装 pip install django 安装django
pip install celery==3.1.23 安装celery
pip install django-celery 安装django-celery
对Redis的支持需要额外的依赖。你可以用 celery[redis] 捆绑 同时安装 Celery 和这些依赖
pip install -U celery[redis]
pip install python-jenkins 安装jenkins
消息队列 yum install -y rabbitmq-server 安装rabbitmq
环境变量PATH加入/usr/lib/rabbitmq/bin
rabbitmq-plugins enable rabbitmq_management 启用web管理插件
chkconfig rabbitmq-server on 开机自启动
/etc/init.d/rabbitmq-server start 启动RabbitMQ
增加用户
rabbitmqctl add_user shhnwangjian 123456
rabbitmqctl set_user_tags shhnwangjian administrator
命令执行成功后,rabbitmq-server就已经安装好并运行在后台了。
WEB页面访问:http://ip地址:15672/
另外也可以通过命令rabbitmq-server来启动rabbitmq server以及命令rabbitmqctl stop来停止server。
更多的命令可以参考rabbitmq官网的用户手册:https://www.rabbitmq.com/manpages.html
redis 安装 http://redis.io/download
django中celery实现
创建项目和app
django-admin.py startproject celery-wj
cd celery-wj
django-admin.py startapp app01
配置settings.py
当djcelery.setup_loader()运行时,Celery便会去查看INSTALLD_APPS下包含的所有app目录中的tasks.py文件,找到标记为task的方法,将它们注册为celery task。 BROKER_URL和CELERY_RESULT_BACKEND分别指代你的Broker的代理地址以及Backend(result store)数据存储地址。 在Django中如果没有设置backend,会使用其默认的后台数据库用来存储数据。注意,此处backend的设置是通过关键字CELERY_RESULT_BACKEND来配置,与一般的.py文件中实现celery的backend设置方式有所不同。一般的.py中是直接通过设置backend关键字来配置,如下所示:
test = Celery('tasks', backend='redis://10.10.83.162:16379/0', broker='redis://10.10.83.162:16379/9')
Django下要查看其他celery的命令,包括参数配置、启动多worker进程的方式都可以通过python manage.py celery --help来查看:
创建一个task
# -*- coding: utf-8 -*-
from celery import task
@task
def add(x, y):
return x + y
注意:与一般的.py中实现celery不同,tasks.py必须建在各app的根目录下,且不能随意命名。
views.py
# -*- coding: utf-8 -*-
from django.shortcuts import HttpResponse, render, redirect, HttpResponseRedirect
from app01 import tasks
def add_test(request):
result = tasks.add.delay(2, 2)
print result
if result.ready():
print "Task has run"
if result.successful():
print "Result was: %s" % result.result
else:
if isinstance(result.result, Exception):
print "Task failed due to raising an exception"
raise result.result
else:
print "Task failed without raising exception"
else:
print "Task has not yet run"
return HttpResponse(result)
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^add/$', views.add_test, name='add'),
]
python manage.py runserver 0.0.0.0:9008
python manage.py celery worker -c 4 --logievel=info
通过terminal测试task
python manage.py shell
from app01 import tasks tasks.add.delay(3,5)
通过页面访问测试task
Celery的可视化监控工具flower
1.安装flower
pip install flower
2.启动
python manage.py celery flower --port=9008(默认是5555端口)
3.帮助
python manage.py celery flower --help
4.可视化页面
参考博客:http://www.cnblogs.com/znicy/p/5626040.html
Django中如何使用django-celery完成异步任务: http://www.weiguda.com/blog/73/
djcelery入门:实现运行定时任务: http://my.oschina.net/kinegratii/blog/292395
https://www.cnblogs.com/wdliu/p/9530219.html
Celery是一个基于python开发的分布式任务队列,如果不了解请阅读笔者上一篇博文Celery入门与进阶,而做python WEB开发最为流行的框架莫属Django,但是Django的请求处理过程都是同步的无法实现异步任务,若要实现异步任务处理需要通过其他方式(前端的一般解决方案是ajax操作),而后台Celery就是不错的选择。倘若一个用户在执行某些操作需要等待很久才返回,这大大降低了网站的吞吐量。下面将描述Django的请求处理大致流程(图片来源于网络):
请求过程简单说明:浏览器发起请求-->请求处理-->请求经过中间件-->路由映射-->视图处理业务逻辑-->响应请求(template或response)
celery很容易集成到Django框架中,当然如果想要实现定时任务的话还需要安装django-celery-beta插件,后面会说明。需要注意的是Celery4.0只支持Django版本>=1.8的,如果是小于1.8版本需要使用Celery3.1。
配置
新建立项目taskproj,目录结构(每个app下多了个tasks文件,用于定义任务):
taskproj
├── app01
│ ├── __init__.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tasks.py
│ └── views.py
├── manage.py
├── taskproj
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── templates
在项目目录taskproj/taskproj/目录下新建celery.py:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'taskproj.settings') # 设置django环境
app = Celery('taskproj')
app.config_from_object('django.conf:settings', namespace='CELERY') # 使用CELERY_ 作为前缀,在settings中写配置
app.autodiscover_tasks() # 发现任务文件每个app下的task.py
taskproj/taskproj/init.py:
from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ['celery_app']
taskproj/taskproj/settings.py
CELERY_BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件
CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
进入项目的taskproj目录启动worker:
celery worker -A taskproj -l debug 定义与触发任务
任务定义在每个tasks文件中,app01/tasks.py:
from __future__ import absolute_import, unicode_literals
from celery import shared_task
@shared_task
def add(x, y):
return x + y
@shared_task
def mul(x, y):
return x * y
视图中触发任务
from django.http import JsonResponse
from app01 import tasks
# Create your views here.
def index(request,*args,**kwargs):
res=tasks.add.delay(1,3)
#任务逻辑
return JsonResponse({'status':'successful','task_id':res.task_id})
访问http://127.0.0.1:8000/index
若想获取任务结果,可以通过task_id使用AsyncResult获取结果,还可以直接通过backend获取:
扩展 除了redis、rabbitmq能做结果存储外,还可以使用Django的orm作为结果存储,当然需要安装依赖插件,这样的好处在于我们可以直接通过django的数据查看到任务状态,同时为可以制定更多的操作,下面介绍如何使用orm作为结果存储。
1.安装
pip install django-celery-results 2.配置settings.py,注册app
INSTALLED_APPS = (
...,
'django_celery_results',
)
4.修改backend配置,将redis改为django-db
#CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_BACKEND = 'django-db' #使用django orm 作为结果存储
5.修改数据库
python3 manage.py migrate django_celery_results 此时会看到数据库会多创建:
当然你有时候需要对task表进行操作,以下源码的表结构定义:
class TaskResult(models.Model):
"""Task result/status."""
task_id = models.CharField(_('task id'), max_length=255, unique=True)
task_name = models.CharField(_('task name'), null=True, max_length=255)
task_args = models.TextField(_('task arguments'), null=True)
task_kwargs = models.TextField(_('task kwargs'), null=True)
status = models.CharField(_('state'), max_length=50,
default=states.PENDING,
choices=TASK_STATE_CHOICES
)
content_type = models.CharField(_('content type'), max_length=128)
content_encoding = models.CharField(_('content encoding'), max_length=64)
result = models.TextField(null=True, default=None, editable=False)
date_done = models.DateTimeField(_('done at'), auto_now=True)
traceback = models.TextField(_('traceback'), blank=True, null=True)
hidden = models.BooleanField(editable=False, default=False, db_index=True)
meta = models.TextField(null=True, default=None, editable=False)
objects = managers.TaskResultManager()
class Meta:
"""Table information."""
ordering = ['-date_done']
verbose_name = _('task result')
verbose_name_plural = _('task results')
def as_dict(self):
return {
'task_id': self.task_id,
'task_name': self.task_name,
'task_args': self.task_args,
'task_kwargs': self.task_kwargs,
'status': self.status,
'result': self.result,
'date_done': self.date_done,
'traceback': self.traceback,
'meta': self.meta,
}
def __str__(self):
return '<Task: {0.task_id} ({0.status})>'.format(self)
如果想要在django中使用定时任务功能同样是靠beat完成任务发送功能,当在Django中使用定时任务时,需要安装django-celery-beat插件。以下将介绍使用过程。
安装配置 1.beat插件安装
pip3 install django-celery-beat 2.注册APP
INSTALLED_APPS = [
....
'django_celery_beat',
]
3.数据库变更
python3 manage.py migrate django_celery_beat 4.分别启动woker和beta
celery -A proj beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler #启动beta 调度器使用数据库
celery worker -A taskproj -l info #启动woker
5.配置admin
urls.py
# urls.py
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
6.创建用户
python3 manage.py createsuperuser
7.登录admin进行管理(地址http://127.0.0.1:8000/admin)并且还可以看到我们上次使用orm作为结果存储的表。
http://127.0.0.1:8000/admin/login/?next=/admin/
使用示例:
查看结果:
二次开发
django-celery-beat插件本质上是对数据库表变化检查,一旦有数据库表改变,调度器重新读取任务进行调度,所以如果想自己定制的任务页面,只需要操作beat插件的四张表就可以了。当然你还可以自己定义调度器,django-celery-beat插件已经内置了model,只需要进行导入便可进行orm操作,以下我用django reset api进行示例:
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'django_celery_results',
'django_celery_beat',
'rest_framework',
]
urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index$', views.index),
url(r'^res$', views.get_res),
url(r'^tasks$', views.TaskView.as_view({'get':'list'})),
]
views.py
from django_celery_beat.models import PeriodicTask #倒入插件model
from rest_framework import serializers
from rest_framework import pagination
from rest_framework.viewsets import ModelViewSet
class Userserializer(serializers.ModelSerializer):
class Meta:
model = PeriodicTask
fields = '__all__'
class Mypagination(pagination.PageNumberPagination):
"""自定义分页"""
page_size=2
page_query_param = 'p'
page_size_query_param='size'
max_page_size=4
class TaskView(ModelViewSet):
queryset = PeriodicTask.objects.all()
serializer_class = Userserializer
permission_classes = []
pagination_class = Mypagination
访问http://127.0.0.1:8000/tasks如下:
celery在Django中的使用
https://www.cnblogs.com/bufufan/p/11069058.html