一、Celery介绍及基本使用

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

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

 

Celery 在执行任务时需要通过一个消息中间件来接收和发送任务消息,以及存储任务结果, 一般使用rabbitMQ or Redis,后面会讲

1.1 Celery有以下优点:

  1. 简单:一单熟悉了celery的工作流程后,配置和使用还是比较简单的
  2. 高可用:当任务执行失败或执行过程中发生连接中断,celery 会自动尝试重新执行任务
  3. 快速:一个单进程的celery每分钟可处理上百万个任务
  4. 灵活: 几乎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
简单demo

 

 

 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()
vim celery.py

 

 

 

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)
vim tasks.py

 

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的任务
后台启动worker

 

 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: 添加一条定时任务
periodic_task.py

 

启动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'
View Code

更多定时配置方式如下:

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.
crontab(minute=0,
hour='0,3,6,9,12,15,18,21')
Same as previous.
crontab(minute='*/15') Execute every 15 minutes.
crontab(day_of_week='sunday') Execute every minute (!) at Sundays.
crontab(minute='*',
hour='*',day_of_week='sun')
Same as previous.
crontab(minute='*/10',
hour='3,17,22',day_of_week='thu,fri')
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.
crontab(0, 0,
day_of_month='2-30/3')
Execute on every even numbered day.
crontab(0, 0,
day_of_month='1-7,15-21')
Execute on the first and third weeks of the month.
crontab(0, 0,day_of_month='11',
month_of_year='5')
Execute on the eleventh of May every year.
crontab(0, 0,
month_of_year='*/3')
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))
celery.py

 

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']
__init__.py

 

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'
setting.py

 

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)
tasks.py

 

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),

]
url.py

 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)
view:index

  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)
view:task_res

 

 

 

将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
celery

 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]'
email

 

 

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)
tasks发送邮件任务

 

 

3)url.py 注册url 、邮箱验证url:

path('register/', views.register),    #用于注册
re_path(r'^register/(\w+)/$', views.register),   #用于异步邮件发送后,验证邮箱
url.py

 

 

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")
register

 邮箱验证:

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就完成啦!

 

posted on 2018-08-13 00:13  Eric_nan  阅读(210)  评论(0编辑  收藏  举报