Celery速学笔记

目的

在经济下行的情况下,只能多多拓展自我技术栈,提高竞争能力

结构图

在这里插入图片描述
1、Broker:一般就是Redis或者MQ,存放任务队列的
2、Worker:消费者
3、Backend:结果存放队列,也是Redis或者MQ
4、Task:个人感觉就是生产者

安装

pip install celery

个人直接用的5.3.1,一上手就是最新版本,导致遇到个坑,看别人以前的教程会有问题。

第一个坑

Traceback (most recent call last):
  File "C:\Python39\lib\site-packages\billiard\pool.py", line 362, in workloop
    result = (True, prepare_result(fun(*args, **kwargs)))
  File "C:\Python39\lib\site-packages\celery\app\trace.py", line 635, in fast_trace_task
    tasks, accept, hostname = _loc
ValueError: not enough values to unpack (expected 3, got 0)

在通过your_func.delay()任务给celery去执行的时候就会报这个错。问GPT被绕进去了,后面还是自行网上找了下解决方案,下面直接抄作业

解决

  • Windows 10系统
    看别人描述大概就是说win10上运行celery4.x就会出现这个问题,解决办法如下,原理未知:先安装一个 eventlet
pip install eventlet

然后启动worker的时候加一个参数,如下:

celery -A celery_tasks.main worker -l info -P eventlet

然后就可以正常的调用了

基础使用

创建一个python文件,命名:my_task.py

import celery
import time

backend='redis://127.0.0.1:6379/1'
broker='redis://127.0.0.1:6379/2'
cel=celery.Celery('first_task',backend=backend,broker=broker)

@cel.task
def send_email(name):
    print("向%s发送邮件..."%name)
    time.sleep(5)
    print("向%s发送邮件完成"%name)
    return "ok"

@cel.task
def send_msg(name):
    print("向%s发送短信..."%name)
    time.sleep(5)
    print("向%s发送短信完成"%name)
    return "ok"

看以上的命名my_task,cel,frist_task尽量区别化命名,方便后面运行调试信息输出时,可以对比哪些参数是哪些

运行

D:\代码\my_code\study\study_celery>celery -A my_task worker -l info -P eventlet

 -------------- celery@WINDOWS-3T5S0CL v5.1.2 (sun-harmonics)
--- ***** -----
-- ******* ---- Windows-10-10.0.10586-SP0 2023-07-02 20:24:05
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app:         first_task:0x2b9d54582e0
- ** ---------- .> transport:   redis://:**@120.79.235.7:6379/63
- ** ---------- .> results:     redis://:**@120.79.235.7:6379/63
- *** --- * --- .> concurrency: 8 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[tasks]
  . my_task.send_email
  . my_task.send_msg

[2023-07-02 20:24:06,312: INFO/MainProcess] Connected to redis://:**@120.79.235.7:6379/63
[2023-07-02 20:24:06,394: INFO/MainProcess] mingle: searching for neighbors
[2023-07-02 20:24:08,250: INFO/MainProcess] mingle: sync with 1 nodes
[2023-07-02 20:24:08,254: INFO/MainProcess] mingle: sync complete
[2023-07-02 20:24:08,368: INFO/MainProcess] pidbox: Connected to redis://:**@120.79.235.7:6379/63.
[2023-07-02 20:24:08,500: INFO/MainProcess] celery@WINDOWS-3T5S0CL ready.

上面提示过多创建不一样的名字,方便我们区分每个名称是代表什么

  • celery -A my_task worker -l info -P eventlet
    启动指令中my_task就是文件名。启动指令中不要带.py

  • app: first_task:0x2b9d54582e0
    app的名称就是你celery.Celery生成app时的第一个传参

  • transport: redis://:@120.79.235.7:6379/63
    results: redis://:
    @120.79.235.7:6379/63
    你的中间redis和结果redis,如果你项目多了,测试,正式环境多了,启动时记得多检查

  • [tasks]
    . my_task.send_email
    . my_task.send_msg
    这里可以清晰的看到你这个celery app里面有哪些Task

  • celery@WINDOWS-3T5S0CL ready.
    看到这条信息,redis才是正常运行

不要小看启动信息,在每次启动的时候请多检查里面的信息

让你的celery app执行起来

先创建一个文件,produce_task.py:

from my_task import send_email, send_msg

email_result = send_email.delay("hello")
print(email_result.id)

msg_result = send_msg.delay("world")
print(msg_result.id)

执行以上代码,即对启动着的celery发送两个执行命令,分别执行官send_msg和send_email,delay里直接传参就是send_msg的传参

踩坑

这里又踩坑,broker和backend在同一个redis的话,会导致两个delay,只执行一个,问题点找不到,然后换成两个就好了。问题根源后面再找,现在不要打算学习进度

查看执行结果

在这里插入图片描述在这里插入图片描述

在backend的redis里面就可以查看到所有执行结果,成功的,失败的,是以string型进行存储。直接存DB根目录

获取执行结果

# 成功结果
async_succ_result = AsyncResult(id="23c7ebfd-5bf6-4f83-b7c8-9c870b168d17", app=cel)
# 失败结果
async_fail_result = AsyncResult(id="923360d1-caa4-41ca-9887-3bc404ff0803", app=cel)
# 判断是否成功
success_flag1 = async_succ_result.successful()
success_flag2 = async_fail_result.successful()
# 判断是否失败
failure_flag1 = async_succ_result.failed()
failure_flag2 = async_fail_result.failed()
# 获取结果,如果是成功,则返回之前函数里的return值
result = async_succ_result.get()
# 获取结果,如果是失败,则会报异常
try:
    result = async_fail_result.get()
except Exception as e:
    print(e)

运行状态

# 运行结果状态
if async_succ_result.status == 'PENDING':
    print('任务等待中被执行')
elif async_succ_result.status == 'RETRY':
    print('任务异常后正在重试')
elif async_succ_result.status == 'STARTED':
    print('任务已经开始被执行')
elif async_succ_result.status == 'FAILURE':
    print('任务执行失败')
elif async_succ_result.status == 'SUCCESS':
    print('任务执行成功')

函数里的备注只有这五个状态

删除结果

async_succ_result.forget()

注意如果删除了结果,则获取的时候会卡住。

多任务结构

  • 当你需要执行的任务很多的时候需要对任务进行结构性分层,此时则需要进行多文件的区分,以便维护
    在这里插入图片描述

新建一个目录:my_multi_tasks,作为我们的多任务根目录

  • 创建celery.py文件
import celery

backend='redis://127.0.0.1:6379/1'
broker='redis://127.0.0.1:6379/2'
second_cel = celery.Celery(
    'second_tasks',
    backend=backend,
    broker=broker,
    include={
    
    
        'my_multi_tasks.my_task_01',
        'my_multi_tasks.my_task_02'
    }
)

此处文件名必须为celery,因为celery.exe控制器执行命令时,就是检测你的项目底下的celery文件,去配置你的多任务结构。如果名字不为celery,则会报错如下:

D:\代码\my_code\study\study_celery>celery -A my_multi_tasks worker -l info -P eventlet
Usage: celery [OPTIONS] COMMAND [ARGS]...

Error: Invalid value for '-A' / '--app':
Unable to load celery application.
Module 'my_multi_tasks' has no attribute 'celery'

Module ‘my_multi_tasks’ has no attribute ‘celery’,说明了my_multi_tasks没有celery属性,可以看出默认程序是去.celery了

  • 创建my_task_01.py文件
import time
from my_multi_tasks.elery import second_cel

@second_cel.task
def second_send_email(name):
    print(f"向{name}发送邮件".center(30, '1'))
    time.sleep(5)
    print(f"向{name}发送邮件完成".center(30, '1'))
    return "ok"
  • 创建my_task_02.py文件
import time
from my_multi_tasks.elery import second_cel

@second_cel.task
def second_send_msg(name):
    print(f"向{name}发送短信".center(30, '2'))
    time.sleep(5)
    print(f"向{name}发送短信完成".center(30, '2'))
    return "ok"

现在将send_email和send_msg分别放在task01和task02,记得在函数前面加上该多任务结构的celery

  • 创建produce_task_second.py文件
from my_multi_tasks.my_task_01 import second_send_email
from my_multi_tasks.my_task_02 import second_send_msg

email_result = second_send_email.delay('second_hello')
msg_result = second_send_msg.delay('second_world')
  • 执行指令celery -A my_multi_tasks worker -l info -P eventlet
    启动celery后,使用produce_task_second.py文件进行任务的发送。可以查看任务执行的过程

定时任务

任务结构体还是直接照搬上面多任务结构的任务体。针对p添加多一个produce文件进行定时任务的讲解

from datetime import datetime
from my_multi_tasks.my_task_01 import second_send_email
from my_multi_tasks.my_task_02 import second_send_msg

# 指定时间发送
time_temp = datetime(2023, 7, 4, 6, 19, 00)
time_temp_utc = datetime.utcfromtimestamp(time_temp.timestamp())
result = second_send_email.apply_async(args=['kola', ], eta=time_temp_utc)
print(result.id)

# 延迟发送
from datetime import timedelta
time_now = datetime.now()
time_now_utc = datetime.utcfromtimestamp(time_now.timestamp())
time_now_utc_new = time_now_utc + timedelta(seconds=20)
result2 = second_send_msg.apply_async(args=['cat', ], eta=time_now_utc_new)
print(result2.id)
  • 注意:
    1、celery的定时任务默认是以国际标准UTC为准,所以中国时间还需要做一下UTC转换
    2、在执行以上语句后脚本即结束,任务已丢入队列。等待时间到达后进行执行
    3、注意args的传参,它是[‘kola’, ],有个逗号,意味着传进去的是一个tuple

通过配置信息和beat,启动定时任务

beat是celery很重要的一个生产者。它是一个脚本服务,跟worker一样,启动后是一直在跑的一个脚本程序,现在进行配置

import celery
from celery.schedules import crontab


backend='redis://127.0.0.1:6379/1'
broker='redis://127.0.0.1:6379/2'
second_cel = celery.Celery(
    'second_tasks',
    backend=backend,
    broker=broker,
    include={
    
    
        'my_multi_tasks.my_task_01',
        'my_multi_tasks.my_task_02'
    }
)


# 设置定时任务的执行时间的时区,改为中国,关闭utc
second_cel.conf.timezone = 'Asia/Shanghai'
second_cel.conf.enable_utc = False


# 发送任务的调度器
second_cel.conf.beat_schedule = {
    
    
    # 随意命名
    'add-task-every-1-minute': {
    
    
        # 传参
        'args': ('张三',),
        # 执行函数的路径,指定second_send_email函数
        'task': 'my_multi_tasks.my_task_01.second_send_email',
        # 调度时间
        # 'schedule': 10.0,                     # 每几秒,执行一次
        # 'schedule': timedelta(seconds=6),     # 每几秒,执行一次,但timedelta更丰富,比如day,hour
        'schedule': crontab(minute="*/1"),    # 每一分钟,执行一次
    },
    'add-task-birthday': {
    
    
        'args': ('李四',),
        'task': 'my_multi_tasks.my_task_02.second_send_msg',
        # 每年411号,842分执行
        'schedule': crontab(minute=10, hour=5, day_of_month=5, month_of_year=7),
    },
}

celery支持直接修改执行任务的默认时区:second_cel.conf.timezone
通过配置的形式生成定时任务清单:second_cel.conf.beat_schedule

  • 启动指令:celery -A my_multi_tasks beat -l info
    注意这里不需要加-P eventlet,因为-P是指定消费者的执行器。这个生产者,所以不需要。

在执行并观察后,可以发现生产者beat和消费者worker是完全分离的。beat也是一个程序,通过命令行启动后只管按照配置表设置的时间往中间件broker的一个键celery丢定时任务。以下为一个样例

{
    
    
	"body": "W1siXHU2NzRlXHU1NmRiIl0sIHt9LCB7ImNhbGxiYWNrcyI6IG51bGwsICJlcnJiYWNrcyI6IG51bGwsICJjaGFpbiI6IG51bGwsICJjaG9yZCI6IG51bGx9XQ==",
	"content-encoding": "utf-8",
	"content-type": "application/json",
	"headers": {
    
    
		"lang": "py",
		"task": "my_multi_tasks.my_task_02.second_send_msg",
		"id": "1dce9df1-da1e-4c4e-ac6b-be170b815400",
		"shadow": null,
		"eta": null,
		"expires": null,
		"group": null,
		"group_index": null,
		"retries": 0,
		"timelimit": [null, null],
		"root_id": "1dce9df1-da1e-4c4e-ac6b-be170b815400",
		"parent_id": null,
		"argsrepr": "['\u674e\u56db']",
		"kwargsrepr": "{}",
		"origin": "gen12600@WINDOWS-3T5S0CL",
		"ignore_result": false
	},
	"properties": {
    
    
		"correlation_id": "1dce9df1-da1e-4c4e-ac6b-be170b815400",
		"reply_to": "3f3250fb-6ed4-3d13-83db-a7d8e30c8ee9",
		"delivery_mode": 2,
		"delivery_info": {
    
    
			"exchange": "",
			"routing_key": "celery"
		},
		"priority": 0,
		"body_encoding": "base64",
		"delivery_tag": "88d2a3dd-5612-4ef7-ae36-0d2484b9a468"
	}
}

感觉就两个参数值有观察意义,一个是任务函数,一个是传参
“task”: “my_multi_tasks.my_task_02.second_send_msg”,
“argsrepr”: “[‘\u674e\u56db’]”,

  • 注意在启动了beat后,如果不及时消费任务会越堆积越多。到时候worker一启动,这些任务全部都要被执行。可能需要一些过期不执行机制

Celery应用在Django中

在这里插入图片描述
新建一个Django项目,在More Settings里面Application name中命名一个app01,在创建项目的时候就会顺带创建一个app

在这里插入图片描述在urls里面创建一个test路径指向app01的视图
再在app01的views视图函数里面添加test函数,简单测试一下网站访问127.0.0.1/test的页面,确认返回ok

  • 随后在Django项目根目录底下创建一个my_celery文件夹,用于存放你的celery
    在这里插入图片描述
    目录的配置如上,一个email任务,一个sms任务,config是配置文件,main是主程序
  • config.py(配置文件,这里特别注意broker_url和result_backend这两个变量名不能错,必须不能错
broker_url = 'redis://127.0.0.1:6379/15'
result_backend = 'redis://127.0.0.1:6379/14'
  • tasks.py(任务文件,celery的任务必须写在tasks.py的文件中,别的文件名称不识别
import time
import logging
from my_celery.main import app
log = logging.getLogger("django")

@app.task  # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
def send_sms(mobile):
    """发送短信"""
    print("向手机号%s发送短信成功!"%mobile)
    time.sleep(5)
    return "send_sms OK"

@app.task  # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
def send_sms2(mobile):
    print("向手机号%s发送短信成功!" % mobile)
    time.sleep(5)
    return "send_sms2 OK"
  • main.py(主程序,创建celery的app和加载Django配置文件)
import os
from celery import Celery

# 创建celery的实例对象,做解耦,不在这里传参broker和backend,通过配置文件进行设置
app = Celery('my_django_celery')

# 把celery和Django进行结合,识别和加载Django的配置文件,注意这里是在环境中加入Django的配置
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celeryPros.settings.dev')

# 通过app对象加载配置,把config.py里面的配置读到celery里面去
app.config_from_object('my_celery.config')

# 加载任务,注意!默认会去目录底下的tasks.py文件里面找,所以tasks.py这个文件命名有特殊意义
app.autodiscover_tasks([
    'my_celery.my_email',
    'my_celery.my_sms',
])

# 启动Celery的命令
# 强烈建议切换目录到mycelery根目录下启动
# celery -A my_celery.main worker --loglevel=info
  • 启动指令:celery -A my_celery.main worker --loglevel=info
    注意这里尽量要在celery文件夹的外面路径,然后执行指令。可以通过my_celery.main去定位路径

以上都配置完成后,再回到Django的app01项目中的views.py函数中去触发你的任务

from django.shortcuts import render, HttpResponse
from my_celery.my_sms.tasks import send_sms
from my_celery.my_sms.tasks import send_sms2
from my_celery.my_email.tasks import send_email
from my_celery.my_email.tasks import send_email2

# Create your views here.

def test(request):
    # 异步任务
    # 1. 声明一个和celery一模一样的任务函数,但是我们可以导包来解决
    # send_sms.delay("110")
    # send_sms2.delay("119")
    # # send_sms.delay()        #如果调用的任务函数没有参数,则不需要填写任何内容

    # 定时任务
    from datetime import timedelta, datetime
    time_now = datetime.now()
    time_now_utc = datetime.utcfromtimestamp(time_now.timestamp())
    time_now_utc_new = time_now_utc + timedelta(seconds=20)
    send_email.apply_async(args=['cat', ], eta=time_now_utc_new)

    return HttpResponse('ok')

启动你的Django项目,然后在浏览器里访问路径http://127.0.0.1:8000/test/,即可通过访问vies视图函数进行任务的触发

猜你喜欢

转载自blog.csdn.net/weixin_43651674/article/details/131504406