• celery


    • celery

    http://www.pianshen.com/article/2176289575/
    https://www.jianshu.com/p/9be4d8d30d8e

    异步任务的调用方法:
    1.result = add.delay(1, 2):这是apply_async方法的别名,但接受的参数较为简单;
    2.result = add.apply_async(args=[1, 2], kwargs={'countdown':5, 'expires':60})
    3.result = celeryapp.send_task('task.add', args=[1, 2]):可以发送未被注册的异步任务,即没有被celery.task装饰的任务;

    apply_async 可以传递更多的参数,
    eg:
    countdown=5:等待5S后再执行, eta=now+tiedelta(second=20):指定任务的开始时间, expires=60:指定任务的超时时间,retry=True: 指定任务失败后是否重试

    result.ready() 查看任务状态,返回布尔值, 任务执行完成, 返回 True, 否则返回 False.
    result.get(timeout=5) 获取任务执行结果,可以设置等待时间

    result.state # 查看任务当前的状态,

    PENDING:任务等待中,
    STARTED:任务已开始,
    SUCCESS:任务执行成功,
    FAILURE:任务执行失败,
    RETRY: 任务将被重新执行,
    REVOKED:任务取消,
    PROCESS:任务执行中
    result.traceback # 如果任务抛出异常, 可以获取完整的堆栈信息

    任务的执行结果
    执行结果保存到redis1号库, 在1号库会有类似下面格式的键, 其对应的值就是执行结果了.
    键: celery-task-meta-080ee8b0-24c4-48a0-a2dd-a1bcebb45b5b
    值: {
    "status": "SUCCESS",
    "result": 30,
    "traceback": null,
    "children": [],
    "task_id": 080ee8b0-24c4-48a0-a2dd-a1bcebb45b5b
    }

    定时任务

    from celery.schedule import crontab

    crontab对象指定精确时间, 默认参数值都为'*' 意为每分钟都执行
    这里指的是早上8点30


    今天踩得坑是 在windows中,celery的定时任务不好使, 在linux环境下就没问题了, 不知道啥原因, 系统问题吧. 以后还是多用linux吧.

    调用方法

    1. delay

    task.delay(args1, args2, kwargs=value_1, kwargs2=value_2)

    2. apply_async

    delay 实际上是 apply_async 的别名, 还可以使用如下方法调用, 但是 apply_async 支持更多的参数:
    task.apply_async(args=[arg1, arg2], kwargs={key:value, key:value})
    countdown : 等待一段时间再执行.
    add.apply_async((2,3), countdown=5)
    eta : 定义任务的开始时间.
    add.apply_async((2,3), eta=now+tiedelta(second=10))
    expires : 设置超时时间.
    add.apply_async((2,3), expires=60)
    retry : 定时如果任务失败后, 是否重试.
    add.apply_async((2,3), retry=False)
    retry_policy : 重试策略.
    max_retries : 最大重试次数, 默认为 3 次.
    interval_start : 重试等待的时间间隔秒数, 默认为 0 , 表示直接重试不等待.
    interval_step : 每次重试让重试间隔增加的秒数, 可以是数字或浮点数, 默认为 0.2
    interval_max : 重试间隔最大的秒数, 即 通过 interval_step 增大到多少秒之后, 就不在增加了, 可以是数字或者浮点数, 默认为 0.2 .
    自定义发布者,交换机,路由键, 队列, 优先级,序列方案和压缩方法:

    task.apply_async((2,2), compression='zlib',
        serialize='json',
        queue='priority.high',
        routing_key='web.add',
        priority=0,
        exchange='web_exchange')
    

    shell中直接调用
    task.s()()

    任务绑定, 记录日志, 重试

    # 修改 tasks.py 文件.
    from celery.utils.log import get_task_logger
    logger = get_task_logger(__name__)
    @app.task(bind=True)
    def div(self, x, y):
        logger.info(('Executing task id {0.id}, args: {0.args!r}'
                     'kwargs: {0.kwargs!r}').format(self.request))
        try:
            result = x/y
        except ZeroDivisionError as e:
            raise self.retry(exc=e, countdown=5, max_retries=3)     # 发生 ZeroDivisionError 错误时, 每 5s 重试一次, 最多重试 3 次.
        return result
    

    当使用 bind=True 参数之后, 函数的参数发生变化, 多出了参数 self, 这这相当于把 div 编程了一个已绑定的方法, 通过 self 可以获得任务的上下文.

    信号系统

    信号可以帮助我们了解任务执行情况, 分析任务运行的瓶颈. Celery 支持 7 种信号类型.
    1 任务信号
    before_task_publish : 任务发布前
    after_task_publish : 任务发布后
    task_prerun : 任务执行前
    task_postrun : 任务执行后
    task_retry : 任务重试时
    task_success : 任务成功时
    task_failure : 任务失败时
    task_revoked : 任务被撤销或终止时
    2 应用信号
    3 Worker 信号
    4 Beat 信号
    5 Eventlet 信号
    6日志信号
    7 命令信号

    # 在执行任务 add 之后, 打印一些信息.
    @after_task_publish
    def task_send_handler(sender=None, body=None, **kwargs):
        print 'after_task_publish: task_id: {body[id]}; sender: {sender}'.format(body=body, sender=sender)
    

    官方文档
    http://docs.celeryproject.org/en/latest/userguide/signals.html#task-success

    子任务与工作流:

    可以把任务 通过签名的方法传给其他任务, 成为一个子任务.

    from celery import signature
    task = signature('task.add', args=(2,2), countdown=10)
    task
    task.add(2,2)   # 通过签名生成任务
    task.apply_async()
    

    还可以通过如下方式生成子任务 :

    from proj.task import add
    task = add.subtask((2,2), countdown=10)     # 快捷方式 add.s((2,2), countdown-10) 
    task.apply_async()
    

    自任务实现片函数的方式非常有用, 这种方式可以让任务在传递过程中财传入参数.

    partial = add.s(2)
    partial.apply_async((4,))
    

    子任务支持如下 5 种原语,实现工作流. 原语表示由若干指令组成的, 用于完成一定功能的过程.
    1 chain : 调用连, 前面的执行结果, 作为参数传给后面的任务, 直到全部完成, 类似管道.

    from celery import chain
    res = chain(add.s(2,2), add.s(4), add.s(8))()
    res.get()
    
    管道式:
    
    (add.s(2,2) | add.s(4) | add.s(8))().get()
    

    2 group : 一次创建多个(一组)任务.

    from celery import group
    
    res = group(add.s(i,i) for i in range(10))()
    res.get()
    

    3 chord : 等待任务全部完成时添加一个回调任务.

    res = chord((add.s(i,i) for i in range(10)), add.s(['a']))()
    res.get()    # 执行完前面的循环, 把结果拼成一个列表之后, 再对这个列表 添加 'a'.
    [0,2,4,6,8,10,12,14,16,18,u'a']
    

    4 map/starmap : 每个参数都作为任务的参数执行一遍, map 的参数只有一个, starmap 支持多个参数.

    add.starmap(zip(range(10), range(10)))
    相当于:
    @app.task
    def temp():
        return [add(i,i) for i in range(10)]
    

    5 chunks : 将任务分块.

    res = add.chunks(zip(range(50), range(50)),10)()
    res.get()
    

    在生成任务的时候, 应该充分利用 group/chain/chunks 这些原语.

    关闭不想要的功能 :

    @app.task(ignore_result=True)   # 关闭任务执行结果.
    def func():
        pass
    CELERY_DISABLE_RATE_LIMITS=True     # 关闭限速.
    

    根据任务状态执行不同操作 :

    # tasks.py
    Task类有许多方法
    ![](https://img2018.cnblogs.com/blog/1361758/202002/1361758-20200224004152799-263075383.png)
    
    定义一个类,继承Task   对于任务执行的状态,有on_success,on_failure,afer_return, on_retry这几种,常用的就是成功后做的操作,失败后做的操作,相当于一个回调函数
    
    class MyTask(Task):
        def on_success(self, retval, task_id, args, kwargs):
            #成功后的回调函数,入参为 异步任务的执行结果,任务id,任务函数的入参args,kwargs
            print 'task done: {0}'.format(retval)
            return super(MyTask, self).on_success(retval, task_id, args, kwargs)
    
        def on_failure(self, exc, task_id, args, kwargs, einfo):
            # 失败后的回调函数,一次为 错误的标题(NotFund这种的), 任务id , 任务函数的入参,错误的详细信息
            print 'task fail, reason: {0}'.format(exc)
            return super(MyTask, self).on_failure(exc, task_id, args, kwargs, einfo)
    可在回调函数中继续定义异步任务之后的操作,
    还有一个after_return(status, retval, task_id, args, kwargs, einfo),是在在异步任务结果返回后,可以返回任务的执行状态,执行的结果/或者错误标题。任务id,参数,如果错误的详细信息
    
    注意,当同时定义了after_return,on_success后,任务执行完只执行到after_return,on_success略过?不知道为啥,为了保险,还是 用on_success,on_failure这俩个。
    
    # 正确函数, 执行 MyTask.on_success() :
    @app.task(base=MyTask)  # 调用的时候,在装饰器中定义base指定回调函数的类,在shell中调用即可触发
    def add(x, y):
        return x + y
    
    # 错误函数, 执行 MyTask.on_failure() : 
    @app.task  #普通函数装饰为 celery task
    def add(x, y):
        raise KeyError
        return x + y
    

    任务状态

    启动方式

    普通
    $ celery -A proj worker -l info

    使用 daemon 方式 multi :

    $ celery multi start web -A proj -l info --pidfile=/path/to/celery_%n.pid --logfile=/path/to/celery_%n.log 
    # web 是对项目启动的标识, 
    # %n 是对节点的格式化用法.
        %n : 只包含主机名
        %h : 包含域名的主机
        %d : 只包含域名
        %i : Prefork 类型的进程索引,如果是主进程, 则为 0.
        %I : 带分隔符的 Prefork 类型的进程索引. 假设主进程为 worker1, 那么进程池的第一个进程则为 worker1-1
    

    常用 multi 相关命令:

    $ celery multi show web     # 查看 web 启动时的命令
    $ celery multi names web    # 获取 web 的节点名字
    $ celery multi stop web     # 停止 web 进程
    $ celery multi restart web  # 重启 web
    $ celery multi kill web     # 杀掉 web 进程
    

    celery的任务回调

    在每个celery任务执行完成之后,根据任务的状态 success failure等状态,有相应的回调函数,on_success on_failre等方法,在公共类里面已经有了这些方法,只需要根据需要定义的方法,即可,
    在celery下的任务执行完成之后,成功失败都可以使用入参,输出/错误信息,
    不过需要注意的一点是在on_success的时候, celery任务的状态并不是底下任务执行状态,而是一整套流程,

    信号

    定义了celery的信号,可以自己定义信号执行方法,也可以使用公有信号,例如监测某个表的对象新增,在新增的时候即可触发信号对应的方法,

    问题:

    2020-4-30 15:12:15,今天碰到的问题是: celery依赖的任务容错做足,状态 错误信息 都在返回结果中保存,保证函数执行绝对成功,在回调的时候都调用on_success方法,在执行on_success的时候保存任务id和相关的入参和结果中的信息,但是在入库的时候出现了记录要保存两次的问题,经过检查发现这个表对象的新增是有一个信号量在监测的,只要这个表对象增加,那么另外一个表就会新增同样的信息,但是在处理那张表数据的时候使用的是get方法,查询对象的时候返回了多条,所以报了错。因为这个表对象的新增还是在on_success流程中,所以on_success既定的流程走完之后,另一张表新增出错,随即出发了on_failure的函数,又去新增一条错误记录,所以每次执行的时候在同一张表中就出现了两条数据

  • 相关阅读:
    NIO[读]、[写]在同一线程(单线程)中执行,让CPU使用率最大化,提高处理效率
    解码(ByteBuffer): CharsetDecoder.decode() 与 Charset.decode() 的不同
    ACCESS与SQL Server下SQL Like 查询的不同
    试着用c写了一个多线程的同步
    收藏:c语言的多线程同步
    java.sql.SQLException: [Microsoft][ODBC Microsoft Access Driver] 不能使用 '(未知的)';文件已在使用中
    Linux进程管理
    MySQL数据备份
    MySQL索引
    Socket.IO 概述
  • 原文地址:https://www.cnblogs.com/cizao/p/11484359.html
Copyright © 2020-2023  润新知