数据中台系列3:celery 安装与使用 window 篇

1、概述

celery 是一个使用 python 开发的分布式异步任务队列管理系统。
功能介绍可百度一下,这里不赘述。
Celery的架构由三部分组成,消息中间件(broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。
celery 本身并不提供 broker 和 backend 功能,而是由 RabbitMQ 和 Redis 提供(backend 功能也可以由其他组件提供,比如数据库),因此要想使用 celery,就要先把 RabbitMQ 或者 Redis 安装部署好,相关内容见:
数据中台系列1:redis 安装使用之 windows 篇
数据中台系列2:rabbitMQ 安装使用之 window 篇

2、安装

pip install celery

celery 要在 window 环境下正常使用,还需要 eventlet 或者 gevnt 库的支持,因此

pip install eventlet

3、场景1:本机运行的异步任务 hello world

3-1、场景描述

a、broker、backend 都在本机,默认都采用 redis
b、本机启动 worker 服务(进程1)
c、本机主动生成任务,通过本机的 broker,由本机的 worker 消费后,结果输出到本机的 backend。

3-2、show me the code

本机运行的异步任务场景,如下建立文件:

x:\xxx\celery_tasks
             │ 	 	celery_app.py
             │  		config.py
             │
             └─tasks
 		             task1.py

其中:

# 文件名:celery_tasks.celery_app.py
from celery import Celery

app = Celery("domodo")
app.config_from_object('celery_tasks.config')
# 文件名:celery_tasks.config.py,配置文件
from datetime import timedelta

broker_url = "redis://127.0.0.1:6379/1"
result_backend = "redis://127.0.0.1:6379/2"
# task_serializer = 'json'
# result_serializer = 'json'
# accept_content = ['json']
enable_utc = True
timezone = 'Asia/Shanghai'

# 需要执行的任务
imports = ('celery_tasks.tasks.tasks1',)
# 文件名:celery_tasks.tasks.task1.py
# 定义一个 MyTask 类,根据任务的不同执行作不同的应对,这样它就能做很多事情,不只是将结果存入 backend 那么单一。
# 任务例子
from abc import ABC
from celery import Task
from celery_tasks.celery_app import app

class MyTask(Task, ABC):
    """
    自定义一个类,继承自celery.Task
    exc: 失败时的错误的类型;
    task_id: 任务的id;
    args: 任务函数的位置参数;
    kwargs: 任务函数的关键字参数;
    einfo: 失败时的异常详细信息;
    retval: 任务成功执行的返回值;
    """
    def on_failure(self, exc, task_id, args, kwargs, einfo):
        """任务失败时执行"""

    def on_success(self, retval, task_id, args, kwargs):
        """任务成功时执行"""
        print("任务执行成功")

    def on_retry(self, exc, task_id, args, kwargs, einfo):
        """任务重试时执行"""

@app.task(base=MyTask)
def say_hello(x, y):
    return print(f"hello world {x + y}")
3-3、运行

1)在命令行窗口1中,进入 xxx 目录,并执行以下命令,启动 celery,等待任务执行指令

celery -A celery_app worker -l info -P eventlet -c 10

如果正常运行,命令行窗口中会出现以下内容:


 -------------- celery@xxxxxxxxxxx v5.3.1 (emerald-rush)
--- ***** -----
-- ******* ---- Windows-10-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         xxxx:xxxxxxxx
- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 10 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . say_hello

[2023-07-25 01:28:57,898: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1
[2023-07-25 01:28:57,901: INFO/MainProcess] mingle: searching for neighbors
[2023-07-25 01:28:58,915: INFO/MainProcess] mingle: all alone
[2023-07-25 01:28:58,921: INFO/MainProcess] pidbox: Connected to redis://127.0.0.1:6379/1.
[2023-07-25 01:28:58,925: INFO/MainProcess] celery@xxxxxxxxxxxxxx ready.

最后有 ready 字样,就表示 celery 服务运行正常,并等待任务运行指令。

2)在命令行窗口2中进入 python 后,执行以下命令,发出执行 say_hello 任务的指令:

>>> from tasks.tasks1 import say_hello
>>> say_hello.delay('alice')
<AsyncResult: 55999c5b-104a-4701-bfe9-a1490f9ed26c>
>>>

此时在命令行窗口1中的 celery 服务会接收到任务指令,执行并在窗口1中显示相关信息(如下),成功执行完毕后,根据 say_hello 装饰器中的参数 base设定,回调 on_success 方法,发出 “任务执行成功” 的信息。

[2023-07-25 01:44:08,907: INFO/MainProcess] Task say_hello[0081398c-c72f-401b-9c82-6d2f08241c53] received
[2023-07-25 01:44:08,907: WARNING/MainProcess] hello, alice
[2023-07-25 01:44:08,950: WARNING/MainProcess] 任务执行成功
[2023-07-25 01:44:08,950: INFO/MainProcess] Task say_hello[0081398c-c72f-401b-9c82-6d2f08241c53] succeeded in 0.045999999972991645s: None

由此,整个任务执行完毕。

4、场景2:本机运行的定时任务 hello world

相比第3点,本项希望能在指定条件下执行指定代码,或者重复执行指定代码。
以下默认使用 redis 作为 broker 和 backend,并且已经启动了 redis 服务。

4-1、场景描述

a、broker、backend 都在本机,默认都采用 redis
b、本机启动 worker 服务(进程1)
c、本机启动定时器,按定时器被动生成任务,通过本机的 broker,由本机的 worker 消费后,结果输出到本机的 backend。

4-2、show me the code

本机运行的异步任务场景,如下建立文件:

x:\xxx\celery_tasks
             │ 	 	celery_app.py
             │  		config.py
             │
             └─tasks
 	             	task1.py

其中:
a、场景1的文件 celery_app.py, task1.py 没有改动。
b、场景1的文件 config.py 在末尾添加以下内容

# 文件名:celery_app.py

beat_schedule = {
    'say_hello': {
        'task': 'my_celery.tasks.tasks1.say_hello',  # 绑定的定时任务函数
        'schedule': 5,  # 每隔 2 秒执行一次
        # 'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),   # 每年4月11号,8点42分执行
        # 'schedule': crontab(minute='*/10',hour='3,17,22', day_of_week='thu,fri'),   # 每隔10分钟执行一次,仅限于周六日3-4 am, 5-6 pm, and 10-11 pm
        'args': ('张三',)  # 要传递的参数
    }
}
4-3、运行

1)在命令行窗口1中,进入 xxx 目录,并执行以下命令,启动 celery,等待任务执行指令

celery -A my_celery.celery_app worker -l info -P eventlet -c 10

如果正常运行,命令行窗口中会出现以下内容:


 -------------- celery@xxxxxxxxxxx v5.3.1 (emerald-rush)
--- ***** -----
-- ******* ---- Windows-10-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         xxxx:xxxxxxxx
- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 10 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . say_hello

[2023-07-25 01:28:57,898: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1
[2023-07-25 01:28:57,901: INFO/MainProcess] mingle: searching for neighbors
[2023-07-25 01:28:58,915: INFO/MainProcess] mingle: all alone
[2023-07-25 01:28:58,921: INFO/MainProcess] pidbox: Connected to redis://127.0.0.1:6379/1.
[2023-07-25 01:28:58,925: INFO/MainProcess] celery@xxxxxxxxxxxxxx ready.

最后有 ready 字样,就表示 celery 服务运行正常,并等待任务运行指令。

2)在命令行窗口2中,进入 xxx 目录后运行以下命令,启动调度器:

celery -A my_celery.celery_app beat

如果出现以下输出,则表示调度器启动正常,开始按设置的要求发出指令。

celery beat v5.3.1 (emerald-rush) is starting.
__    -    ... __   -        _
LocalTime -> 2023-07-30 23:41:11
Configuration ->
    . broker -> redis://127.0.0.1:6379/1
    . loader -> celery.loaders.app.AppLoader
    . scheduler -> celery.beat.PersistentScheduler
    . db -> celerybeat-schedule
    . logfile -> [stderr]@%WARNING
    . maxinterval -> 5.00 minutes (300s)

同时,在窗口1中,celery 的 worker 会定期收到指令并执行,从而反复产生类型以下的输出信息:

[2023-07-30 23:43:31,372: INFO/MainProcess] Task my_celery.tasks.tasks1.say_hello[db931cb3-9ead-4f52-9a40-f74045903401] received
[2023-07-30 23:43:31,415: WARNING/MainProcess] 任务执行成功
[2023-07-30 23:43:31,415: INFO/MainProcess] Task my_celery.tasks.tasks1.say_hello[db931cb3-9ead-4f52-9a40-f74045903401] succeeded in 0.046999999991385266s: 'hello, 张三'

至此定时任务设置完成。

ps:
使用 crontab 能够进行更加精细的定时任务设置,具体内容可自行查阅相关技术文档,或者等待以后本系列补充相关内容。

5、场景3:分布式部署 celery,多个生产者,一个消费者

5-1、场景描述

a、同一个局域网内的多机器部署 celery。其中,server1是任务消费者,server2、server3… 是任务生产者。
b、broker、backend 都在 server1,默认都采用 redis。
c、server1 启动 worker 服务(进程1)。任务的实现方法都在 server1 上。
d、每个生产者各自生成任务,使用消费者的 broker 和 backend,不保存具体的任务实现方法。
e、任务信息(任务名、参数),通过 server1 的 broker,传入消费者的队列中,server1 的 worker 获取并消费,结果统一输出到 server1 的 backend。

5-2、show me the code

具体来说,在消费者上的代码如下:

x:\xxx\celery_tasks
             │  		celery_config.py
             └─       tasks.py
# 文件名:tasks.py
from celery import Celery
from celery import Task

app = Celery('tasks')
app.config_from_object('celery_config')


class MyTask(Task):
    def on_success(self, retval, task_id, args, kwargs):
        print(f"任务 {task_id} 完成")

    def on_failure(self, exc, task_id, args, kwargs, einfo):
        pass


@app.task(base=MyTask)
def add(x, y):
    return f"x + y = {x + y}"


@app.task(base=MyTask)
def multiply(x, y):
    return f"x * y = {x * y}"
# 文件名:celery_config.py
from kombu import Queue
from kombu import Exchange

broker_url = r"redis://x.x.x.x:6379/5"
result_backend = r"redis://x.x.x.x:6379/6"
serializer = 'json'	  #  任务执行结果序列化方式
result_expires = 60 * 60 * 5    #  任务结果保存时间
enable_utc = True
timezone = "Asia/Shanghai"

worker_concurrency = 5  # worker并发数
worker_max_tasks_per_child = 5  # 每个worker最大执行任务数

task_queues = (
    Queue('priority_low', exchange=Exchange('priority', type='direct'), routing_key='priority_low'),
    Queue('priority_high', exchange=Exchange('priority', type='direct'), routing_key='priority_high'),
)
task_routes = ([
                   ('tasks.add', {'queue': 'priority_low'}),
                   ('tasks.multiply', {'queue': 'priority_high'}),
               ],)

在生产者上的代码:

x:\xxx\celery_tasks
             │  	celery_config.py
             └─     main.py
# 文件名:main.py
from celery import Celery
import celery_config

app = Celery('tasks', broker=celery_config.broker_url)
app.config_from_object(celery_config)
for i in range(5):
    print(f"第 {i} 次生成任务")
    app.send_task("tasks.add", queue=celery_config.task_queues[0], args=(i, i + 1))
    app.send_task("tasks.multiply", queue=celery_config.task_queues[1], args=(i, i + 1))
# 配置文件与消费者上的一模一样,这里不再列出。
5-3、运行

1)在消费者计算机的命令行窗口中启动 celery

celery -A tasks worker -l info -P eventlet -c 10

2)在生产者计算机的命令行窗口中(或者 IDE 里)直接运行 main.py

python main.py

6、场景4:分布式部署 celery,多生产者、多消费者

6-1、场景描述

a、在局域网内多台电脑上部署 celery,消费者为 server1、server2,生产者为 server3、server4。
b、生产者与消费者可以在同一台电脑上,也可以各自在不同的电脑上。但是建议同一台电脑,最好只有一个生产者,和/或一个消费者。
c、不同消费者指定同一个地址的 redis 作为 broker 和 backend (本例都在 server1)。
d、任务可根据需要设定执行的优先级高低。优先级取值范围为 [0,9],值越小越优先。
e、所有消费者上都有任务的实现方法,生产者上可以有,也可以没有任务的实现方法。

6-2、show me the code

A、以下代码部署在消费者 server 上,各个消费者上的内容完全一致。
a、文件结构:

x:\xxx\celery_tasks
             │  	celery_config.py
             │  	tasks.py
             └─     celery_app.py

b、代码:

# celery_app.py
import os
import sys
from celery import Celery

sys.path.append(os.path.abspath("."))

app = Celery('tasks', include=['tasks'])
app.config_from_object('celery_config')

# celery_config.py
from kombu import Queue
from kombu import Exchange

broker_url = r"redis://x.x.x.x:6379/5"
result_backend = r"redis://x.x.x.x:6379/6"
serializer = 'json'	  #  任务执行结果序列化方式
result_expires = 60 * 60 * 5
accept_content = {'json'}
timezone = "Asia/Shanghai"
enable_utc = True
worker_concurrency = 5  # celery worker并发数
worker_max_tasks_per_child = 5  # 每个worker最大执行任务数
broker_connection_retry_on_startup = True
broker_heartbeat = 30   # 每隔 30 秒发一次心跳,防止长时间无交互导致不同服务器之间连接出问题

task_queues = (
    Queue('default', exchange=Exchange('default', type='direct'), routing_key='default'),
)
task_routes = ([
                   ('tasks.add', {'queue': 'default'}),
                   ('tasks.multiply', {'queue': 'default'}),
               ],)

# tasks.py
from celery_app import app

@app.task
def add(x, y):
    return f"任务 add 返回值 {x + y}"


@app.task
def multiply(x, y):
    return f"任务 multiply 返回值 {x * y}"

B、在生产者上部署的代码
在生产者上部署的代码,celery_app.py、celery_config.py 完全一样。根据本地是否有 tasks.py 而分两种情况
a-1、生产者上也有 tasks 内容
文件结构

x:\xxx\celery_tasks
             │  	celery_config.py
             │  	tasks.py
             │  	celery_trial1.py
             └─     celery_app.py

代码
除了 celery_trial.py 之外,其他文件内容与消费者上对应文件的内容一致。

# celery_trial1.py
from tasks import *

for i in range(10):
    print(f'执行第 {i} 项任务')

    multiply.apply_async(args=(i, i + 1), queue='default', priority=10)
    add.apply_async(args=(i, i + 1), queue='default', priority=1)

a-2、生产者上没有 tasks 内容
文件结构

x:\xxx\celery_tasks
             │  	celery_config.py
             │  	celery_trial2.py
             └─     celery_app.py

代码
除了 celery_trial2.py 之外,其他文件内容与消费者上对应文件的内容一致。

# celery_trial2.py
import celery_config as cfg
from celery import Celery

app = Celery('tasks', include=['tasks'])
app.config_from_object(cfg)

for i in range(10):
    app.send_task('tasks.add', queue=cfg.task_queues[0], args=(i, i + 1), priority=1)
    app.send_task('tasks.multiply', queue=cfg.task_queues[0], args=(i, i + 1), priority=10)

6-3、运行

1)消费者上,命令行窗口中,在项目所在目录启动 worker。因为消费者多于1个,需要起不同的名字区别开
对 server1

x:\xxxx\celery -A tasks worker -l info -P eventlet -c 10 -n worker1@%h

显示为

 -------------- worker1@xxxxxxxx v5.2.7 (dawn-chorus)
--- ***** -----
-- ******* ---- Windows-2012ServerR2-6.3.9600-SP0 2023-08-06 22:34:53
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         tasks:0x80d1f3b2e8
- ** ---------- .> transport:   redis://x.x.x.x:6379/5
- ** ---------- .> results:     redis://x.x.x.x:6379/6
- *** --- * --- .> concurrency: 10 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> default          exchange=default(direct) key=default

[tasks]
  . tasks.add
  . tasks.multiply

[2023-08-06 22:34:53,593: INFO/MainProcess] Connected to redis://x.x.x.x:6379/5
[2023-08-06 22:34:53,603: INFO/MainProcess] mingle: searching for neighbors
[2023-08-06 22:34:54,673: INFO/MainProcess] mingle: sync with 1 nodes
[2023-08-06 22:34:54,675: INFO/MainProcess] mingle: sync complete
[2023-08-06 22:34:54,700: INFO/MainProcess] pidbox: Connected to redis://x.x.x.x:6379/5.
[2023-08-06 22:34:54,707: INFO/MainProcess] worker1@xxxxxxxx ready.
[2023-08-06 22:34:59,067: INFO/MainProcess] Events of group {task} enabled by remote.

对 server2

x:\xxxx\celery -A tasks worker -l info -P eventlet -c 10 -n worker2@%h
 -------------- worker2@xxxxxxxx v5.3.1 (emerald-rush)
--- ***** -----
-- ******* ---- Windows-10-10.0.19045-SP0 2023-08-06 22:34:46
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         tasks:0x21736cc0820
- ** ---------- .> transport:   redis://x.x.x.x:6379/5
- ** ---------- .> results:     redis://x.x.x.x:6379/6
- *** --- * --- .> concurrency: 10 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> default          exchange=default(direct) key=default
                .> priority_high    exchange=priority(direct) key=priority_high
                .> priority_low     exchange=priority(direct) key=priority_low

[tasks]
  . tasks.add
  . tasks.do_something
  . tasks.multiply

[2023-08-06 22:34:46,190: INFO/MainProcess] Connected to redis://x.x.x.x:6379/5
[2023-08-06 22:34:46,313: INFO/MainProcess] mingle: searching for neighbors
[2023-08-06 22:34:47,626: INFO/MainProcess] mingle: sync with 1 nodes
[2023-08-06 22:34:47,626: INFO/MainProcess] mingle: sync complete
[2023-08-06 22:34:47,887: INFO/MainProcess] worker2@xxxxxxxx ready.
[2023-08-06 22:34:47,964: INFO/MainProcess] pidbox: Connected to redis://x.x.x.x:6379/5.
[2023-08-06 22:34:52,263: INFO/MainProcess] Events of group {task} enabled by remote.
[2023-08-06 22:34:56,817: INFO/MainProcess] sync with worker1@xxxxxxxx

至此,两个消费者的 worker 都启动完成,并且互相之间做了同步。

2)本地没有任务实现方法的生产者发出任务

x:\xxxx\python celery_trial1.py

此时,在 server1 上的情况是

[2023-08-06 22:49:26,335: INFO/MainProcess] Task tasks.add[7e376424-8cf6-477a-a3db-bd917114b9ca] received
[2023-08-06 22:49:26,501: INFO/MainProcess] Task tasks.add[6411115c-cdc5-4a61-99ac-70e6d912fc41] received
[2023-08-06 22:49:26,607: INFO/MainProcess] Task tasks.add[7e376424-8cf6-477a-a3db-bd917114b9ca] succeeded in 0.26500000013038516s: '任务 add 返回值 1'
[2023-08-06 22:49:26,612: INFO/MainProcess] Task tasks.add[fc6393ad-880a-4471-b0ba-3a85ff3f29b7] received
[2023-08-06 22:49:26,615: INFO/MainProcess] Task tasks.add[6411115c-cdc5-4a61-99ac-70e6d912fc41] succeeded in 0.10899999993853271s: '任务 add 返回值 3'
[2023-08-06 22:49:26,619: INFO/MainProcess] Task tasks.add[66133c6b-2dad-4055-acd6-c1f913f3ee22] received
[2023-08-06 22:49:26,811: INFO/MainProcess] Task tasks.add[fc6393ad-880a-4471-b0ba-3a85ff3f29b7] succeeded in 0.1880000000819564s: '任务 add 返回值 7'
[2023-08-06 22:49:26,816: INFO/MainProcess] Task tasks.add[7dd086e7-2fcc-4f96-89aa-1fb58b389697] received
[2023-08-06 22:49:26,820: INFO/MainProcess] Task tasks.add[66133c6b-2dad-4055-acd6-c1f913f3ee22] succeeded in 0.10899999993853271s: '任务 add 返回值 9'
[2023-08-06 22:49:26,826: INFO/MainProcess] Task tasks.add[70536d87-17cc-43c9-abad-84d99c57e601] received
[2023-08-06 22:49:27,034: INFO/MainProcess] Task tasks.add[7dd086e7-2fcc-4f96-89aa-1fb58b389697] succeeded in 0.20300000021234155s: '任务 add 返回值 13'
[2023-08-06 22:49:27,038: INFO/MainProcess] Task tasks.multiply[8387036f-a73e-4c7f-ad4f-f04a9be7ab8f] received
[2023-08-06 22:49:27,043: INFO/MainProcess] Task tasks.add[70536d87-17cc-43c9-abad-84d99c57e601] succeeded in 0.10899999993853271s: '任务 add 返回值 15'
[2023-08-06 22:49:27,136: INFO/MainProcess] Task tasks.multiply[ded684bc-a77f-4808-ac49-849ad58f8133] received
[2023-08-06 22:49:27,239: INFO/MainProcess] Task tasks.multiply[8387036f-a73e-4c7f-ad4f-f04a9be7ab8f] succeeded in 0.18700000015087426s: '任务 multiply 返回值 2'
[2023-08-06 22:49:27,247: INFO/MainProcess] Task tasks.multiply[dd8e6e32-df5d-4b82-8399-9afc64957c3c] received
[2023-08-06 22:49:27,250: INFO/MainProcess] Task tasks.multiply[ded684bc-a77f-4808-ac49-849ad58f8133] succeeded in 0.10899999993853271s: '任务 multiply 返回值 6'
[2023-08-06 22:49:27,255: INFO/MainProcess] Task tasks.multiply[422bf619-e2ca-4961-9490-d0703c1f5f0b] received
[2023-08-06 22:49:27,384: INFO/MainProcess] Task tasks.multiply[dd8e6e32-df5d-4b82-8399-9afc64957c3c] succeeded in 0.125s: '任务 multiply 返回值 30'
[2023-08-06 22:49:27,513: INFO/MainProcess] Task tasks.multiply[422bf619-e2ca-4961-9490-d0703c1f5f0b] succeeded in 0.125s: '任务 multiply 返回值 42'

在 server2 上的显示是

[2023-08-06 22:49:29,711: INFO/MainProcess] Task tasks.multiply[e0bb8b91-3699-4247-ad29-53111c84d3e6] received
[2023-08-06 22:49:29,815: INFO/MainProcess] Task tasks.add[121361bd-9801-4381-a6cc-742d8142cd95] received
[2023-08-06 22:49:29,918: INFO/MainProcess] Task tasks.multiply[e0bb8b91-3699-4247-ad29-53111c84d3e6] succeeded in 0.17099999997299165s: '任务 multiply 返回值
[2023-08-06 22:49:29,921: INFO/MainProcess] Task tasks.add[6a4aa7bc-a76f-4ecf-9566-bf964216ae29] received
[2023-08-06 22:49:30,043: INFO/MainProcess] Task tasks.add[121361bd-9801-4381-a6cc-742d8142cd95] succeeded in 0.17099999997299165s: '任务 add 返回值 5'
[2023-08-06 22:49:30,080: INFO/MainProcess] Task tasks.add[7fded524-7fd1-4ff8-af0f-6ff1a9b55829] received
[2023-08-06 22:49:30,107: INFO/MainProcess] Task tasks.add[6a4aa7bc-a76f-4ecf-9566-bf964216ae29] succeeded in 0.10899999999674037s: '任务 add 返回值 11'
[2023-08-06 22:49:30,169: INFO/MainProcess] Task tasks.add[5a01692d-3441-4af1-ba6b-41b898b2d1b3] received
[2023-08-06 22:49:30,254: INFO/MainProcess] Task tasks.add[7fded524-7fd1-4ff8-af0f-6ff1a9b55829] succeeded in 0.15600000001722947s: '任务 add 返回值 17'
[2023-08-06 22:49:30,277: INFO/MainProcess] Task tasks.multiply[9ed38491-9e6e-45ad-ae19-d7d6445406c7] received
[2023-08-06 22:49:30,304: INFO/MainProcess] Task tasks.add[5a01692d-3441-4af1-ba6b-41b898b2d1b3] succeeded in 0.09399999998277053s: '任务 add 返回值 19'
[2023-08-06 22:49:30,364: INFO/MainProcess] Task tasks.multiply[e07e4802-94dd-4c40-b979-351ddb74aef7] received
[2023-08-06 22:49:30,469: INFO/MainProcess] Task tasks.multiply[9ed38491-9e6e-45ad-ae19-d7d6445406c7] succeeded in 0.15600000001722947s: '任务 multiply 返回值
[2023-08-06 22:49:30,495: INFO/MainProcess] Task tasks.multiply[5f3cc9c0-6c5f-42b6-a3f2-77d568f57866] received
[2023-08-06 22:49:30,518: INFO/MainProcess] Task tasks.multiply[e07e4802-94dd-4c40-b979-351ddb74aef7] succeeded in 0.11000000004423782s: '任务 multiply 返回值
[2023-08-06 22:49:30,589: INFO/MainProcess] Task tasks.multiply[0d585f2f-12c5-4cf8-b681-27c9ebbe46d9] received
[2023-08-06 22:49:30,674: INFO/MainProcess] Task tasks.multiply[dcbee2aa-aeb3-4c41-8e79-3311faa5bb46] received
[2023-08-06 22:49:30,687: INFO/MainProcess] Task tasks.multiply[5f3cc9c0-6c5f-42b6-a3f2-77d568f57866] succeeded in 0.14100000000325963s: '任务 multiply 返回值
[2023-08-06 22:49:30,768: INFO/MainProcess] Task tasks.multiply[0d585f2f-12c5-4cf8-b681-27c9ebbe46d9] succeeded in 0.15600000001722947s: '任务 multiply 返回值
[2023-08-06 22:49:30,825: INFO/MainProcess] Task tasks.multiply[dcbee2aa-aeb3-4c41-8e79-3311faa5bb46] succeeded in 0.09399999998277053s: '任务 multiply 返回值
90'

可以看到,一共生产了20个任务,10个 add,10个 multiply,其中有6个 add,由 server1 消费掉,4个 multiply 由 server2 消费掉。

3)本地有任务实现方法的生产者发出任务

x:\xxxx\python celery_trial2.py

结果与 2)的差不多。这里就不重复贴了。

7、场景5:(本地)多队列

7-1、场景描述

a、为了方便理解,使用本地多队列的例子。
b、有多种不同的任务:视频上传、视频压缩、照片压缩、其他。
c、这些任务需要送到不同的任务队列中,使用不同的 worker 处理。

7-2、show me the code

文件结构如下:

x:\xxx\celery_tasks
             │ 	 	celery_app.py
             │  		config.py
             │
             └─tasks
 	             	tasks.py

其中:
a、celery_app.py 内容与场景2中的同名文件内容一样。
b、config.py 的内容如下:

# config.py

from kombu import Queue, Exchange
 
broker_url = 'redis://127.0.0.1:6379/1'
result_backend = 'redis://127.0.0.1:6379/2'
 
# 定义队列
task_queues = (
    # 默认队列
    Queue('default', exchange=Exchange('default'), routing_key='default'),
    
    # 自定义队列
    Queue('videos', exchange=Exchange('media', type='direct'), routing_key='media.video'),
    Queue('images', exchange=Exchange('media', type='direct'), routing_key='media.image'),
)
 
# 指定路由
task_routes = {
    'tasks.tasks.image_compress': {'queue': 'images', 'routing_key': 'media.image'},
    'tasks.tasks.video_upload': {'queue': 'videos', 'routing_key': 'media.video'},
    'tasks.tasks.video_compress': {'queue': 'videos', 'routing_key': 'media.video'}
}

# 注册需要执行的任务
imports = ('tasks.tasks',)
# task1.py
import time
from celery_tasks.celery_app import app
 
# 视频压缩与上传
@app.task
def video_compress(video_name):
    time.sleep(10)
    return f'Compress video file: {video_name} finished.'
 
@app.task
def video_upload(video_name):
    time.sleep(5)
    return f'upload video file: {video_name} finished.'
 
# 照片压缩
@app.task
def image_compress(image_name):
    time.sleep(10)
    return f'Compress image file: {image_name} finished.'
 
# 其他任务
@app.task
def other(str):
    time.sleep(10)
    return 'Do other things finished.'
7-3、运行

1)在命令行窗口1中,进入 xxx 目录,并执行以下命令,启动 celery,等待生产者发出任务执行指令

celery -A celery_app worker -l info -P eventlet -c 10

如果正常运行,命令行窗口中会出现以下内容:

 -------------- celery@xxxxxxxxxxx v5.3.1 (emerald-rush)
--- ***** -----
-- ******* ---- Windows-10-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         xxxx:xxxxxxxx
- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 10 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . say_hello

[2023-07-25 01:28:57,898: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1
[2023-07-25 01:28:57,901: INFO/MainProcess] mingle: searching for neighbors
[2023-07-25 01:28:58,915: INFO/MainProcess] mingle: all alone
[2023-07-25 01:28:58,921: INFO/MainProcess] pidbox: Connected to redis://127.0.0.1:6379/1.
[2023-07-25 01:28:58,925: INFO/MainProcess] celery@xxxxxxxxxxxxxx ready.

最后有 ready 字样,就表示 celery 服务运行正常,并等待任务运行指令。

注意,如果不想启动所有队列,可以在命令行中末尾加上想启动,或者想排除的队列名。比如:

celery -A celery_app worker -l info -P eventlet -c 10 -Q images
celery -A celery_app worker -l info -P eventlet -c 10 -X images

2)在命令行窗口2中进入 python 后,执行以下命令,发出执行 say_hello 任务的指令:

>>> from tasks.tasks import *
>>> video_upload.delay('wahaha')
>>>> other.delay('yoho')
<AsyncResult: 37d07710-5714-4716-9fe9-e90015d5a9fe>
>>>>

同时命令行窗口1中会即时显示

[2023-08-13 22:38:50,658: INFO/MainProcess] Task tasks.tasks.video_upload[e36e53a6-d4d7-4134-830b-151ecee971e9] received
[2023-08-13 22:38:55,710: INFO/MainProcess] Task tasks.tasks.video_upload[e36e53a6-d4d7-4134-830b-151ecee971e9] succeeded in 5.047000000020489s: 'upload video file: wahaha finished.'

任务完成。

8、可视化实时监控 celery 运行情况

1) 安装 flower

pip install flower

2)在本机上执行定期任务时,在命令行窗口3中,进入 xxxx 目录,输入以下指令启动 flower服务:

celery -A my_celery.celery_app flower

然后在浏览器中输入以下地址即可看到 worker 的运行情况:

localhost:5555

3)在远程的消费者计算机上执行任务时,在消费者计算机的命令行窗口中,进入 xxx 目录,输入以下指令启动 flower 服务:

celey -A tasks flower

然后在本机(生产者计算机)浏览器中输入以下地址即可看到 worker 的运行情况。

http://x.x.x.x:5555

以下是第 6 点的显示情况:
在这里插入图片描述

在这里插入图片描述

9、运行状态检查

很多情况下,我们需要在 python 代码中获取某个任务的状态,可以在生产并发送任务时将任务的 id 记录下来,之后可以直接查询具体 id 的状态:

# celery_trial1.py
from tasks import *

res = multiply.apply_async(args=(i, i + 1), queue='default', priority=10)

res.ready()		# 执行任务时,会返回 False,执行完成后,会返回 True

res 的状态有:

res.status 		# 任务的状态,有 STARTED(已经被执行), PENDING(执行中), RETRY(异常后正在重试), SUCCESS(成功) 等
res.ready()		# 任务是否完成。是为 True,否为 False
res.successful()		# 任务是否成功。是为 True,否为 False
res.failed()		# 任务是否失败。是为 True,否为 False
res.get()			# 获取任务完成后的返回值

10、关于 heart_beat 的情况说明

在 windows 下使用 celery,如果生产者长时间不发送任务,celery 会判断这个生产者状态异常,从而中断与这个生产者的连接。
解决这个问题的方法,就是给任务队列设置一个心跳时间,比如每一分钟或者每五分钟向 Redis 发送一次数据以保证队列始终是活跃的状态,这样只要你的电脑不关机并保持网络畅通(如果是远程 Redis),celery 任务队列服务就不会出现假死状态。
具体的方法有两个(二选一):
1)显式设置
在 celeryconfig.py 中新增以下项目,设置好心跳:

broker_heartbeat = 30   # 每隔 30 秒发一次心跳,防止长时间无交互导致不同服务器之间连接出问题

2)将心跳设置为定时任务
同样是在 celeryconfig.py 中,新增以下定时任务:

beat_schedule = {
    'heart-beat': {
        'task': 'tasks.heart_beat',
        'schedule': datetime.timedelta(seconds=30),
        'args': ()  # 任务函数参数
    },
}

然后在生产者上新开一个命令行窗口,运行:

celery -A tasks beat -l INFO

11、命令行窗口启动 celery 时的常用参数

celery -A xxxx worker -l info -P eventlet -c 10 -Q queue1

Options:
  -A APP, --app=APP     app instance to use (e.g. module.attr_name)
  --workdir=WORKING_DIRECTORY
                        Optional directory to change to after detaching.
  -c CONCURRENCY, --concurrency=CONCURRENCY
                        Number of child processes processing the queue. The
                        default is the number of CPUs available on your
                        system.
  -l LOGLEVEL, --loglevel=LOGLEVEL
                        Logging level, choose between DEBUG, INFO, WARNING,
                        ERROR, CRITICAL, or FATAL.
  -n HOSTNAME, --hostname=HOSTNAME
                        Set custom hostname, e.g. 'w1.%h'. Expands: %h
                        (hostname), %n (name) and %d, (domain).
  -B, --beat            Also run the celery beat periodic task scheduler.
                        Please note that there must only be one instance of
                        this service.
   -Q QUEUES, --queues=QUEUES
                        List of queues to enable for this worker, separated by
                        comma. By default all configured queues are enabled.
                        Example: -Q video,image
  -X EXCLUDE_QUEUES, --exclude-queues=EXCLUDE_QUEUES

12、参考资料

1)并行分布式框架celery
2)win10下实现django+celery定时任务 保姆级别教程
3)利用Celery实现定时任务
4)celery分布式任务部署多台机器,形成一发布者,多消费者(每个消费者处理不同任务)
5)celery框架|多台server上部署celery并用flower监测状态
6)celery实现任务优先级控制
7)Celery实践, 多队列,多优先级,任务重试
8)Celery多队列
9)手把手教你在Windows下设置分布式队列Celery的心跳轮询
10)Celery学习笔记(一)

PS:
1、细节内容后续会慢慢补充完整。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值