Celery

Celery

1.Celery 简介

1.1celery 是什么

Celery(芹菜)是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。

专注于实时处理的异步任务队列,同时也支持任务调度。

注意Celery 4.0支持Django 1.8和更高版本。对于Django 1.8之前的版本,请使用Celery 3.1。

1.2.为什么使用celery

  • 可以用于解决异步任务和定时任务
  • 使用方便简单

Celery是一个使用Python开发的分布式任务调度模块,因此对于大量使用Python构建的系统,可以说是无缝衔接,使用起来很方便。(python开发,使用方便)
Celery专注于实时处理任务,同时也支持任务的定时调度。因此适合实时异步任务定时任务等调度场景。(适用解决异步任务;定时任务)
Celery需要依靠RabbitMQ等作为消息代理,同时也支持Redis甚至是Mysql,MongoDB等,当然,官方默认推荐的是RabbitMQ。

1.3.celery的优点

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

1.4.Celery 特性

  • 方便查看定时任务的执行情况, 如 是否成功, 当前状态, 执行任务花费的时间等.
  • 可选 多进程, Eventlet 和 Gevent 三种模型并发执行.
  • Celery 是语言无关的.它提供了python 等常见语言的接口支持.

1.5.Celery 扮演生产者和消费者的角色

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

任务结果存储

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

另外, Celery还支持不同的并发和序列化的手段

  • 并发:Prefork, Eventlet, gevent, threads/single threaded
  • 序列化:pickle, json, yaml, msgpack. zlib, bzip2 compression, Cryptographic message signing 等等

1.6.celery架构图

img

1.7. 产生任务的方式

  • 发布者发布任务(WEB 应用)
  • 任务调度按期发布任务(定时任务)

1.8.celery 依赖三个库: 这三个库, 都由 Celery 的开发者开发和维护.

  • billiard : 基于 Python2.7 的 multisuprocessing 而改进的库, 主要用来提高性能和稳定性.
  • librabbitmp :C 语言实现的 Python 客户端
  • kombu : Celery 自带的用来收发消息的库, 提供了符合 Python 语言习惯的, 使用 AMQP 协议的高级借口.

1.9. celery的应用场景

celery是一个强大的 分布式任务队列的异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。

  • 异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
  • 定时任务:定时执行某件事情,比如每天数据统计

2.Celery执行流程

image-20220912205816214

3.Celery的安装

你可以通过pip安装Celery:

pip install -U Celery				# -U 表示 update

4.Celery执行异步任务

4.1.Celery的使用

创建一个python项目,由生产消费模型。我这命名Celerypro

  • 启动celery (消费者)
    • 监听事件
    • 开启默认并发量
    • 消息中间件,创建队列
    • 等待事件触发
  • 海量数据 (生产者)
    • 调用消费者方法(自定义处理数据方法).delay(参数)
    • 创建与消息中间件中的队列连接
    • 执行自定义处理方法
    • celery返回存储结果查询key(无论成功与否)
    • celery将结果存储存储结果中(查询可知)
  • 消费者在合适时机,通过key,前往存储结果,查询结果,获取数据

4.1.1消费者:celery_task.py

celery_task,处理外部传递来的海量数据,处理(task)不就是消费者?

import time
import celery


backend = 'redis://:root@127.0.0.1:6379/1'        # 异步结果(backend)  results
broker = 'redis://:root@127.0.0.1:6379/2'         # 消息中间件(broker) transport
cel = celery.Celery('test', backend=backend, broker=broker)   



@cel.task
def send_email(name):
    print("向%s发送邮件..." % name)
    time.sleep(5)
    print("向%s发送邮件完成" % name)
    return "ok"									# 结果存储在存储结果中(ps:redis)


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

# 消费者(处理生产者生产的数据),如何处理:一个个的处理方法函数

# Usage: celery [OPTIONS] COMMAND [ARGS]...
# celery -A celery_task worker -l info
#   - 中间经过:
#       - celery 通过命令连接 消息中间件(redis),创建队列,监听此队列
#       - 启动多个worker 监听任务

# redis 细分16个库(0-15)
# 链接格式:'redis://:密码@ip:port/part'      


# 添加@cel.task装饰器,send_email方法就是celery异步任务;中间就会有一个delay方法
# 当你在生产者环境中调用delay方法时,它会执行:
#   - 生产者连接消息中间件中由results创建的队列
#   - 连接传参:函数名称 + 函数参数

4.1.2生产者:produce_task.py

from celery_task import send_msg, send_email


result1 = send_email.delay('yang@gmail.com')
# <class 'celery.result.AsyncResult'>
print(type(result1), result1)

result2 = send_msg.delay('yang@gmail.com')
# <class 'str'>
print(type(result2.id), result2.id)     

# 最好转化为str, celery 返回的不是结果数据,而是通过返回的结果作为钥匙去redis(存储结果)取。
# 生产者(模仿工作中大量的并发数据,同时发送)

# 需要将celery任务,也就是处理数据的函数导入,并执行函数的delay方法:result = func.delay(args,kwargs)


# 在生产者中只要调用消费者中的delay方法
#   - 生成者会链接到由celery创建的队列中。
# 	- 找到处理此生产的函数,并创建成为一个worker
# 	- 将函数处理的结果存储到存储结果中(ps:redis)
# 	- 并返回获取结果的key (ps:redis 查询的key)

4.1.3执行celery异步文件

控制台命令行执行:

celery -A celery_app worker -l info
options
    -A APPLICATION 		
    -l 日志输出 
    worker 表示消息中间件	

prefork:默认可能会报错,报错安装: eventlet模块,并在执行时,添加: -P eventlet  (携程)
'-P' or '--pool': 'prefork', 'eventlet', 'gevent', 'solo', 'processes', 'threads'

4.1.4获取存储结果

from celery.result import AsyncResult
from celery_task import cel	# 消费者中创建的cel


# AsyncResult(id=celery_result.id, app=func_in_app)
async_result = AsyncResult(id="3177abcc-a949-46b7-afaa-60234b1e7a4d", app=cel)


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

4.2多任务结构

创建一个python项目,由生产消费模型。我这命名CeleryTask

4.2.1目录结构

image-20220913180352192

4.2.2celery.py

文件名可以自拟,不一定要用celery

# 此位celery的配置文件

import celery

# 准备好消息中间件,存储结果的 path
border = 'redis://:root@127.0.0.1:6379/1'
backend = 'redis://:root@127.0.0.1:6379/2'
# 包含以下两个任务文件.去相应的py文件中找任务,对多个任务做分类(path,split='.')
include = [
    'celery_tasks.async_task01',
    'celery_tasks.async_task02',
]


# 创建Celery app
app = celery.Celery('celery_app_name', backend=backend, broker=border, include=include)

# 设置当前时区
app.conf.timezone = 'Asia/Shanghai'

# 是否使用utc时间
app.conf.enable_utc = False

4.2.3async_task01.py

import time
from .celery import app


@app.task
def async_task01_fun(name):
    time.sleep(5)
    print('async_task01_fun(name):', name)
    print("完成向%s发送邮件任务" % name)
    return __name__

4.2.4async_task02.py

import time
from .celery import app


@app.task
def async_task02_fun(name):
    time.sleep(5)
    print('async_task02_fun(name):', name)
    print("完成向%s发送短信任务" % name)
    return __name__

4.2.5produce_task.py

# 导入生产的任务
from celery_tasks import async_task01, async_task02


result = async_task01.async_task01_fun.delay('yang')
print(result.id)

result2 = async_task02.async_task02_fun.delay('yang')
print(result2.id)

4.2.6check_result.py

# 导入celery中的异步返回,与celery app对象
from celery.result import AsyncResult
from celery_tasks.celery import app


async_result = AsyncResult(id='10b21178-6ba0-464a-8be5-d6b5586ea290', app=app)

if async_result.successful:
    """success"""
    print(async_result.get())
    # result.forget() # 将结果删除,执行完成,结果不会自动删除
    # async.revoke(terminate=True)  # 无论现在是什么时候,都要终止
    # async.revoke(terminate=False) # 如果任务还没有开始执行呢,那么就可以终止。

elif async_result.failed():
    print('执行失败')
elif async_result.status == 'PENDING':
    print('任务等待中被执行')
elif async_result.status == 'RETRY':
    print('任务异常后正在重试')
elif async_result.status == 'STARTED':
    print('任务已经开始被执行')

# status:PENDING,STARTED,RETRY,FAILURE,SUCCESS

4.2.7执行异步文件命令

celery -A celery_tasks worker -l info -P eventlet

5.Celery执行定时任务

image-20220914200653943

redis命令

redis: 
	- 登录
		redis-cli -h host -p port -a password
	- 切换区域(0-15)
		select idnex
	- 查询所有键
		keys *
	- 查看key对应值的类型
		type key
	- 查看redis中list类型的所有数据
		LRANGE KEY START STOP
		LRANGE KEY  0     -1
	-删除redis中的key
	delete key
		

目录结构

image-20220914234943680

5.1 config.py

xxx.config.beat_schedule: 定时任务相关的调度器 ,xxx是自己创建按的celery_app

from datetime import timedelta

from celery import Celery

app = Celery(
    main='app_name',
    broker='redis://:root@127.0.0.1:6379/1',
    backend='redis://:root@127.0.0.1:6379/2',
    include=[       # 用于解决消费者方的路径问题
        'auto_task01',
        'auto_task02'
    ]
)


# celery schedule ( 时间表 )       定时向消息中间件中的队列中插入任务

app.conf.beat_schedule = {

    # 自定义key(尽量见名知意)
    "add-every-5-second": {
        'task': 'auto_task01.auto_task01',  # 定时任务的路径,通过'.'实现分割,用于解决生产者方的问题
        'schedule': timedelta(seconds=5),  # schedule(日程): 间隔时间
        'args': ('yang',)  # 方法args参数
    },
    "add-every-3-second": {
        'task': 'auto_task02.auto_task02',
        'schedule': timedelta(seconds=3),
        'args': ('alex',),
        'kwargs': {
            'name': 'yang',
            'age': 18
        }
    },
}

5.2auto_task01.py

import time
from config import app


@app.task
def auto_task01(name):
    print(name)
    time.sleep(5)
    return __name__

5.3auto_task02.py

import time
from config import app


@app.task
def auto_task02(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)
    time.sleep(5)
    return __name__

5.4redis_handler.py

import redis

conn = redis.Redis(host='localhost', port=6379, db=1, password='root')  # broker
result = conn.lrange('celery', 0, -1)       # celery 存储在redis 数据库中的 消息中间件中的任务(消息)队列
print('type: celery', conn.type('celery'))  # 查看celery 存储的格式类型:list

for task in result:
    print('task:    ', task, end='\n\n')

# 解决”历史遗留问题“
conn.delete('celery')

result = conn.lrange('celery', 0, -1)
print(result)

执行定时任务

处理定时任务:worker

celery -A config worker -l info -P eventlet

添加定时任务:beat
Celery Beat进程会读取配置文件的内容,周期性的将配置中到期需要执行的任务发送给任务队列

celery -A config beat -l info

6.Django中使用Celery异步任务

目录结构

image-20220915145317789

关键步骤

app.config_from_object		# 导入配置文件配置项

ps:
	app = Celery()
	app.config_from_object(path_Split_dot)
	<=> app = Celery(config)

    
# 加载任务,默认任务存放位置为:xxx.tasks 文件,
app.autodiscover_tasks(packages:list[str]=None, related_name='tasks', force=False)		
ps:
	packages = ['celery_tasks.tasks.asy.sms', 'celery_tasks.tasks.asy.email']
	app.autodiscover_tasks(packages)
    <=> 默认将sms目录与email目录下的tasks文件中的方法作为celery_task

    
#使用Django时要通知Django当前使用的是哪个配置文件;可以改变环境变量 DJANGO_SETTINGS_MODULE 实现这一点(环境变量),让django知道使用的是哪个配置文件。
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DCelery.settings')

6.1Celery 消费者区域

6.1.1main.py

celery的主配置文件。

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DCelery.settings')			# 务必在创建celery app之前(之后操作数据库相关的会找不到)
app = Celery('DCelery')

# 导入配置文件
path = 'celery_tasks.config'
app.config_from_object(path)

# 导入任务文件(模块/包)
packages = ['celery_tasks.tasks.asy.sms', 'celery_tasks.tasks.asy.email',
            'celery_tasks.tasks.schedule']
app.autodiscover_tasks(packages)

6.1.2config.py

方便解耦,部分配置文件存放。

# 配置文件(解耦)(名字固定)
broker_url = 'redis://:root@127.0.0.1:6379/1'
result_backend = 'redis://:root@127.0.0.1:6379/2'

6.1.3tasks任务

celery的任务就一个要求:任务文件(模块)的名字必须是tasks

# path:celery_tasks/tasks/asy/sms/tasks.py
import time
import random
from celery_tasks.main import app


@app.task
def send_sms(name):
    sms = random.randrange(1000, 10000)
    print('正在向%s发送短信....' % name)
    time.sleep(5)
    print('%s已经接受到短信....' % name)
    return sms


# path:celery_tasks/tasks/asy/email/tasks.py
@app.task
def send_email(name):
    print('正在向%s发送短信....' % name)
    time.sleep(4)
    print('%s已经接受到短信....' % name)
    return True


# path:celery_tasks/tasks/shedule/tasks.py
@app.task
def clear_db_orders(name):
    order_id = random.randrange(1000, 10000)
    print('正在清楚%s的订单信息....' % name)
    time.sleep(5)
    print('已经清楚完成%s的订单信息....' % name)
    return order_id


6.2 Django 生产者区域

其实没什么变化,直接导入使用。

views.py

from datetime import datetime
from datetime import timedelta

from django.http import HttpResponse

from celery_tasks.tasks.asy.email.tasks import send_email
from celery_tasks.tasks.asy.sms.tasks import send_sms
from celery_tasks.tasks.schedule.tasks import clear_db_orders


def test(request):

    # 异步任务(同步操作则会等待发送完成之后加载)
    name = 'yang'
    send_sms.delay(name)
    send_email.delay(name)

    # 定时任务
    ctime = datetime.now()
    utc_time = datetime.utcfromtimestamp(ctime.timestamp())
    delay_time = timedelta(seconds=10)
    etc_time = utc_time + delay_time
    clear_db_orders.apply_async(args=('yang',), etc=etc_time)

    return HttpResponse('OK')

6.3命令行启动celery

在django中celery充当的是一个消费者的角色,所以只需要使用worker模式即可。(视情况而定)

celery -A celery_tasks.main worker [-l info -P eventlet -c 5]		# []中的可以选填

django中还有django-celery模块。

7.Django中使用Celery定时任务

如果想要在django中使用定时任务功能同样是靠beat完成任务发送功能,当在Django中使用定时任务时,需要安装django-celery-beat插件。以下将介绍使用过程。

7.1安装配置

1.beat插件安装

pip3 install django-celery-beat

2.注册APP

INSTALLED_APPS = [
    ....   
    'django_celery_beat',
]

3.数据库变更

python manage.py migrate
python3 manage.py migrate django_celery_beat

4.分别启动woker和beta

#启动beta 调度器使用数据库
celery -A proj beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler  
#启动woker
celery worker -A taskproj -l info 

5.配置admin

urls.py写入:

# urls.py
from django.conf.urls import url
from django.contrib import admin
 
urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

6.创建用户

python3 manage.py createsuperuser 

7.登录admin进行管理

并且还可以看到我们上次使用orm作为结果存储的表。http://127.0.0.1:8000/admin/login/

img

使用示例:

imgimg

查看结果:

img

小bug: 再次支付时,应该是直接获取数据库中的订单信息直接去结账,而不是重新生成订单。

警告提示

这里的debug是说 :userwarning:使用settings.debug会导致内存泄漏,请不要在生产环境中使用此设置!

[2022-09-15 15:06:32,783: WARNING/MainProcess] D:\Python\DCelery\lib\site-packages\celery\fixups\django.py:203: UserWarning: Using settings.DEBUG leads to a memory
            leak, never use this setting in production environments!
  warnings.warn('''Using settings.DEBUG leads to a memory...''')

django.conf.settings

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# 所以我们就要在settings配置文件中把debug改为False(生产请务必使用)

8.Celery commands params

(Celery) E:\StudyClass\Python_Frame\Celery\CeleryTimeTask\CeleryTimeTask>celery
Usage: celery [OPTIONS] COMMAND [ARGS]...

Options:
  -A, --app APPLICATION			# APP
  -b, --broker TEXT				# broker
  -c							# 并发量
  -P, --pool 					# 并发类型
  
Commands:
  beat     Start the beat periodic task scheduler.			    # 生产者(每隔多少时间向队列中插入任务)
  worker   Start worker instance.								# 消费者(从队列中获取任务,并执行,存储结果,返回id)

posted @ 2022-09-15 15:42  Redskaber  阅读(261)  评论(0)    收藏  举报