celery(分布式任务队列)介绍+在django中异步回调使用+定时任务的使用

学习记录

# 1.立即执行
    res = send_sms.delay(1, 2)
    """
    # 2.定时任务(只执行一次,不是周期性定时任务)
    ctime = datetime.datetime.now()
    # 本地时间转换成utc时间
    utc_ctime = datetime.datetime.utcfromtimestamp(ctime.timestamp())
    target_time = utc_ctime + datetime.timedelta(seconds=3)
    res = send_sms.apply_async(args=[6, 3], eta=target_time)
    """
    return HttpResponse(res.id)
from celery.result import AsyncResult
# task_id 为开启异步任务时生成的task_id
task_id = 'd7a3dc7f-b428-4adf-903c-0b11e9ae21ff'
AsyncResult(task_id).status

结果说明
PENDING没有执行 or 状态未知
RECEIVED已接收
STARTED已启动
SUCCESS成功
FAILURE失败
REVOKED已撤销
REJECTED被拒绝
RETRY重试中

转载自:jcelery(分布式任务队列)介绍+在django中异步回调使用+定时任务的使用_台风吹的博客-优快云博客x

Celery介绍

Celery是一个功能完备即插即用的异步任务队列系统。它适用于异步处理问题,当发送邮件、或者文件上传, 图像处理等等一些比较耗时的操作,我们可将其异步执行,这样用户不需要等待很久,提高用户体验。

简单,易于使用和维护,有丰富的文档。

高效,单个celery进程每分钟可以处理数百万个任务。

灵活,celery中几乎每个部分都可以自定义扩展。

总之一句话,快
一. 官方文档

中文文档http://docs.jinkan.org/docs/celery/getting-started/index.html
二. 流程图

 三. 异步回调使用
1.安装

 -U是update的意思,有就进行更新,没有就安装,后面单独将celery运行起来就可以了

    pip install redis==3.4.1 -i https://pypi.douban.com/simple/
    pip install celery==4.4.2 -i https://pypi.douban.com/simple/

2. 使用目录介绍.

a. 使用celery第一件要做的最为重要的事情是需要先创建一个Celery实例,我们一般叫做celery应用,或者更简单直接叫做一个app。

b. app应用是我们使用celery所有功能的入口,比如创建任务,管理任务等,在使用celery的时候,app必须能够被其他的模块导入。

c. 一般celery任务目录直接放在项目的根目录下即可,路径:

    ├── mycelery/
        ├── config.py     # 配置文件
        ├── __init__.py   
        ├── main.py       # 主程序
        └── sms/          # 一个目录可以放置多个任务,该目录下存放当前任务执行时需要的模块或依赖,也可以每个任务单独一个目录
            └── tasks.py  # 任务的文件,名称必须是这个!!!

3. 主要文件
1.main.py

    # 主程序
    import os
    from celery import Celery
     
    # 创建celery实例对象
    app = Celery("dbj")  # celery对象可以创建多个,所以我们最好给我们当前的celery应用起个名字,比如叫做dbj
     
    # 把celery和django进行组合,需要识别和加载django的配置文件
    import os
     
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dbj.settings.dev')
    # 如果只是使用了logging日志功能的话可以不写以下两句,因为logging是python提供的模块,但是将来可能使用celery来执行其他的django任务,所以我们先写上
    import django
     
    django.setup()
     
    # 通过app对象加载配置
    app.config_from_object("mycelery.config")
     
    # 加载任务
    # 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
    # app.autodiscover_tasks(["任务1","任务2"])
    app.autodiscover_tasks(["mycelery.sms",])
     
     
     

2. config.py

    # 配置文件
    # 1. 有redis有密码
    # 任务队列的链接地址(变量名必须叫这个)
    broker_url = 'redis://:foobared@127.0.0.1:6379/14'
    # 结果队列的链接地址(变量名必须叫这个)
    result_backend = 'redis://:foobared@127.0.0.1:6379/15'
     
     
    # 2. redis无密码写法
    # 任务队列的链接地址(变量名必须叫这个)
    # broker_url = 'redis://127.0.0.1:6379/14'  
    # 结果队列的链接地址(变量名必须叫这个)
    # result_backend = 'redis://127.0.0.1:6379/15'
     

3. 创建一个任务文件sms/tasks.py,并创建任务,代码:

    # celery的任务必须写在tasks.py的文件中,别的文件名称不识别!!!
    from mycelery.main import app
     
    @app.task(name="send_sms")  # name表示设置任务的名称,如果不填写,则默认使用函数名(路径)做为任务名
    def send_sms():
        print("发送短信!!!")
     
    @app.task  # name表示设置任务的名称,如果不填写,则默认使用函数名做为任务名
    def send_sms2():
        print("发送短信任务2!!!")

3.1 创建任务案例

    from mycelery.main import app
    from lyapi.libs.tencent.sms import send_sms_single
     
     
    # 任务列表,在main里面会被自动发现,但不会执行,只有send_sms.delay(code, phone) 才执行
    @app.task(name='send_sms')  # 给任务起的别名叫send_sms
    def send_sms(phone_num, template_id, template_param_list):
        ret = send_sms_single(phone_num, template_id, template_param_list)
        # ret 还是成功信息/错误信息
        return ret

4. 控制台启动

    # 启动Celery的命令
    # 切换目录到mycelery根目录下启动
    # celery -A mycelery.main worker --loglevel=info

5. 最后一步,views使用

    import datetime
    from auc_celery.sms.tasks import send_sms
     
     
    # celery -A auc_celery.main worker -l info -P eventlet
    def text(request):
        """生成id"""
        # 1.立即执行
        res = send_sms.delay(1, 2)
        """
        # 2.定时任务(只执行一次,不是周期性定时任务)
        ctime = datetime.datetime.now()
        # 本地时间转换成utc时间
        utc_ctime = datetime.datetime.utcfromtimestamp(ctime.timestamp())
        target_time = utc_ctime + datetime.timedelta(seconds=3)
        res = send_sms.apply_async(args=[6, 3], eta=target_time)
        """
        return HttpResponse(res.id)
     
     
    def text2(request):
        """根据id找任务结果及相关"""
        nid = request.GET.get('nid')
        # 导入包
        from celery.result import AsyncResult
        # 导入app
        from auc_celery.sms.tasks import app
        # 导入内置的函数,获取任务对象
        result_object = AsyncResult(id=nid, app=app)
        # 获取返回结果
        # data1 = result_object.get()  # 3
        # 获取状态码
        # status = result_object.status  # SUCCESS
        # # 取消任务
        # result_object.revoke()
        # print('success', result_object.successful())
        # print(data1)
        if result_object.successful():
            # print('success', result_object.successful())
            # 获取结果
            data = result_object.get()
            print("data", data)
            # 清空
            # result_object.forget()
        # 失败的逻辑处理
        elif result_object.failed():
            print('失败')
        else:
            print('未知')
        return HttpResponse('...')
     
     
    # 周期定时器
    # celery -A auc_celery.main beat
    # celery启动 -A--->文件名 worker--->启动工作者  -l info--->日志等级  -P eventlet--->解决在windows上报错的问题
    # celery  -A auc_celery.main worker -l info -P eventlet
    """
    小贴士: 如果需要celery保存上次任务运行的时间在数据文件中,通过 -s 指定一个名为celerybeat-schedule的文件即可:
    celery -A proj beat -s /home/celery/var/run/celerybeat-schedule
    """

四. 如果报以下错误

Celery ValueError: not enough values to unpack (expected 3, got 0)
报错原因:

win10上运行celery4.x以上的版本就会出现这个问题
解决办法:
1.安装一个eventlet模块

pip3 install eventlet -i https://pypi.douban.com/simple/

2. 然后启动celery的时候加一个参数

celery -A <mymodule> worker -l info -P eventlet

启动案例:

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

输入到文件

celery -A cat_celery.main worker -l info -P eventlet --logfile=D:/pythonProject/cat/logs/celery.log

ps:<mymodule>指代的是,你的app所在的文件
详细描述错误原因

错误详细介绍https://blog.youkuaiyun.com/qq_52385631/article/details/122786364?spm=1001.2014.3001.5501
五. 定时器使用(配置同上,)

定时器官方文档http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html
a. 在clery文件夹里面创建一个包,order

 b. 在config.py配置中生成定时器

    # 配置文件
    # 1. 有redis有密码
    # 任务队列的链接地址(变量名必须叫这个)
    broker_url = 'redis://:foobared@127.0.0.1:6379/14'
    # 结果队列的链接地址(变量名必须叫这个)
    result_backend = 'redis://:foobared@127.0.0.1:6379/15'
     
    # 2. redis无密码写法
    # 任务队列的链接地址(变量名必须叫这个)
    # broker_url = 'redis://127.0.0.1:6379/14'
    # 结果队列的链接地址(变量名必须叫这个)
    # result_backend = 'redis://127.0.0.1:6379/15'
     
     
     
    # 定时器的配置文件
    # 导入定时器
    from celery.schedules import crontab
    from .main import app
     
    # 定时任务的调度列表,用于注册定时任务
    app.conf.beat_schedule = {
        # Executes every Monday morning at 7:30 a.m.
        # check_order_outtime -- 随意起的名字 定时任务的名字,叫什么都都行
        'check_order_outtime': {
            # 本次调度的任务
            'task': 'check_order',  # 这里的任务名称必须先到main.py中注册,如果写了别名,直接写别名就可以了
            # 定时任务的调度周期
            # 'schedule': crontab(minute=0, hour=0),   # 每周凌晨00:00
            'schedule': crontab(),  # 每分钟,没写秒数,那么就是每分钟的0秒开始
            # 'args': (16, 16),  # 注意:任务就是一个函数,所以如果有参数则需要传递
        },
    }

c. 定时器执行的任务 tasks.py(文件一定要叫这个名字)

    from mycelery.main import app
    from order.models import Order
    import datetime
     
     
    @app.task(name="check_order")
    def check_order():
        # 进行订单时间的校验
        #  支付时间 < 当前时间 + 时间段 ,把订单状态进行修改
        # 我们配置的1分钟执行1次
        return 'ok'

 定时器执行的任务案例

    from mycelery.main import app
    from order.models import Order
    import datetime
     
     
    @app.task(name="check_order")
    def check_order():
        # 进行订单时间的校验
        #  支付时间 < 当前时间 + 时间段 ,把订单状态进行修改
        timer = datetime.datetime.now() + datetime.timedelta(days=1)
        Order.objects.filter(pay_time__lte=timer, order_status=0).update(order_status=3)
        return 'ok'

d. main.py 配置自动发现

app.autodiscover_tasks(["mycelery.sms", 'mycelery.order'])

完整版 main.py

    # 主程序
    import os
    from celery import Celery
     
    # 创建celery实例对象
    app = Celery("xxxx")  # celery对象可以创建多个,所以我们最好给我们当前的celery应用起个名字,比如叫做xxx
     
    # 把celery和django进行组合,需要识别和加载django的配置文件
    import os
     
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lyapi.settings.dev')
    # 如果只是使用了logging日志功能的话可以不写以下两句,因为logging是python提供的模块,但是将来可能使用celery来执行其他的django任务,所以我们先写上
    import django
     
    django.setup()
     
    # 通过app对象加载配置
    app.config_from_object("mycelery.config")
     
    # 加载任务
    # 参数必须必须是一个列表,里面的每一个任务都是任务的路径名称
    # app.autodiscover_tasks(["任务1","任务2"])
    app.autodiscover_tasks(["mycelery.sms", 'mycelery.order'])
     

六. 启动流程

    # 启动Celery的命令
    # 切换目录到mycelery根目录下启动
    # 会报错
    # celery -A mycelery.main worker --loglevel=info
     
    # 报错的类型
    # Celery ValueError: not enough values to unpack (expected 3, got 0)
     
    # 解决办法
    # pip install eventlet
    # celery -A <mymodule> worker -l info -P eventlet
     
    # 定时器启动
    # celery -A mycelery.main beat
    # 启动celery
    # celery -A mycelery.main worker -l info -P eventlet
     

七:多个定时器的配置
7.1 task

    import datetime
    import uuid
    from auction_celery.main import app
     
    from api import models
     
     
    @app.task(name="check_auction")
    def check_auction():
        """拍卖场未开始--->预售"""
        # 当前时间,拍卖场开始时间,拍卖场结束时间
        now = datetime.datetime.now()
        # 拍卖场开始时间<当前时间---status修改成拍卖中
        # 1. 查询到状态为未开拍且开拍时间<当前时间 的拍卖场
        query_auction = models.Auction.objects.filter(status=1, preview_start_time__lte=now,
                                                      preview_end_time__gte=now)
        # 专场的商品状态修改
        for item in query_auction:
            item.status = 2
            item.save()
        # 专场的status修改
        query_auction.update(status=2)
        return 'ok0'
     
     
    @app.task(name="check_auction_pay")
    def check_auction_pay():
        """拍卖场预售--->开始"""
        # 当前时间,拍卖场开始时间,拍卖场结束时间
        now = datetime.datetime.now()
        # 拍卖场开始时间<当前时间---status修改成拍卖中
        # 1. 查询到状态为未开拍且开拍时间<当前时间 的拍卖场
        query_auction = models.Auction.objects.filter(status=2, auction_start_time__lte=now,
                                                      auction_end_time__gte=now)
        # 专场的商品状态修改
        for item in query_auction:
            item.status = 3
            item.save()
            # 专场的status修改
        query_auction.update(status=3)
        return 'ok00'
     
     
    @app.task(name="check_auction_end")
    def check_auction_end():
        """拍卖场开始--->结束"""
        # 当前时间,拍卖场开始时间,拍卖场结束时间
        now = datetime.datetime.now()
        # 拍卖场开始时间<当前时间---status修改成拍卖中
        # 1. 查询到状态为未开拍且开拍时间<当前时间 的拍卖场
        query_auction = models.Auction.objects.filter(status=3, auction_end_time__lte=now, is_operation=False)
        # 专场的商品状态修改
        total_price = 0
        for item in query_auction:
            # item--- 拍卖场
            # 查询到所有结束的商品
            auction_item_query = item.auction_item.all().filter(status=3)
            for auction_item in auction_item_query:
                # auction_item 拍品
                # 根据价格判断是成交/流拍
                if auction_item.bid_record.all():
                    max_price = auction_item.bid_record.all().filter(status=1).order_by('-id').first().price
                    total_price += max_price
                    # 当前商品的价格添加
                    auction_item.deal_price = max_price
                    auction_item.save()
                    # 为出价最高者,创建订单
                    userinfo = auction_item.bid_record.all().order_by('-id').first().user
                    models.Order.objects.create(
                        uid=str(uuid.uuid4()),
                        userinfo=userinfo,
                        auction=item,
                        auction_item=auction_item,
                        price=max_price,
                    )
                    # 把当前商品状态修改成成交
                    item.auction_item.filter(status=3).update(status=4)
                else:
                    item.auction_item.filter(status=3).update(status=5)
            # 当前专卖场的总价格,
            item.total_price = total_price
        # 把当前专场的状态修改成结束
        query_auction.update(status=4, is_operation=True)
        return 'ok'
     
     
    @app.task(name="check_auction_item_order")
    def check_auction_item_order():
        """生成的订单支付--状态修改"""
        # 当前时间,拍卖场开始时间,拍卖场结束时间
        now = datetime.datetime.now()
        # 当前时间 时间间隔 创建时间 create_time
        # 创建时间  < 当前时间 - 时间间隔
        # 时间间隔 + 创建时间 < 当前时间
        timer = now - datetime.timedelta(days=1)
        query_order = models.Order.objects.filter(status=1, create_time__lte=timer).update(status=3)
        return 'ok'
     
    #  支付时间 < 当前时间 + 时间段

7.2 config配置

    # 配置文件
    # 1. 有redis有密码
    # 任务队列的链接地址(变量名必须叫这个)
    broker_url = 'redis://:foobared@106.14.42.253:8889/14'
    # 结果队列的链接地址(变量名必须叫这个)
    result_backend = 'redis://:foobared@106.14.42.253:8889/15'
     
    # 定时器的配置文件
    # 导入定时器
    from celery.schedules import crontab
    from .main import app
     
    # 定时任务的调度列表,用于注册定时任务
    app.conf.beat_schedule = {
        # Executes every Monday morning at 7:30 a.m.
        # check_order_outtime -- 随意起的名字 定时任务的名字,叫什么都都行
        'check_auction_out_time': {
            # 本次调度的任务
            'task': 'check_auction',  # 这里的任务名称必须先到main.py中注册,如果写了别名,直接写别名就可以了
            # 定时任务的调度周期
            # 'schedule': crontab(minute=0, hour=0),   # 每周凌晨00:00
            'schedule': crontab(),  # 每分钟,没写秒数,那么就是每分钟的0秒开始
            # 'args': (16, 16),  # 注意:任务就是一个函数,所以如果有参数则需要传递
        },
        'check_auction_pay': {
            # 本次调度的任务
            'task': 'check_auction_pay',  # 这里的任务名称必须先到main.py中注册,如果写了别名,直接写别名就可以了
            # 定时任务的调度周期
            # 'schedule': crontab(minute=0, hour=0),   # 每周凌晨00:00
            'schedule': crontab(),  # 每分钟,没写秒数,那么就是每分钟的0秒开始
            # 'args': (16, 16),  # 注意:任务就是一个函数,所以如果有参数则需要传递
        },
        'check_auction_end': {
            # 本次调度的任务
            'task': 'check_auction_end',  # 这里的任务名称必须先到main.py中注册,如果写了别名,直接写别名就可以了
            # 定时任务的调度周期
            # 'schedule': crontab(minute=0, hour=0),   # 每周凌晨00:00
            'schedule': crontab(),  # 每分钟,没写秒数,那么就是每分钟的0秒开始
            # 'args': (16, 16),  # 注意:任务就是一个函数,所以如果有参数则需要传递
        },
        'check_auction_item_order': {
            # 本次调度的任务
            'task': 'check_auction_item_order',  # 这里的任务名称必须先到main.py中注册,如果写了别名,直接写别名就可以了
            # 定时任务的调度周期
            # 'schedule': crontab(minute=0, hour=0),   # 每周凌晨00:00
            'schedule': crontab(),  # 每分钟,没写秒数,那么就是每分钟的0秒开始
            # 'args': (16, 16),  # 注意:任务就是一个函数,所以如果有参数则需要传递
        },
    }
    # # 配置时间,如果django,settings里面的时间,没有改,下面两行可以不写,因为django默认utc时间
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
     

8. 时间celery配置
8.1 配置1(推荐)

    # 配置时间,如果django,settings里面的时间,没有改,下面两行可以不写,因为django默认utc时间
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False

8.2 配置2(可能报错)

    CELERY_TIMEZONE = "Asia/Shanghai"
    CELERY_ENABLE_UTC = False

9. 定时器的时间crontab()
1.介绍

    其中,crontab()实例化的时候没设置任何参数,都是使用默认值。crontab一共有7个参数,常用有5个参数分别为:

    minute:分钟,范围0-59;

    hour:小时,范围0-23;

    day_of_week:星期几,范围0-6。以星期天为开始,即0为星期天。这个星期几还可以使用英文缩写表示,例如“sun”表示星期天;

    day_of_month:每月第几号,范围1-31;

    month_of_year:月份,范围1-12。
     

a、默认参数

    这些参数可以设置表达式,表达稍微复杂的设置。默认值都是"*"星号,代表任意时刻。即crontab()相当与:

    含义是每天、每小时、每分钟执行一次任务。这说法太反人类语言习惯,简单说就是每1分钟执行一次任务。

crontab(minute='*', hour='*', day_of_week='*', day_of_month='*', month_of_year='*')

b、具体某个值

    上面提到这些参数的取值范围。我们可以直接设置某个值。例如:

    即每小时的15分时刻执行一次任务。直接指定某个时刻。

crontab(minute=15)

     以此类推可以设置每天0点0分时刻执行任务的设置如下:

crontab(minute=0, hour=0)

     当然,也可以设置多个值。例如0分和30分执行一次任务:

    这里使用字符串,用逗号隔开数值。这里的逗号是表示多个表达式or逻辑关系。

crontab(minute='0,30')

 c、设置范围

    设置范围也是设置多个值,例如指定9点到12点每个小时的每分钟执行任务。

 crontab(minute='*', hour='9-12')

    这里*号是默认值,可以省略如下:

 crontab(hour='9-12')

    上面提到逗号是or逻辑关系。拓展一下,指定9点到12点和20点中每分钟执行任务:

 crontab(hour='9-12,20')

    crontab的表达式越来越复杂了。celery还提供了一个类得到表达式解析结果,代码如下:

     from celery.task.schedules import crontab_parser
     r = crontab_parser(23, 0).parse('9-12,20')
     print(r)

     其中,crontab_parse是一个解析类。第1个参数是范围的最大值;第2个参数是范围的最小值。通过parse输入表达式,可得到表达式的解析结果:

 set([9, 10, 11, 12, 20])

 d、设置间隔步长

    假如我要设置1、3、5、7、9、11月份每天每分钟执行任务,按照上面的做法可以设置如下:

crontab(day_of_month='1,3,5,7,9,11')

    观察数据可以发现,都是间隔2的步长。需要设置的数字比较少,若数字比较多显得很麻烦。例如我想每间隔2分钟就执行一次任务,要写30个数字想想就觉得很麻烦。crontab表达式还提供了间隔的处理,例如:

     crontab(minute='*/2')
     crontab(minute='0-59/2') #效果等同上面

    这个/号不是除以的意思。相当与range的第3个参数,例如:

 range(0, 59+1, 2)

差不多crontab表达式就这些,多举几个例子:

    #每2个小时中每分钟执行1次任务
    crontab(hour='*/2')
     
    #每3个小时的0分时刻执行1次任务
    #即[0,3,6,9,12,15,18,21]点0分
    crontab(minute=0, hour='*/3')
     
    #每3个小时或8点到12点的0分时刻执行1次任务
    #即[0,3,6,9,12,15,18,21]+[8,9,10,11,12]点0分
    crontab(minute=0, hour='*/3,8-12')
     
    #每个季度的第1个月中,每天每分钟执行1次任务
    #月份范围是1-12,每3个月为[1,4,7,10]
    crontab(month_of_year='*/3')
     
    #每月偶数天数的0点0分时刻执行1次任务
    crontab(minute=0, hour=0, day_of_month='2-31/2')
     
    #每年5月11号的0点0分时刻执行1次任务
    crontab(0, 0, day_of_month='11', month_of_year='5')


————————————————
版权声明:本文为优快云博主「台风吹」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_52385631/article/details/122783121

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值