Celery分布式队列入门

一、Celery 是什么?

Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery,也可定时执行任务等等。

通常我们可以在Django执行一些比较耗时的任务(例如发邮件)和后台任务(例如爬虫和更新服务器缓存), 举几个实例场景中可用的例子:

  1. 你想对100台机器执行一条批量命令,可能会花很长时间 ,但你不想让你的程序等着结果返回,而是给你返回 一个任务ID,你过一段时间只需要拿着这个任务id就可以拿到任务执行结果, 在任务执行ing进行时,你可以继续做其它的事情。 
  2. 你想做一个定时任务,比如每天检测一下你们所有客户的资料,如果发现今天 是客户的生日,就给他发个短信祝福

二、Celery 有5个核心角色。

1.Task

任务(Task)就是你要做的事情,例如一个注册流程里面有很多任务,给用户发验证邮件就是一个任务,这种耗时任务可以交给Celery去处理,还有一种任务是定时任务,比如每天定时统计网站的注册人数,这个也可以交给Celery周期性的处理。

2.Broker

Broker 的中文意思是经纪人,指为市场上买卖双方提供中介服务的人。在Celery中它介于生产者和消费者之间经纪人,这个角色相当于数据结构中的队列,用来接收和发送任务消息,以及存储任务结果。例如一个Web系统中,生产者是处理核心业务的Web程序,业务中可能会产生一些耗时的任务,比如短信,生产者会将任务发送给 Broker,就是把这个任务暂时放到队列中,等待消费者来处理。消费者是 Worker,是专门用于执行任务的后台服务。Worker 将实时监控队列中是否有新的任务,如果有就拿出来进行处理。Celery 本身不提供队列服务,一般用 Redis 或者 RabbitMQ 来扮演 Broker 的角色

3.Worker

Worker 就是那个一直在后台执行任务的人,也称为任务的消费者,它会实时地监控队列中有没有任务,如果有就立即取出来执行。

4.Beat

Beat 是一个定时任务调度器,它会根据配置定时将任务发送给 Broker,等待 Worker 来消费。

5.Backend

Backend 用于保存任务的执行结果,每个任务都有返回值,比如发送邮件的服务会告诉我们有没有发送成功,这个结果就是存在Backend中,当然我们并不总是要关心任务的执行结果。

584bbf78e1783.png

记住这5个角色后面理解Celery就轻松了。

三、Celery 的优点。

  1. 简单:一单熟悉了celery的工作流程后,配置和使用还是比较简单的
  2. 高可用:当任务执行失败或执行过程中发生连接中断,celery 会自动尝试重新执行任务
  3. 快速:一个单进程的celery每分钟可处理上百万个任务
  4. 灵活: 几乎celery的各个组件都可以被扩展及自定制

Celery基本工作流程图

四、Celery的安装

Celery安装:

pip install celery

 

五、Celery的使用

1.在项目中的简单使用

目录结构:

learn_celery/
├── __init__.py  
├── config.py
└── tasks.py

各目录文件说明:

__init__.py:初始化Celery以及加载配置文件

from celery import Celery

app = Celery('learn_celery')                                # 创建 Celery 实例
app.config_from_object('learn_celery.config')               # 加载配置模块

 

config.py:  Celery相关配置文件,更多配置参考:http://docs.celeryproject.org/en/latest/userguide/configuration.html

# celery中间人 redis://redis服务所在的ip地址:端口/数据库号
BROKER_URL = 'redis://localhost:6379/0'
# celery结果返回,可用于跟踪结果
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'

# celery内容等消息的格式设置
CELERY_ACCEPT_CONTENT = ['application/json', ]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'  # 结果序列化方案
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24  # 任务过期时间

# celery时区设置
CELERY_TIMEZONE = 'Asia/Shanghai'

CELERY_IMPORTS = (     # 指定导入的任务模块,可以指定多个
    'learn_celery.tasks',
)

 

tasks.py :任务定义文件

from ..learn_celery import app

@app.task
def show_name(name):
    return name

 

启动Worker:

celery worker -A learn_celery -l info

各个参数含义:

  worker: 代表第启动的角色是work当然还有beat等其他角色;

  -A :项目路径,这里我的目录是learn_celery

  -l:启动的日志级别,更多参数使用celery --help查看

若你安装的Celery版本是4.X以上的,并且在windows运行,则命令为:

celery -A learn_celery worker -l info -P gevent

查看日志输出,会发现我们定义的任务,以及相关配置:

虽然启动了worker,但是我们还需要通过delay或apply_async来将任务添加到worker中,这里我们通过交互式方法添加任务,并返回AsyncResult对象,通过AsyncResult对象获取结果:

 

AsyncResult除了get方法用于常用获取结果方法外还提以下常用方法或属性:

  • state: 返回任务状态;
  • task_id: 返回任务id;
  • result: 返回任务结果,同get()方法;
  • ready(): 判断任务是否以及有结果,有结果为True,否则False;
  • info(): 获取任务信息,默认为结果;
  • wait(t): 等待t秒后获取结果,若任务执行完毕,则不等待直接获取结果,若任务在执行中,则wait期间一直阻塞,直到超时报错;
  • successfu(): 判断任务是否成功,成功为True,否则为False;

2.在Django项目中的使用

通常我们可以在Django执行一些比较耗时的任务(例如发邮件)和后台任务(例如爬虫和更新服务器缓存)。

研究发现,在Django中使用有两种方式:

1)使用django-celery应用;

2)直接使用Celery。

这里Celery的中间人(broker),我采用Redis。也可以用Django自身和mongodb等。Celery的中间人你可以理解为在Celery执行过程中的数据支持。保存列队记录、执行记录等等。安装Redis,可参考Redis在CentOS7和Windows的安装过程

这里还需要安装celery-with-redis,执行命令:

pip install celery-with-redis

该命令会自动安装redis(python库)、celery、kombu、billiard、amqp、vine和celery-with-redis相关库。注意,这里pip安装的redis是python操作redis的库,非redis数据库。redis数据库需要独立安装。

a.Celery方式的选择

先说说django-celery的方式吧。

这种方式就是通过manage.py启动celery。通常先被提到的方案是不会采用。用pip安装django-celery,在settings引用djcelery应用。再更新数据库:

python manage.py makemigrations djcelery
python manage.py migrate djcelery

 

查看数据库,会发现多了很多相关的表。

另外djcelery还有个用途是在admin后台动态添加定时任务。这个功能也是比较鸡肋,维护不方便而且可能造成各种不可预知的问题。

所以建议直接使用Celery管理Django中的任务。这种方式也是Celery官网推荐的方式.

b.Django简单项目准备

这里我也简单做一个示例。

首先,确保celery和redis已经安装好了,并且已经启动了Redis服务。

另外,有个已经搭建好了Django项目。作为示例,简单project和简单app如下:

 

 左边侧边栏是该django的目录结构,右边是myapp中的Blog模型。再进入后台随便加了几条数据:

为了测试,一切从简。views.py写了一个响应方法:

from django.shortcuts import render
from django.http import HttpResponse
 
from .models import Blog
import json
 
def home(request):
    data = list(Blog.objects.values('caption'))
    return HttpResponse(json.dumps(data), content_type = 'application/json')

 

django项目的urls.py加了一条首页的url路由设置:

from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
]

运行django项目:

python manage.py runserver

 打开http://localhost:8000/,如下效果:

c.Django加入Celery

现打开首页要执行一个收集访客数据,发送邮件等操作。这是一个耗时任务,若放在home处理方法中执行,用户打开首页会很慢。用户体验不好,很可能不会等到页面打开。

通常这个耗时任务可以多线程处理或者异步处理。我们模拟一个耗时任务,丢给Celery异步处理。

先模拟耗时任务,打开views.py,修改如下:

from django.shortcuts import render
from django.http import HttpResponse
 
from .models import Blog
import json
import time
 
def sendmail(email):
    print('start send email to %s' % email)
    time.sleep(5) #休息5秒
    print('success')
    return True
 
def home(request):
    #耗时任务,发送邮件
    sendmail('test@test.com')
 
    #其他行为
    data = list(Blog.objects.values('caption'))
    return HttpResponse(json.dumps(data), content_type = 'application/json')

如此一来,至少需要再多等待5秒,才可以打开网页。

打开settings.py所在的文件夹,新建celery.py文件。加入如下代码:

from __future__ import absolute_import, unicode_literals

from celery import Celery
from django.conf import settings
import os

# 获取当前文件夹名,即为该Django的项目名
project_name = os.path.split(os.path.abspath('.'))[-1]
project_settings = '%s.settings' % project_name

# 设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', project_settings)

# 实例化Celery
app = Celery(project_name)

# 使用django的settings文件配置celery
app.config_from_object('django.conf:settings')

# Celery加载所有注册的应用
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

这个文件还没被加载,接着打开settings.py同个目录下的__init__.py文件。让运行该Django项目的时候,加载该文件配置Celery。修改代码如下:

from __future__ import absolute_import, unicode_literals
 
#引入celery实例对象
from .celery import app as celery_app

还需在settings.py中设置celery,尤其是中间人的设置。若不设置中间人,会提示无法连接中间人的错误。在settings.py文件中添加如下设置:

#celery settings
#celery中间人 redis://redis服务所在的ip地址:端口/数据库号
BROKER_URL = 'redis://localhost:6379/0'
#celery结果返回,可用于跟踪结果
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
 
#celery内容等消息的格式设置
CELERY_ACCEPT_CONTENT = ['application/json',]
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
 
#celery时区设置,使用settings中TIME_ZONE同样的时区
CELERY_TIMEZONE = TIME_ZONE

更多配置参考:http://docs.celeryproject.org/en/latest/userguide/configuration.html

d.把耗时任务丢给celery处理

上面views.py中有个耗时任务sendmail。在myapp应用中新建文件tasks.py,将sendmail方法剪切到该文件中并用定义为celery任务。tasks.py文件如下代码:

#celeryProject是当前django的项目名
from celeryProject import celery_app
import time
 
@celery_app.task
def sendmail(email):
    print('start send email to %s' % email)
    time.sleep(5) #休息5秒
    print('success')
    return True

另外原先的views.py修改如下:

from django.shortcuts import render
from django.http import HttpResponse
 
from .models import Blog
from .tasks import sendmail #引用tasks.py文件的中sendmail方法
import json
 
def home(request):
    #耗时任务,发送邮件(用delay执行方法)
    sendmail.delay('test@test.com')
 
    #其他行为
    data = list(Blog.objects.values('caption'))
    return HttpResponse(json.dumps(data), content_type = 'application/json')

e.本地启动celery并测试

启动celery之前,确保已经安装redis和启动redis服务,可参考Redis在CentOS7和Windows的安装过程

本地开发环境运行redis-cli看是否可以正常连接,若不行,再手工执行redis-server命令并保持窗口即可。

接着,启动celery worker。这个worker是用于异步执行任务的“工作者”。进入manage.py文件所在的目录,执行如下命令:

celery -A celeryProject worker -l info

若你安装的Celery版本是4.X以上的,并且在windows运行,则命令为:

celery -A celeryProject worker -l info -P gevent

 出现如下窗口和消息,则正常执行。

 

最后,再启动django服务器。这个大家熟悉的python manage.py runserver。

打开首页,可以发现没有5秒等待立即得到首页内容。查看celery worker,可看到执行sendmail方法的消息。

 

 

__init__.py:初始化Celery以及加载配置文件

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from celery import Celery
app = Celery('project')                                # 创建 Celery 实例
app.config_from_object('project.config')               # 加载配置模块

config.py:  Celery相关配置文件,更多配置参考:http://docs.celeryproject.org/en/latest/userguide/configuration.html

复制代码
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

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' # 结果序列化方案

CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间

CELERY_TIMEZONE='Asia/Shanghai'   # 时区配置

CELERY_IMPORTS = (     # 指定导入的任务模块,可以指定多个
    'project.tasks',
)
复制代码

tasks.py :任务定义文件

复制代码
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

from project import app
@app.task
def show_name(name):
    return name
复制代码

启动Worker:

celery worker -A project -l info

 

posted on 2019-08-17 15:14  xufat  阅读(246)  评论(0)    收藏  举报

导航

/* 返回顶部代码 */ TOP