在Celery中设置组中任务之间的延迟

时间:2015-05-29 15:15:10

标签: python celery

我有一个python应用程序,用户可以在其中启动某项任务。

任务的全部目的也是以给定URL的特定间隔执行给定数量的POST / GET请求。

因此,用户提供N个请求数,V - 每秒请求数。

考虑到由于I / O延迟导致实际r / s速度可能更大或更小,设计此类任务的效果如何更好。

首先,我决定将Celery与Eventlet一起使用,否则我将需要十几件不可接受的作品。

我天真的做法:

  • 客户端使用task.delay()
  • 启动任务
  • 内部任务我做了类似的事情:

    @task
    def task(number_of_requests, time_period):
       for _ in range(number_of_requests):
           start = time.time()
           params_for_concrete_subtask = ...
           # .... do some IO with monkey_patched eventlet requests library
           elapsed = (time.time() - start)
           # If we completed this subtask to fast
           if elapsed < time_period / number_of_requests:
               eventlet.sleep(time_period / number_of_requests)
    

一个工作示例是here

如果我们太快,我们会尝试等待以保持所需的速度。如果我们太慢,那么客户的预期就可以了。我们不违反要求/第二要求。但如果我重新启动Celery,这会恢复正常吗?

我认为这应该有用,但我认为有更好的方法。 在Celery中,我可以定义一个具有特定速率限制的任务,这几乎符合我的需求保证。所以我可以使用Celery group功能并写:

@task(rate_limit=...)
def task(...):
    #

task_executor = task.s(number_of_requests, time_period)
group(task_executor(params_for_concrete_task) for params_for_concrete_task in ...).delay()

但是在这里我硬编码了动态的rate_limit,我没有看到改变它的方法。我看到了一个例子:

  task.s(....).set(... params ...)

但是我试图将rate_limit传递给它不起作用的set方法。

另一种可能的想法是使用Celery的定期任务调度程序。使用默认的实施周期和定期执行的任务是固定的。

我需要能够动态创建任务,这些任务会以特定的速率限制定期运行一定次数。也许我需要运行自己的调度程序,它将从数据库中获取任务?但我没有看到任何关于此的文档。

另一种方法是尝试使用chain函数,但我无法弄清楚任务参数之间是否存在延迟。

1 个答案:

答案 0 :(得分:4)

如果要动态调整rate_limit,可以使用以下代码执行此操作。它还在运行时创建chain()。 运行此命令,您将看到我们成功地将rate_limit覆盖为5 /秒至0.5 /秒。

  

test_tasks.py

from celery import Celery, signature, chain
import datetime as dt

app = Celery('test_tasks')
app.config_from_object('celery_config')

@app.task(bind=True, rate_limit=5)
def test_1(self):
    print dt.datetime.now()


app.control.broadcast('rate_limit',
                       arguments={'task_name': 'test_tasks.test_1',
                                  'rate_limit': 0.5})

test_task = signature('test_tasks.test_1').set(immutable=True)

l = [test_task] * 100

chain = chain(*l)
res = chain()

我也尝试从类中覆盖属性,但IMO在工作者注册任务时设置了rate_limit,这就是.set()没有效果的原因。我在这里猜测,必须检查源代码。

解决方案2

使用上一次调用的结束时间实现自己的等待机制,在链中将函数的返回传递给下一个。

所以它看起来像这样:

from celery import Celery, signature, chain
import datetime as dt
import time

app = Celery('test_tasks')
app.config_from_object('celery_config')

@app.task(bind=True)
def test_1(self, prev_endtime=dt.datetime.now(), wait_seconds=5):
    wait = dt.timedelta(seconds=wait_seconds)
    print dt.datetime.now() - prev_endtime
    wait = wait - (dt.datetime.now() - prev_endtime)
    wait = wait.seconds
    print wait
    time.sleep(max(0, wait))
    now = dt.datetime.now()
    print now
    return now

#app.control.rate_limit('test_tasks.test_1', '0.5')
test_task = signature('test_tasks.test_1')

l = [test_task] * 100

chain = chain(*l)
res = chain()

我认为这实际上比广播更可靠。