Bootstrap

【celery】celery分布式处理异步定时任务总结


前言

> 什么是celery?

celery是一个简单灵活的分布式系统,可以实时处理异步任务队列,也支持任务调度
celery框架由消息中间件(message broker),任务执行单元(worker),任务结果储存(task result store)组成


Celery支持不同的并发和序列化的手段

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

>celery工作原理

在这里插入图片描述
生产者: 用户发送请求到我们的项目我们的项目可以将用户的请求发送到我们的中间件中
消费者: celery会产生多个worker(进程)对中间件进行监测,如果里边有任务他就会立刻执行任务
每个worker下边还会有很多的线程处理速度也会非常快,每分钟可以处理百万计任务
中间件 消息队列,用于存放生产者发送的需要执行的任务(官方推荐使用RabbitMQ)

> celery可以干什么?

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

eg1:每隔0.1秒给你的好闺蜜发一条短信
eg2:一个小时后获取执行某宝某商品的抢购
eg3: 同一时间给大量客户发送邮件等等

> celery有什么优点?

首先celery使用非常简单,容易上手,保证你看完这边文章就可以轻松自如的使用celery。
而且celery具有高可用性,维护起来也是非常的方便。
celery每分钟可以处理百万计的任务。
celery几乎每个部分都可以扩展,自定义的实现日志记录,消费者,调度器,生产者,broker等

一、celery的安装

pip install celery

二、celery异步任务的执行

1,创建项目celeryproject
2,创建异步任务执行文件celery_task
配置文件 celery_task

import celery   #模块导入
import time
backend='redis://127.0.0.1:6379/1'   #任务结果储存地址  
broker='redis://127.0.0.1:6379/2'     #消息中间件   我们暂时使用redis充当,推荐使用RabbitMQ
cel=celery.Celery('test',backend=backend,broker=broker)    #’test‘任务名可以随便取


#定义需要异步执行的任务  
@cel.task          
def send_email(name):     
    print("向%s发送邮件..."%name)
    time.sleep(3)
    print("向%s发送邮件完成"%name)
    return "ok"

执行任务文件

from celery_task import send_email

result1 = send_email.delay("张三")
print(result1.id)
result2 = send_email.delay("李四")
print(resul2.id)

执行命令

celery worker -A 文件路径 -l info

在这里执行后会发现两个任务几乎是同时执行的,并不会影响到我们的主线程执行其他任务



执行完毕后在创建一个文件result来查看我们的任务执行结果

from celery.result import AsyncResult
from celery_task import cel
ID =‘输入刚才执行后返回的ID’ 
async_result=AsyncResult(id=ID, 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('任务已经开始被执行')

三、celery定时任务的执行

定时任务在这里给大家介绍三种方法:在固定时间执行 在固定时间后执行 在固定周期执行

1,在固定时间执行

from celery_task import send_email
from datetime import datetime

T1 = datetime(2022, 5, 20, 13, 14, 00)     #在2022年5月20日13时14分00秒给隔壁老王发消息
print(v1)
T2 = datetime.utcfromtimestamp(T1.timestamp())   #把T时间转变成国标(utc)时间
print(v2)
result = send_email.apply_async(args=["隔壁老王",], eta=T2)
print(result.id)

2,在固定时间后执行

from celery_task import send_email
from datetime import datetime
from datetime import timedelta

time = datetime.now()     #获取当前时间
# 默认用国标(utc)时间
utc_time = datetime.utcfromtimestamp(time.timestamp())

time_delay = timedelta(seconds=10)     #10秒后执行任务
task_time = utc_ctime + time_delay

# 使用apply_async并设定时间
result = send_email.apply_async(args=["隔壁老王"], eta=task_time)
print(result.id)

3,在固定周期执行

这里需要修改配置文件

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


backend='redis://127.0.0.1:6379/1'   #任务结果储存地址  
broker='redis://127.0.0.1:6379/2'     #消息中间件   我们暂时使用redis充当,推荐使用RabbitMQ
cel = Celery('tasks', broker=broker, backend=backend, include=[
    'celery_task.任务1',    #任务路径  否则找不到
    'celery_task.任务2',
])
cel.conf.timezone = 'Asia/Shanghai'     #时区
cel.conf.enable_utc = False        #是否使用国标时间

cel.conf.beat_schedule = {
    'add-everyTask-10s': {                # 名字随意命名
        # 执行task下的test_celery函数(任务路径)   
        'task': 'celery_task.task.send_email',
        # 'schedule': 1.0,    #每隔一秒发送一次
        # 'schedule': crontab(minute="*/1"),   #每分钟发送一次   crontab为定时函数
        'schedule': timedelta(seconds=10),   #每隔十秒发送一次     timedelta时差函数
        # 传递参数
        'args': ('隔壁老王',)
    },
    # 'add-everyTask': {
    #     'task': 'celery_tasks.task.send_email',
    #     每年5月20号,13点14分给老王发短信
    #     'schedule': crontab(minute=14, hour=13, day_of_month=20, month_of_year=5),
    #     'args': ('隔壁老王',)
    # },
}

这里的cel.conf.beat_schedule代表每隔固定的时间去消息队列添加任务,启动命令为

$ celery beat -A 文件路径
# Celery Beat进程会读取配置文件的内容,周期性的将配置中到期需要执行的任务发送给任务队列

启动worker进程

$ celery -A 配置文件路径 worker -l info 或者$ celery -B -A proj worker -l info

注意: 如果先启动beat后启动woeker会产生一个bug
先启动beat会不断的向消息队列添加任务在worker没有启动之前是不会执行,所以在启动worker后会有任务堆积。

$ celery -A 配置文件路径 worker -l info -C 数量  
#-C 数量    可以并发执行

四、在Django中使用celery

1,在根目录创建一个包(page)文件celery_task
2,包文件下创建两个py(config和main 注意名字不能是别的)文件和数个包(一个包代表一个不同的任务,数量看需求)文件

celery_tsaks

config.py代码

broker_url = 'redis://127.0.0.1:6379/1'      #消息中间件地址(暂时用redis,官方推荐RabbitMQ)
result_backend = 'redis://127.0.0.1:6379/2'   #结果储存地址   可以没有

RabbitMQ使用教程:“后期出”

main.py代码

# 主程序
import os
from celery import Celery
# 创建celery实例对象
app = Celery("sms")

# 把celery和django进行组合,识别和加载django的配置文件
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Django项目.settings.dev')

# 通过app对象加载配置
app.config_from_object("mycelerys.config")

# 加载任务
# 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
# app.autodiscover_tasks(["任务1","任务2"])
app.autodiscover_tasks(["celery_tasks.sms",])  #注意路劲点包文件不是任务函数


#可以设置周期执行任务  不用可以删除
app.conf.beat_schedule = {
    'add-everyTask-10s': {                # 名字随意命名
        # 执行task下的test_celery函数(任务路径)   
        'task': 'celery_task.sms.tasks.send_sms',
        # 'schedule': 1.0,    #每隔一秒发送一次
        # 'schedule': crontab(minute="*/1"),   #每分钟发送一次   crontab为定时函数
        'schedule': timedelta(seconds=10),   #每隔十秒发送一次     timedelta时差函数
        # 传递参数
        'args': ('隔壁老王',)
    },
    # 'add-everyTask': {
    #     'task': 'celery_tasks.sms.tasks.send_sms',
    #     每年5月20号,13点14分给老王发短信
    #     'schedule': crontab(minute=14, hour=13, day_of_month=20, month_of_year=5),
    #     'args': ('隔壁老王',)
    # },
}
# 启动Celery的命令
# 强烈建议切换目录到mycelery根目录下启动
# celery -A mycelery.main worker --loglevel=info

sms

init.py
tasks.py

celery_tasks下包文件sms下tasks(celery的任务必须写在tasks.py的文件中,别的文件名称不识别!!!)文件代码

from celery_tasks.main import app
import time
import logging

log = logging.getLogger("django")

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

    return "send_sms OK"

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

    return "send_sms2 OK"

发送异步或者定时任务

完成之后我们的Django就可以在view.py随时调用函数向任务队列发送任务这我们分别展示两种不同的方式
模块导入部分

from django.shortcuts import render

# Create your views here.


from django.shortcuts import render,HttpResponse
from mycelerys.sms.tasks import send_sms,send_sms2

异步任务部分

def test(request):

    # 1. 声明一个和celery一模一样的任务函数,但是我们可以导包来解决

	send_sms.delay("110")
	send_sms.delay()   # 如果调用的任务函数没有参数,则不需要填写任何内容

定时任务部分

from datetime import timedelta
from datetime import datetime

def test(request):
	time = datetime.now()
	# 默认用utc时间
	utc_time = datetime.utcfromtimestamp(time.timestamp())
	time_delay = timedelta(seconds=10)
	task_time = utc_ctime + time_delay
	result = send_sms.apply_async(["911", ], eta=task_time)
	print(result.id)
	
	return HttpResponse('ok')

篇尾

小张是一名正在成长的程序猿,如果文章有那些问题或者错误还请大佬指正。

;