分布式Celery使用

Celery-介绍

文档:http://docs.jinkan.org/docs/celery/getting-started/introduction.html 3.1.7

文档:https://www.celerycn.io/ru-men/celery-jian-jie 4.3.0

文档:https://docs.celeryq.dev/en/stable/userguide/application.html 最新

是一个简单,灵活可靠,处理大量消息的分布式系统,专注于实时处理异步任务,同时支持任务调度
分布式系统是什么:
   比如:数据库,消息中间件,web后端,web前端,中间件系统,架构在不同的服务器上,也就是多台服务器组成的一个整体的系统应用。
   
Celery架构分为:
1.消息中间件(message broker)
   2.任务执行单元(worker)
   3.任务结果储存(task resuit store)
1.消息中间件:(借助第三方)
celery不提供消息信息服务,但是可以与第三方进行继承中间件,Rabbitmq Redis MongoDB SQLAlchemy等
2.任务执行单元:(执行任务)
worker是crlery提供的任务执行单元,worker并发的运行在分布式的节点中
3.任务储存单元: (存储执行的任务结果)
task resuit store 用来存储worker的执行结果,crlery同时支持不同方式存储任务结果,包括AMQP,redis等
支持不同的并发手段和序列化手段
并发:Prefork, Eventlet, gevent, threads/single threaded
序列化:pickle, json, yaml, msgpack. zlib, bzip2 compression, Cryptographic message signing 等等
1.  user 用户 通过自己 商场web 购买产品 (在自商场web中发起请求对该商品进行下单操作)
下单后需要通过 (短信或者邮件的方式通知,用于下单成功,商品当前状态)
2. 因为对邮件提醒与短信提醒操作耗时较长,所以需要将这部分的从操作存放在'消息队列(AMQP broker)中间件代理'
3. celery worker,就会监听当前中间件队列中得任务,从中拿到任务,进行执行
# 注意: 因为如果是同不得方式进行获取中间件队列的任务(任务又是 io事件 较长的操作) 那么就造成执行过长(尤其是遇到大量的用访问时),解决方式: 多线程 多进程 协程 等方式并发解决(能处理,但是比较复杂)。 而celery可以简单进行处理大量的io操作(是在并发的基础上进行的封装)
   上面的图片中1 ... n 的操作就是celery从队列中进行任务的异步并发执行,1...n那么就是n个任务(n个线程)n个worker,(进程 + 协程的方式进行执行 更为高效)
4. 执行完毕任务,会将任务结果存储到 task resuit store (任务存储器中),如果需要结果,就可以从任务存储器中获取当前的结果

使用场景

celery是一个强大的 分布式任务队列的异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。
1.异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
2.定时任务:定时执行某件事情,比如每天数据统计

优点

1.Celery 使用和维护都非常简单,并且不需要配置文件。
2.woker和client会在网络连接丢失或者失败时,自动进行重试。并且有的brokers 也支持“双主”或者“主/从”的方式实现高可用。
3.单个的Celery进程每分钟可以处理百万级的任务,并且只需要毫秒级的往返延迟(使用 RabbitMQ, librabbitmq, 和优化设置时)
4.Celery几乎每个部分都可以扩展使用,自定义池实现、序列化、压缩方案、日志记录、调度器、消费者、生产者、broker传输等等
Celery使用的是基于消费者与生产者模型
是异步执行的,同时执行多个任务

Celery-基本功能

1.安装
pip install -U Celery 最新版
win系统不支持 celery
pip install eventlet # win需要进行安装
2.常用配置项(大写变量在新版本已经弃用,请使用对应小写)
# 在celery4.x以后,就是BROKER_URL,如果是以前,需要写成CELERY_BROKER_URL
关于配置的官方文档:
https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-worker_concurrency
1.中间件链接
BROKER_URL = 'redis://127.0.0.1:6379/0'
2.指定结果的接收地址
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'
3.指定任务序列化方式 默认json
CELERY_TASK_SERIALIZER = 'msgpack'
4.指定结果序列化方式 默认 json
CELERY_RESULT_SERIALIZER = 'msgpack'
5.指定任务接受的序列化类型. 默认['json']
CELERY_ACCEPT_CONTENT = ['msgpack']
6.任务过期时间,celery任务执行结果的超时时间 默认为1天时间
CELERY_TASK_RESULT_EXPIRES = 24 * 60 * 60
7.任务发送完成是否需要确认,对性能会稍有影响 默认为False
CELERY_ACKS_LATE = True
8.压缩方案选择,可以是zlib, bzip2,默认是发送没有压缩的数据 默认为None未设置
CELERY_MESSAGE_COMPRESSION = 'zlib'
9.规定完成任务的时间 单个任务的运行时间限制,否则会被杀死
在5s内完成任务,否则执行该任务的worker将被杀死,任务移交给父进程 默认为None未设置
CELERYD_TASK_TIME_LIMIT = 5
10.c1elery worker的并发数,默认是服务器的内核数目,也是命令行-c参数指定的数目 默认为None未设置全部
CELERYD_CONCURRENCY = 4
11.celery worker 每次去BROKER中预取任务的数量 默认为4
CELERYD_PREFETCH_MULTIPLIER = 4
12.每个worker执行了多少任务就会死掉,默认是无限的
CELERYD_MAX_TASKS_PER_CHILD = 40
13.设置当前celery时区
CELERY_TIMEZONE = "Asia/Shanghai"
14.如果启用,消息中的日期和时间将转换为使用 UTC 时区。
CELERY_ENABLE_UTC = True # 默认为false
15.用于向后兼容性 当设置时 如果为 false,则改用系统本地时区。
CELERY_ENABLE_UTC = False # 默认为false
3.设置配置的方式:
app = Celery('task')
# 方式1
app.conf.task_serializer = 'json'
# 方式2
app.conf.update(
   task_serializer='json',
   accept_content=['json'],  # Ignore other content
   result_serializer='json',
   timezone='Europe/Oslo',
   enable_utc=True,
)
# 方式3 将变量写入py文件中
celeryconfig.py
task_serializer='json'
app.config_from_object('celeryconfig')
4.创建celery任务
app = Celery('task')
@app.task # 使用装饰器,当前就是celery任务
def func('接受的参数'):
   pass
5.执行执行celery脚本
# 注意,执行脚本需要在执行创建任务的存储py文件名作为执行的app名称,不然会出现找不到任务的情况
执行命令
-l info 打印日志登记
--app app(脚本) == -A app(脚本) 启动脚本
-P gevent # win 系统需要设置的 指定启动器
celery --app app(脚本名称) worker -l info -P gevent
celery -A app(脚本名称) worker -l info -P gevent
6.执行任务
a = func.delay('传入的参数')
a.id # 获取执行任务的id

Celery-单级目录(简单)

目录结构

celery_app
-app.py # celery对象
-config.py # celery配置
-task.py # 任务 当前py文件作为命令的执行 名称
   -main.py # 调用celery任务函数执行
   -result.py # 获取执行任务的结果 状态
# 注意: 在win 下进行执行cmd命令
1.需要将celery_app路径 加入到python找包的列表(sys.path)中 #app.py作为执行文件
celery -A app worker -l info -P gevent
2.如果没有将celery_app路径 加入到python找包的列表(sys.path)中 # task.py作为执行文件
celery -A task worker -l info -P gevent
# 为什么:
如果没有加入python找包的列表中(sys.path),执行:[celery -A app worker -l info -P gevent ] 就会出现 ModuleNotFoundError错误,本人通过pycharm创建项目,pycharm会自动的将最外层包添加到(sys.path)python找包的列表中,通过cmd执行,脱离了pycharm无法找到

app.py

import os
import sys
from celery import Celery
sys.path.append(os.path.abspath(os.path.dirname(__name__)))
# 实例化对象
app = Celery('task')
# 位置文件
app.config_from_object('config')
# 自动搜索任务 通过传入文件列表(str)的参数扫描任务
app.autodiscover_tasks(['task'])

config.py

# 中间件链接
BROKER_URL = 'redis://127.0.0.1:6379/2'
# 使用存储结果的数据库链接
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'
# 设置当前celery时区
CELERY_TIMEZONE = "Asia/Shanghai"

task.py

from app import app as celery_app
import time
# 消费者
# 定义celery任务
@celery_app.task
def send_weixin(name):
print(f'开始发送微信---发送者{name}')
time.sleep(3)
print('微信发送成功')
return 'ok'
# 定义celery任务
@celery_app.task
def send_email(name):
print(f'开始发送邮件---发送者{name}')
time.sleep(3)
print('邮件发送完成')
return 'ok'

main.py

from task import *
# 执行任务 生产者
# 当进行执行时,那么就会执行celery内部的任务
# celery就会链接中间件,执行任务,将任务结果存储到数据库中
result_email = send_email.delay('haha')
print(result_email.id)
result_weixin = send_weixin.delay('hah')
print(result_weixin.id)
# id的作用: 当任务执行完毕后,不会将返回结果进行返回,而是返回一个需要的id,可以根据这个id,从CELERY_RESULT_BACKEND 存储的数据库中进行获取对应的结果

result.py

from app import app as celery_app # celery 创建实例对象
from celery.result import AsyncResult # 异步获取结果
# 根据任务的id 通过AsyncResult对象从 存储结果的库中获取相应的数据
# id 就是任务执行返回的异步id值
async_result = AsyncResult(id='de187cb2-51b5-4a66-92ca-8ca96b60d1f0', app=celery_app)
if async_result.successful(): # 返回 'True' 如果任务成功执行。
result = async_result.get() # 获取任务的返回值
print(result)
elif async_result.failed(): # 返回 'True' 如果任务执行失败
print('执行失败')
elif async_result.status == 'PENDING': # 任务正在等待执行
print('任务正在等待执行')
elif async_result.status == 'RETRY': # 该任务将重试,可能是因为失败。
print('该任务将重试,可能是因为失败。')
elif async_result.status == 'STARTED': # 任务已启动。
print('任务已启动')
elif async_result.status == 'FAILURE': # 任务引发异常,或已超出重试限制。
print('任务引发异常,或已超出重试限制')
elif async_result.status == 'SUCCESS': # 任务已成功执行
print('任务已成功执行')

Celery-多级目录

task_cel
-celery_tasks # 消费者
- __init__.py # win需要将实例对象放在__init__中
- celery.py # celery实例化对象 win不可用(会出现找不模块的现象)
- task01.py # 不同的任务类型存放在不同的任务目录下
- task02.py #
check_result.py # 获取执行任务执行的结果
produce_task.py # 执行celery任务
如果使用celery.py文件创建celery实例对象的情况下:
# 在win 环境下无法启动 出现了ModuleNotFoundError(报错找不模块或者找到路径或者没法办发现任务)
celery -A celery_tasks.celery worker -l info -P gevent
在win环境下使用多级目录建议使用__init__.py
当使用celery_tasks就会自动触发init.py文件的代码
celery -A celery_tasks worker -l info -P gevent
# 执行命令时需要在task_cel文件夹下执行
celery -A celery_tasks worker -l info -P gevent

--init--.py

import os
import sys
from celery import Celery
sys.path.append(os.path.abspath(os.path.dirname(__name__)))
# 1.可以添加 Celery(include=['celery_tasks.task01','celery_tasks.task02'])
# 2.也可以使用autodiscover_tasks(['celery_tasks.task01','celery_tasks.task02']) 自动扫描
# 3.也可以使用在配置文件中 imports = (
'celery_app.task1',
'celery_app.task2',
)
app = Celery('celery_dom', )
# 设置配置
app.conf.update(
broker_url='redis://127.0.0.1:6379/1',
result_backend='redis://127.0.0.1:6379/2',
timezone='Asia/Shanghai'
)
# 自动发现任务,扫描任务,
app.autodiscover_tasks(['celery_tasks.task01', 'celery_tasks.task02'])

task01.py与task02.py

from celery_tasks import app
import time
# task01.py
@app.task
def send_email(name):
print(f'开始发送邮件----发送人{name}')
time.sleep(3)
print('发送完成')
return '666'
# task02.py
@app.task
def send_wixin(name):
print(f'开始发送微信----发送人{name}')
time.sleep(4)
print('发送完成')
return '666'

check_result.py

from celery_tasks.task01 import send_email
from celery_tasks.task02 import send_wixin
result1 = send_email.delay('啊哈')
print(result1.id)
result2 = send_wixin.delay('啊哈')
print(result1.id)

check_result.py

from celery_tasks import app as celery_app # celery 创建实例对象
from celery.result import AsyncResult # 异步获取结果
# 根据任务的id 通过AsyncResult对象从 存储结果的库中获取相应的数据
# id 就是任务执行返回的异步id值
async_result = AsyncResult(id='c296cb30-0a1e-4b62-be00-c2529c5ab434', app=celery_app)
if async_result.successful(): # 返回 'True' 如果任务成功执行。
result = async_result.get() # 获取任务的返回值
print(result)
elif async_result.failed(): # 返回 'True' 如果任务执行失败
print('执行失败')
elif async_result.status == 'PENDING': # 任务正在等待执行
print('任务正在等待执行')
elif async_result.status == 'RETRY': # 该任务将重试,可能是因为失败。
print('该任务将重试,可能是因为失败。')
elif async_result.status == 'STARTED': # 任务已启动。
print('任务已启动')
elif async_result.status == 'FAILURE': # 任务引发异常,或已超出重试限制。
print('任务引发异常,或已超出重试限制')
elif async_result.status == 'SUCCESS': # 任务已成功执行
print('任务已成功执行')

Celery-定时功能

固定某个事件进行执行

Celery-单级目录(简单)

目录结构

celery_dom
-main.py # 当前脚本中存放是celery的对象与配置信息
-task.py # 存放的是celery任务
-produce.py # 定时执行celery的任务

main.py

from celery import Celery
import os
import sys
# 将main的父级文件添加到sys.path中(一定要加 否者就会报找不文件包的错误)
path = os.path.abspath(os.path.dirname(__name__))
sys.path.append(path)
print(path)
app = Celery('task')
# 设置配置
app.conf.update(
broker_url='redis://127.0.0.1:6379/1',
result_backend='redis://127.0.0.1:6379/2',
timezone='Asia/Shanghai')
# 自动搜索与发现任务
app.autodiscover_tasks(['task'])

task.py

from main import app
import time
@app.task
def send_email(name):
print(f'当前人{name}发送邮件中')
time.sleep(3)
print('发送完成')
return 'ok'
任务

produce.py

import datetime
from task import send_email
# 方式1
# 本地时间 手动延迟时间
v1 = datetime.datetime(2023, 2, 16, 22, 19)
print(v1)
# 国标时间 -8
v2 = datetime.datetime.utcfromtimestamp(v1.timestamp())
print(v2)
# 当前的这个时间就是设置的定时启动的时间
# celery任务.apply_async(args=[传入的celery参数,eta=设置的启动时间必须是utc标准时间]) 设置定时任务
result = send_email.apply_async(args=['哈哈',], eta=v2)
print(result.id)
'''
当进行启动时,
1.先回获取到当前任务id值
2.启动的celery就会处于挂起状态 相当于等待执行
3.当规定的定时时间到了,就会进行执行
'''
# 方式2 自动延迟时间
ctime = datetime.datetime.now()
# 默认使用的是utc时间
utc_ctime = datetime.datetime.utcfromtimestamp(ctime.timestamp())
# 将当前时间 + 10秒 时间
from datetime import timedelta
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay # 也就是在当前时间的基础上 + 10秒钟 在进行执行celery任务
# 执行定时任务
result = send_email.apply_async(args=['哈哈',], eta=task_time)
print(result.id)

启动

celery -A main worker -l info -P gevent

Celery-多级目录执行

目录结构

celery_app
- __init__.py # 存放celery配置与对象
- task1.py # 存放任务1
- task2.py # 存放任务2

命令说明:

# 与上其他功能不同,多目录结构通过设置任务调度器参数进行命令形式向队列中存放任务
# 1.命令1:
指令:celery -A <文件.py> worker -l info -P gevent -c 5
1.1.这个命令启动消费者(celery服务),用来监听broker_url中间件队列中得任务。
1.2.如果对了中没有任务这个生产者就会阻塞。
1.3.当生产者(调用了celery的任务)触发任务,那么就会将任务存放到中间件队列中,那么消费者才能进行执行任务。
# 2.命令2
指令celery -A <文件.py> beat -l info
2.1.当启动这条命令时,就会读取启动文件中得配置信息(beat_schedule配置信息),根据配置信息的时间进行调度任务
2.2.这条命令就是将beat_schedule配置的任务调度器,根据时间设置,将任务添加到中间件队列中,让消费者进行执行
2.3和第一条命令不同,属于根据任务调度器向队列中插入任务
2.4如果当前命令启动,那么就会按照任务调度器时间周期向中间件队列中插入任务(如果不启动worker命令的话,那么队列中的任务就会一直存在,直到启动woker命令才会执行,除了清空队列)
# 3.上面的两条命令相当于不同的两个服务,一个是取任务执行,一个存任务到队列中

init.py

from datetime import timedelta
from celery import Celery
import os
import sys
path = os.path.abspath(os.path.dirname(__name__))
sys.path.append(path)
print(path)
app = Celery('task')
# 设置配置
app.conf.update(
broker_url='redis://127.0.0.1:6379/1',
result_backend='redis://127.0.0.1:6379/2',
timezone='Asia/Shanghai')
# 自动搜索与发现任务(发现celery_app下的task.py文件中得两个任务)
app.autodiscover_tasks(['celery_app.task01', 'celery_app.task02'])
# 设置任务调度器配置(指定多目录结构,自动执行命令)
# 大写:CELERYBEAT_SCHEDULE 小写 beat_schedule
app.conf.beat_schedule = {
'''
配置参数说明
"调度器任务的名称(随便起)见名知意":{
路径需要能让celery扫描或发现的任务
'task':"执行任务的函数路径", 例如:celery_app.task01.send_email
'schedule':'执行的时间设置',
'args':('传递的参数',)
}
'''
"add_send_email_10": {
# 每10秒中执行的任务的路径
'task': 'celery_app.task01.send_email',
# 每短时间执行的一次
# 1.schedule:1.0 直接加数字就是秒
# 2.schedule:crontab(minute='*/1') # */1 每一分钟执行一次
'schedule': timedelta(seconds=6), # 6秒执行一次
# 传递的执行任务的参数
'args': ('我是传递的参数',)
},
# "add_send_weixin_20":{}
}

task1.py 与 task2.py

from celery_app import app
import time
# task1.py
@app.task
def send_email(name):
print(f'当前人{name}发送邮件中')
time.sleep(3)
print('发送完成')
return 'ok'
# task2.py
@app.task
def send_wixin(name):
print(f'当前人{name}发送邮件中')
time.sleep(3)
print('发送完成')
return 'ok'

执行命令

1.需要先执行celery监听队列的命令
celery -A celery_app worker -l info -P gevent -c 5 # -c 5指的是并发量为5 一次性执行5个任务)
2.在进行执行beat命令将任务存入队列中
celery -A celery_app beat -l info
原因:
如果先执行beat命令,那么就会根据配置信息中的调度器时间将任务存储到中间件队列中那么就会在启动worker命令时间间隔内(如果时间比较短,忘记启动worker)存储了多条任务到队列中,就会立马发现队列中得任务并执行。
# 在关闭是注意:请先关闭beat,在关闭worker,如果beat是后关闭的,那么可能会在关闭前的时间有任务存储到队列中,当下在进行启动worker时,就会出现直接进行执行任务(beat历史遗留任务)

Django框架使用Celery

如果使用的不是一台服务器,而是多台,那么
消费者(celery)的任务代码要在生产者中有一份(一模一样)
消费者监听中间件
生产者将任务传入中间件,消费者去执行

目录结构

django_celery_dom
- app01 # 子程序
...
views.py # 存放的视图文件
- django_celery_dom # 主程序
....
urls.py # 存放的路由
- mycelery # 存放的celery程序
__init__.py # win环境问题,将celery对象存放到当前文件中
config.py # 配置文件
main.py # celery对象 因为win环境问题,无法在当前文件存放会出现ModuleNotFoundError错误
sms # 存放celery任务文件
__init__.py
tasts.py # 任务所在位置
启动命令需要在django_celery_dom文件夹下进行启动
celery -A mycelery worker -l info -P gevent
注意:
1.路径问题确定好路径(尤其是sys.path python寻找的路径)
2.环境问题,celery对win不支持

views.py

from django.shortcuts import render
from django.http import JsonResponse
from mycelery.sms.tasks import send_email # 导入celery的任务
def home(request):
# 完成异步任务调度,当访问这个接口或者路由时,就会触发当前的celery的任务去执行
# 1.异步执行,当前视图立马就会返回响应体给前端,而这个任务就会异步进行执行下去
send_email.delay()
# 2.完成定时任务调度
from datetime import datetime,timedelta
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
time_delay = timedelta(seconds=10)
task_time = utc_ctime + time_delay
result = send_email.apply_async([], eta=task_time)
print(result.id)
return JsonResponse({'code':200})

urls.py

from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('home/',views.home) # 绑定的路由关系
]

mycelery.init.py

'''
当前这包就是一个关于celery的程序包
不属于django的一部分,而是属于单独的一段程序
'''
import os
import sys
from celery import Celery
# 将mycelery添加到python寻找的路径列表中
f_file = os.path.dirname(os.path.dirname(__name__))
sys.path.append(f_file)
# 创建对象
app = Celery('sms')
# 将django中配置文件进行加载(django的配置文件都在settings文件中)
os.environ.setdefault('DJANGO_SETTINGS_MODULE','celeryPros.settings.dev')
# 加载celery自己的配置文件
app.config_from_object('mycelery.config')
# 自动扫描任务 加载任务
app.autodiscover_tasks(['mycelery.sms.tasks'])

mycelery.config.py

'''celery配置信息'''
broker_url='redis://127.0.0.1:6379/1'
result_backend='redis://127.0.0.1:6379/2'

mycelery.sms.tesks.py

'''这是celery任务'''
from mycelery import app
import time
@app.task
def send_email():
print('ok')
time.sleep(3)
print('完成')

本文作者:_wangkx

本文链接:https://www.cnblogs.com/kaixinblog/p/16307732.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _wangkx  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起