文章目录

  • Celery概述
  • Celery架构
  • celery 组件
  • 使用场景
  • Celery使用
  • 基本使用
  • 多任务结构
  • 使用配置
  • Celery执行定时任务
  • 设定时间让celery执行一个任务
  • 类似于contab的定时任务
  • Django中使用


Celery概述

Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。Celery 本身不是任务队列, 是管理分布式任务队列的工具. 它封装了操作常见任务队列的各种操作, 我们使用它可以快速进行任务队列的使用与管理.

Celery架构

Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。

消息中间件

Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等

任务执行单元

Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。

任务结果存储

Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等

celery 组件

  • Celery Beat : 任务调度器. Beat 进程会读取配置文件的内容, 周期性的将配置中到期需要执行的任务发送给任务队列.
  • Celery Worker : 执行任务的消费者, 通常会在多台服务器运行多个消费者, 提高运行效率.
  • Broker : 消息代理, 队列本身. 也称为消息中间件. 接受任务生产者发送过来的任务消息, 存进队列再按序分发给任务消费方(通常是消息队列或者数据库).
  • Producer : 任务生产者. 调用 Celery API , 函数或者装饰器, 而产生任务并交给任务队列处理的都是任务生产者.
  • Result Backend : 任务处理完成之后保存状态信息和结果, 以供查询.

分布式计算flink特点 分布式celery_redis

使用场景

异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理、清理/设置缓存等等

定时任务:定时执行某件事情,比如每天数据统计

Celery使用

首先要安装框架:

pip3 install celery

接下来的操作中使用的消息中间件和结果存储都是用的是Redis,所以确保已经安装好Redis

基本使用

首先创建一个py文件: celery_task .py

from celery import Celery
 
# redis没有密码时
# 指定broker(消息中间件)
broker = 'redis://127.0.0.1:6379/0'
# backend(结果存储)
backend = 'redis://127.0.0.1:6379/1'

# redis有密码时
- broker='redis://:密码@127.0.0.1:6379/0'
- backend='redis://:密码@127.0.0.1:6379/1'
 
# 实例化产生一个Celery对象,指定名字test
app = Celery('test', broker=broker, backend=backend)

# 任务其实就是函数
# 需要用一个装饰器装饰,表示该任务是被celery管理的,且可用celery执行
@app.task
def add(x, y):
    import time
    time.sleep(10)
    return x+y

接下来创建py文件:add_task.py,用来进行调用任务

# 用于提交任务的py文件
import celery_task
# 提交任务到消息队列中,只是把任务提交到消息队列中,并没有执行
# 通过delay来将任务添加到worker中
ret=celery_task_s1.add.delay(3,4) # 返回结果是任务的id号:a5ea035f-0cc3-44ba-b334-f5d7c7ce681d
print(ret) # 对于返回的结果需要进行保存,用来取出任务的结果

当我们运行上面的py文件是就会把任务添加到队列中。

接下来启动worker去执行任务,有两种方式:

1、创建py文件:run.py,执行任务

from celery_task import app
if __name__ == '__main__':
    app.worker_main()
    # cel.worker_main(argv=['--loglevel=info')

2、在项目文件夹中执行启动命令(celery_task是任务模块的名字)

linux:celery worker -A celery_app_task -l info

各个参数含义:

  • worker: 代表第启动的角色是work当然还有beat等其他角色;
  • -A :项目路径,这里我的目录是project
  • -l:启动的日志级别,更多参数使用celery --help查看

因为在celery不支持windows,在windows中执行命令时要增加-P eventlet,要安装eventlet模块。

windows:celery worker -A celery_app_task -l info -P eventlet

最后创建py文件:result.py,查看任务执行结果

from celery.result import AsyncResult
from celery_app_task import cel

async = AsyncResult(id="a5ea035f-0cc3-44ba-b334-f5d7c7ce681d", app=cel)

if async.successful():
    result = async.get()
    print(result)
    # result.forget() # 将结果删除
elif async.failed():
    print('执行失败')
elif async.status == 'PENDING':
    print('任务等待中被执行')
elif async.status == 'RETRY':
    print('任务异常后正在重试')
elif async.status == 'STARTED':
    print('任务已经开始被执行')

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

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

前面使用 delay()来调用任务, 也可以使用apply_async() 方法来调用任务。事实上,delay 方法封装了 apply_asyncdelay 是使用 apply_async 的快捷方式。apply_async 支持更多的参数,apply_async 常用的参数如下:

  • countdown:指定多少秒后执行任务
task1.apply_async(args=(2, 3), countdown=5)    # 5 秒后执行任务
  • eta (estimated time of arrival):指定任务被调度的具体时间,参数类型是 datetime
from datetime import datetime, timedelta
# 当前 UTC 时间再加 10 秒后执行任务
task1.multiply.apply_async(args=[3, 7], eta=datetime.utcnow() + timedelta(seconds=10))
  • expires:任务过期时间,参数类型可以是 int,也可以是 datetime
task1.multiply.apply_async(args=[3, 7], expires=10)    # 10 秒后过期

更多的参数列表可以在官方文档中查看。

多任务结构

pro_cel
    ├── celery_task# celery相关文件夹
    │   ├── celery.py   # celery连接和配置相关文件,必须叫这个名字
    │   └── tasks1.py    #  所有任务函数
    │	└── tasks2.py    #  所有任务函数
    ├── check_result.py # 检查结果
    └── send_task.py    # 触发任务

celery.py

from celery import Celery

cel = Celery('celery_demo',
             broker='redis://127.0.0.1:6379/0',
             backend='redis://127.0.0.1:6379/1',
             # 包含以下两个任务文件,去相应的py文件中找任务,对多个任务做分类
             include=['celery_task.tasks1',
                      'celery_task.tasks2'
                      ])

# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False

tasks1.py

import time
from celery_task.celery import cel
@cel.task
def test_celery1(res):
    time.sleep(5)
    return "test_celery任务结果:%s"%res

tasks2.py

import time
from celery_task.celery import cel
@cel.task
def test_celery2(res):
    time.sleep(5)
    return "test_celery2任务结果:%s"%res

send_task.py

from celery_task.tasks1 import test_celery1
from celery_task.tasks2 import test_celery2

# 立即告知celery去执行test_celery任务,并传入一个参数
result = test_celery1.delay('第一个的执行')
print(result.id)
result = test_celery2.delay('第二个的执行')
print(result.id)

check_result.py

from celery.result import AsyncResult
from celery_task.celery import cel

async = AsyncResult(id="08eb2778-24e1-44e4-a54b-56990b3519ef", app=cel)

if async.successful():
    result = async.get()
    print(result)
    # result.forget() # 将结果删除,执行完成,结果不会自动删除
    # async.revoke(terminate=True)  # 无论现在是什么时候,都要终止
    # async.revoke(terminate=False) # 如果任务还没有开始执行呢,那么就可以终止。
elif async.failed():
    print('执行失败')
elif async.status == 'PENDING':
    print('任务等待中被执行')
elif async.status == 'RETRY':
    print('任务异常后正在重试')
elif async.status == 'STARTED':
    print('任务已经开始被执行')

执行顺序:

  • 添加任务(执行send_task.py),
  • 开启work:celery worker -A celery_task -l info -P eventlet,
  • 检查任务执行结果(执行check_result.py)

使用配置

在上面的例子中,我们直接把 Broker 和 Backend 的配置写在了程序当中,更好的做法是将配置项统一写入到一个配置文件中,通常我们将该文件命名为 celeryconfig.py。Celery 的配置比较多,可以在官方文档查询每个配置项的含义。

下面,我们再看一个例子。项目结构如下:

celery_demo                    # 项目根目录
    ├── celery_app             # 存放 celery 相关文件
    │   ├── __init__.py
    │   ├── celeryconfig.py    # 配置文件
    │   ├── task1.py           # 任务文件 1
    │   └── task2.py           # 任务文件 2
    └── client.py              # 应用程序

__init__.py 代码如下:

# -*- coding: utf-8 -*-

from celery import Celery

app = Celery('demo')                                # 创建 Celery 实例
app.config_from_object('celery_app.celeryconfig')   # 通过 Celery 实例加载配置模块

celeryconfig.py 代码如下:

BROKER_URL = 'redis://127.0.0.1:6379'               # 指定 Broker
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'  # 指定 Backend

CELERY_TIMEZONE='Asia/Shanghai'                     # 指定时区,默认是 UTC
# CELERY_TIMEZONE='UTC'                             

CELERY_IMPORTS = (                                  # 指定导入的任务模块
    'celery_app.task1',
    'celery_app.task2'
)

其他模块代码一致。

Celery执行定时任务

设定时间让celery执行一个任务

add_task.py

from celery_task import add
from datetime import datetime,timedelta

```
# 第一种获取时间
v1 = datetime(2019, 2, 13, 18, 19, 56) # 固定设置一个时间执行任务
print(v1)
# v1.timestamp()将上面的时间转换为时间戳
v2 = datetime.utcfromtimestamp(v1.timestamp())
print(v2)
# 取出要执行任务的时间对象,调用apply_async方法,args是参数,eta是执行的时间
result = add.apply_async(args=[1, 3], eta=v2)
print(result.id)
```

# 第二种获取时间
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
time_delay = timedelta(seconds=10) # 可以设置延时几天,几分,几秒后执行
task_time = utc_ctime + time_delay

# 使用apply_async并设定时间
result = add.apply_async(args=[4, 3], eta=task_time) 
print(result.id)

类似于contab的定时任务

下面的场景是每天的什么时候,或者每月的某个时候定时做某些工作。比如:秒杀推送信息,每天某个点同步redis的数据到数据库等。

多任务结构中celery.py修改如下

from datetime import timedelta
from celery import Celery
from celery.schedules import crontab

cel = Celery('tasks',
             broker='redis://127.0.0.1:6379/1',
             backend='redis://127.0.0.1:6379/2', 
             include=['celery_task.tasks1',
                      'celery_task.tasks2',
                     ])
cel.conf.timezone = 'Asia/Shanghai'
cel.conf.enable_utc = False

cel.conf.beat_schedule = {
    # 名字随意命名
    'add-every-10-seconds': {
        # 执行tasks1下的test_celery函数
        'task': 'celery_task.tasks1.test_celery',
        # 每隔2秒执行一次
        # 'schedule': 1.0,
        # 'schedule': crontab(minute="*/1"),
        'schedule': timedelta(seconds=2),
        # 传递参数
        'args': ('test',)
    },
    # 'add-every-12-seconds': {
    #     'task': 'celery_task.tasks1.test_celery',
    #     每年4月11号,8点42分执行
    #     'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
    #     'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
    #     'args': (16, 16)
    # },
}

启动beat用来发送任务:celery beat -A celery_task -l info

启动work用来执行任务:celery worker -A celery_task -l info -P eventlet

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

Example

Meaning

crontab()

每分钟执行

crontab(minute=0,hour=0)

每天0点执行

crontab(minute=0,hour='*/3')

每3小时执行: midnight, 3am, 6am, 9am, noon, 3pm, 6pm, 9pm.

crontab(minute=0,``hour='0,3,6,9,12,15,18,21')

同上

crontab(minute='*/15')

每15分钟执行

crontab(day_of_week='sunday')

周天的每分钟执行

crontab(minute='*',``hour='*',``day_of_week='sun')

同上

crontab(minute='*/10',``hour='3,17,22',``day_of_week='thu,fri')

周三、五,3-4 am, 5-6 pm, and 10-11 pm,每10分钟执行

crontab(minute=0,hour='*/2,*/3')

每小时/2和每小时/3,执行

crontab(minute=0, hour='*/5')

每小时/5,执行

crontab(minute=0, hour='*/3,8-17')

每小时/3,8am-5pm,执行

crontab(0,0,day_of_month='2')

每个月第二天执行。

crontab(0,0,``day_of_month='2-30/3')

每个偶数日执行。

crontab(0,0,``day_of_month='1-7,15-21')

在本月的第一周和第三周执行。

crontab(0,0,day_of_month='11',``month_of_year='5')

每年五月十一日执行。

crontab(0,0,``month_of_year='*/3')

每个季度的第一个月执行。

Django中使用

django中虽然提供了django-celery,但是对于django和celery的版本的匹配度要求很高,所以我们还是使用原生的celery。

首先我们把之前的celery_task文件夹可以直接拷贝到我们项目的目录下使用,最主要还是那个celery.py配置,其他的任务根据项目需求来就行了。

view.py

from django.shortcuts import HttpResponse
 
def index(request):
	from celery_task.tasks1 import test_celery
    # 提交任务,返回id
    res = test_celery.delay('Django中执行')
    return HttpResponse(res.id)
 
def check_result(request):
    res = request.GET.get('id')
    async = AsyncResult(id=res, app=app)
    from celery_task.user_task import user_add
    from celery.result import AsyncResult
    from celery_task.celery import cel
    if async.successful():
        result = async.get()
        return HttpResponse(result)

在Django中执行和前面一样要启动worker。

注意:在实际的项目中,可能要在任务函数中去操作数据库,但是上面只是将对应的文件夹直接拷贝到里Django的项目中,Django项目是无法识别的,所以要在对应的文件中添加下面的语句。

# 单独脚本调用Django内容时,需配置脚本的环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "untitled15.settings")
import django
django.setup()

上面的语句可以让Django中的py文件当作脚本进行执行,并且可以使用Django的环境。