tiger运维走向Python开发_Celery分布式任务队列_week25_day84

 

内容概要

  Celery介绍和基本使用

  在项目中如何使用celery

  启用多个workers

  Celery 定时任务

  与django结合

  通过django配置celery periodic task

 

一、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, 仅需配置一行就可以

broker_url = 'amqp://guest:guest@localhost:5672//'

rabbitMQ 没装的话请装一下,安装看这里  http://docs.celeryproject.org/en/latest/getting-started/brokers/rabbitmq.html#id3

使用Redis做broker也可以

安装redis组件

$ pip install -U "celery[redis]"

配置很容易,只需配置Redis数据库的位置:

app.conf.broker_url = 'redis://localhost:6379/0'

其中URL的格式为:

Redis://:password @ hostname:port / db_number

方案之后的所有字段都是可选的,并且将默认使用端口6379上的localhost,使用数据库0。

 

如果想获取每个任务的执行结果,还需要配置一下把任务结果存在哪

如果还要存储状态并返回Redis中的执行结果,还需要配置:

app.conf.result_backend = 'redis://localhost:6379/0'

 

1. 3 Celery的使用

安装celery模块

$ pip install celery

创建一个celery application 用来定义你的任务列表

创建一个任务文件task_lxh.py

#__author: Tiger lee
# -*- coding:utf-8 -*-
#date: 2017/2/22
#IN Python 3.5

from celery import Celery

app = Celery('task',
             broker='redis://:password@host',
             backend='redis://:password@host') # password和host均为redis的password和redis运行的机器IP

@app.task
def add(x, y):
    print("running...", x, y)
    return x + y

启动Celery Worker来开始监听并执行任务

$ celery -A task_lxh worker --loglevel=info

调用任务

再打开一个终端, 进行命令行模式,调用任务

>>> from tasks import add
>>> add.delay(4, 4)

看你的worker终端会显示收到 一个任务,此时你想看任务结果的话,需要在调用 任务时 赋值个变量

>>> result = add.delay(4, 4)

ready() 方法返回任务是否已完成处理:

>>> result.ready()
False

您可以等待结果完成,但这很少使用,因为它将异步调用转换为同步调用:

>>> result.get(timeout = 18

如果任务引发异常,get()将重新引发异常,但您可以通过指定propagate参数来覆盖此异常:

>>> result.get(propagate = False)

如果任务引发了异常,您还可以访问原始追溯:

>>> result.traceback

 

二、在项目中如何使用celery 

可以把celery配置成一个应用

目录格式如下

proj/__init__.py
    /celery.py
    /tasks.py

proj/celery.py内容

from __future__ import absolute_import, unicode_literals
from celery import Celery
 
app = Celery('proj',
             broker='amqp://',
             backend='amqp://',
             include=['proj.tasks'])
 
# Optional configuration, see the application user guide.
app.conf.update(
    result_expires=3600,
)
 
if __name__ == '__main__':
    app.start()

proj/tasks.py中的内容

from __future__ import absolute_import, unicode_literals
from .celery import app

@app.task
def add(x, y):
    return x + y

@app.task
def mul(x, y):
    return x * y

@app.task
def xsum(numbers):
    return sum(numbers)

启动worker 

$ celery -A proj worker -l info

输出

-------------- celery@Alexs-MacBook-Pro.local v4.0.2 (latentcall)
---- **** -----
--- * ***  * -- Darwin-15.6.0-x86_64-i386-64bit 2017-01-26 21:50:24
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app:         proj:0x103a020f0
- ** ---------- .> transport:   redis://localhost:6379//
- ** ---------- .> results:     redis://localhost/
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery

celery远程执行命令并返回结果步骤:

1 . pip install celery
2. 启动redis
3. 创建celry app
   task_lxh.py
   
    from celery import Celery
    import subprocess,time
    app = Celery('task',
                 broker='redis://:password@host',
                 backend='redis://:password@host') # password和host均为redis的password和redis运行的机器IP

    @app.task
    def add(x, y):
        print("running...", x, y)
        return x + y

    @app.task
    def run_cmd(cmd):
        print('run cmd ...',cmd)
        time.sleep(5)
        cmd_obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        return cmd_obj.stdout.read().decode("utf-8")

4. celery -A task workery --loglevel=info
5. 生成任务
   python3
   >>>:import task_lxh
   >>>:r = task_lxh.cmd_run.delay("df")
   >>>:r.get()

 

后台启动worker

在后台

在生产环境中,您希望在后台运行worker,这在守护进程教程中有详细描述。

守护进程脚本使用celery multi命令在后台启动一个或多个工作线程:

$ celery multi start w1 -A proj -l info
celery multi v4.0.0 (latentcall)
> Starting nodes...
    > w1.halcyon.local: OK
You can restart it too:

$ celery  multi restart w1 -A proj -l info
celery multi v4.0.0 (latentcall)
> Stopping nodes...
    > w1.halcyon.local: TERM -> 64024
> Waiting for 1 node.....
    > w1.halcyon.local: OK
> Restarting node w1.halcyon.local: OK
celery multi v4.0.0 (latentcall)
> Stopping nodes...
    > w1.halcyon.local: TERM -> 64052

或停止:

$ celery multi stop w1 -A proj -l info

stop命令是异步的,因此它不会等待worker关闭。 您可能希望使用stopwait命令,这样可以确保在退出之前完成所有当前正在执行的任务:

$ celery multi stopwait w1 -A proj -l info

 

三、Celery 定时任务

 celery支持定时任务,设定好任务的执行时间,celery就会定时自动帮你执行, 这个定时任务模块叫celery beat

写一个脚本 叫periodic_task.py
from celery import Celery
from celery.schedules import crontab
 
app = Celery()
 
@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 30 seconds
    sender.add_periodic_task(30.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 会添加一条定时任务

上面是通过调用函数添加定时任务,也可以像写配置文件 一样的形式添加, 下面是每30s执行的任务

app.conf.beat_schedule = {
    'add-every-30-seconds': {
        'task': 'tasks.add',
        'schedule': 30.0,
        'args': (16, 16)
    },
}
app.conf.timezone = 'UTC'

任务添加好了,需要让celery单独启动一个进程来定时发起这些任务, 注意, 这里是发起任务,不是执行,这个进程只会不断的去检查你的任务计划, 每发现有任务需要执行了,就发起一个任务调用消息,交给celery worker去执行

启动任务调度器 celery beat

$ celery -A periodic_task beat

输出like below

celery beat v4.0.2 (latentcall) is starting.
__    -    ... __   -        _
LocalTime -> 2017-02-08 18:39:31
Configuration ->
    . broker -> redis://localhost:6379//
    . loader -> celery.loaders.app.AppLoader
    . scheduler -> celery.beat.PersistentScheduler
    . db -> celerybeat-schedule
    . logfile -> [stderr]@%WARNING
    . maxinterval -> 5.00 minutes (300s)

此时还差一步,就是还需要启动一个worker,负责执行celery beat发起的任务

启动celery worker来执行任务

$ celery -A periodic_task worker
  
 -------------- celery@Alexs-MacBook-Pro.local v4.0.2 (latentcall)
---- **** -----
--- * ***  * -- Darwin-15.6.0-x86_64-i386-64bit 2017-02-08 18:42:08
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app:         tasks:0x104d420b8
- ** ---------- .> transport:   redis://localhost:6379//
- ** ---------- .> results:     redis://localhost/
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery

好啦,此时观察worker的输出,是不是每隔一小会,就会执行一次定时任务呢!

注意:Beat需要将任务的最后运行时间存储在本地数据库文件(默认情况下名为celerybeat-schedule)中,因此它需要访问在当前目录中写入,或者您可以为此文件指定自定义位置:

$ celery -A periodic_task beat -s /home/celery/var/run/celerybeat-schedule

更复杂的定时配置  

上面的定时任务比较简单,只是每多少s执行一个任务,但如果你想要每周一三五的早上8点给你发邮件怎么办呢?哈,其实也简单,用crontab功能,跟linux自带的crontab功能是一样的,可以个性化定制任务执行时间

linux crontab详解:  http://www.cnblogs.com/peida/archive/2013/01/08/2850483.html 

 

from celery.schedules import crontab
 
app.conf.beat_schedule = {
    # Executes every Monday morning at 7:30 a.m.
    'add-every-monday-morning': {
        'task': 'tasks.add',
        'schedule': crontab(hour=7, minute=30, day_of_week=1),
        'args': (16, 16),
    },
}

上面的这条意思是每周1的早上7.30执行tasks.add任务

还有更多定时配置方式如下:

上面能满足你绝大多数定时任务需求了,甚至还能根据潮起潮落来配置定时任务, 具体看 http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#solar-schedules

 

四、最佳实践之与django结合 

django 可以轻松跟celery结合实现异步任务,只需简单配置即可

如果你有一个现有的Django项目布局,如:

- proj/
  - proj/__init__.py
  - proj/settings.py
  - proj/urls.py
- manage.py

那么推荐的方法是创建一个新的定义Celery实例的proj/proj/celery.py模块:

file: proj/proj/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.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')
 
app = Celery('proj')
 
# 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()
 
 
@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))

然后你需要在你的proj/proj/__init__.py模块导入这个应用程序。 确保在Django启动时加载应用程序,以便@shared_task装饰器(稍后提到)将使用它:

file: proj/proj/__init__.py

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.
from .celery import app as celery_app
 
__all__ = ['celery_app']

请注意,此示例项目布局适合较大的项目,对于简单项目,您可以使用定义应用程序和任务的单个包含的模块,如“使用Celery的第一步”教程。

让我们分解在第一个模块中发生了什么,首先从未来导入绝对导入,以便我们的celery.py模块不会与库冲突:

from __future__ import absolute_import

然后为celery命令行程序设置默认的DJANGO_SETTINGS_MODULE环境变量:

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')

这行可以不需要,但它可以避免你总是将设置模块传递到celery程序。 它必须始终在创建应用程序实例之前,如:

app = Celery('proj')

这是我们的库的实例。

 

我们还将Django设置模块添加为Celery的配置源。 这意味着您不必使用多个配置文件,而是直接从Django设置配置Celery; 但你也可以分开他们,如果需要。

大写名称空间表示所有Celery配置选项必须以大写而不是小写形式指定,并以CELERY_开头,例如,task_always_eager`设置将变为CELERY_TASK_ALWAYS_EAGER,并且broker_url设置将变为CELERY_BROKER_URL。

你可以直接在这里传递对象,但使用字符串是更好的,因为那时工人不必序列化对象。

app.config_from_object('django.conf:settings', namespace='CELERY')

接下来,可重用应用程序的常见做法是在单独的tasks.pymodule中定义所有任务,并且Celery有自动发现这些模块的方法:

app.autodiscover_tasks()

使用Celery之上的行将根据tasks.py约定从所有已安装的应用程序中自动发现任务:

- app1/
    - tasks.py
    - models.py
- app2/
    - tasks.py
    - models.py

最后,debug_task示例是转储其自己的请求信息的任务。 这是使用Celery 3.1中引入的新的bind = True任务选项来轻松引用当前任务实例。

然后在具体的app里的tasks.py里写你的任务

# Create your tasks here
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
 
@shared_task
def xsum(numbers):
    return sum(numbers)

在你的django views里调用celery task

from django.shortcuts import render,HttpResponse
 
# Create your views here.
 
from  bernard import tasks
 
def task_test(request):
 
    res = tasks.add.delay(228,24)
    print("start running task")
    print("async task res",res.get() )
 
    return HttpResponse('res %s'%res.get())

...

 

 

实例: 将Django项目放到linux机器上运行,实现django与linux上的celery协作

 

上传Django项目到linux上

通过celery来启动Django

 

启动成功的界面

在linux上面修改Django项目下的APP下的tasks.py

在每一个任务里面加上time.sleep(5)

在index获取到task_id之后,通过ID来取任务的状态,交给views返回到worker进行运算并返回结果SUCCESS

Django目录结构与需要修改的py文件如下:

相关代码:

__init__.py

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.
# 这里什么都不用改
from .celery import app as celery_app

__all__ = ['celery_app']
__init__.py

selery.py

#__author: Tiger lee
# -*- coding:utf-8 -*-
#date: 2017/2/22
#IN Python 3.5

from __future__ import absolute_import, unicode_literals
import os
from celery import Celery

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Celery_Django.settings') # 设置celery的环境变量

app = Celery('proj')

# 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.(名字必须以CELERY_开头)
app.config_from_object('django.conf:settings', namespace='CELERY')

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))
selery.py

settings.py需要添加连接redis的代码

CELERY_BROKER_URL = "redis://:lxh123456@192.168.10.106"
CELERY_RESULT_BACKEND = "redis://:lxh123456@192.168.10.106"
settings.py

urls.py

from django.conf.urls import url
from django.contrib import admin
from app_lxh import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index$', views.index),
    url(r'^task_res/(\w+)/$', views.task_res),
]
urls.py

APP下的文件:

tasks.py

#__author: Tiger lee
# -*- coding:utf-8 -*-
#date: 2017/2/22
#IN Python 3.5

# Create your tasks here
from __future__ import absolute_import, unicode_literals
from celery import shared_task

@shared_task
def add(x, y):
    print ("running task add") #这行在把django放到linux里面需要添加
    time.sleep(10) #这行在把django放到linux里面需要添加
    return x + y

@shared_task
def mul(x, y):
    return x * y

@shared_task
def xsum(numbers):
    return sum(numbers)
tasks.py

views.py

from django.shortcuts import render, HttpResponse
from app_lxh import tasks
from celery.result import AsyncResult

# Create your views here.

def index(request):
    res = tasks.add.delay(5, 999)
    print ("res:", res)
    return HttpResponse(res.task_id)

def task_res(request):
    result = AsyncResult(id="c9fbf4d3-abaf-4eb2-a819-2ea9a1a27582")
    return HttpResponse(result.status)
views.py

 

五、在django中使用计划任务功能

有一个django-celery-beat扩展,在Django数据库中存储计划,并提供了一个方便的管理界面来管理运行时的周期性任务。

要安装和使用此扩展程序:

1  使用pip安装软件包:

$ pip install django-celery-beat

2  将django_celery_beat模块添加到您的Django项目的settings.py中的INSTALLED_APPS:

INSTALLED_APPS = (
        ...,
        'django_celery_beat',
    )

注意:模块名称中没有破折号,只有下划线。

3  应用Django数据库迁移,以便创建必要的表:

$ python manage.py migrate

4  使用django调度程序启动celery beat服务:

$ celery -A proj beat -l info -S django

5  访问Django-Admin界面以设置一些定期任务。

 

在admin页面里,有3张表

配置完长这样

此时启动你的celery beat 和worker,会发现每隔2分钟,beat会发起一个任务消息让worker执行scp_task任务

 

注意,经测试,每添加或修改一个任务,celery beat都需要重启一次,要不然新的配置不会被celery beat进程读到

 

posted @ 2017-02-22 14:46  tiger_li  阅读(298)  评论(0)    收藏  举报