芹菜限速:是否可以根据运行时参数对芹菜任务进行不同的限速?

时间:2019-05-07 17:10:40

标签: django redis queue celery message-queue

我想基于运行时确定的某些参数对Celery任务进行速率限制。例如:如果参数为1,则速率限制可能为100。如果参数为2,则速率限制可能为25。此外,我希望能够在运行时修改这些速率限制。 >

芹菜提供了一种方法吗?我可以使用routing_key根据参数将任务发送到不同的队列,但是celery似乎不支持队列级别的速率限制。

一种可能的解决方案是在排队任务时使用eta,但我想知道是否有更好的方法来实现这一目标。

2 个答案:

答案 0 :(得分:0)

Celery提供了一个内置的限速系统,但是它不能像大多数人期望的限速系统那样工作,并且有一些限制。我像您提到的那样在ETA和Redis上的一些Lua脚本的基础上实现了分布式速率限制系统,它运行良好,因此我建议使用该方法。

本文详细介绍了一种类似于该方法的方法:

https://callhub.io/distributed-rate-limiting-with-redis-and-celery/

我使用了一种更简单的版本,我的lua脚本就是这样:

local current_time = tonumber(ARGV[1])
local eta = tonumber(redis.call('get', KEYS[1]))
local interval = tonumber(ARGV[2])

if not eta or eta < current_time then
    redis.call('set', KEYS[1], current_time + interval, 'EX', 10800)
    return nil
else
    redis.call('set', KEYS[1], eta + interval, 'EX', 10800)
    return tostring(eta)
end

我必须简单地重写任务apply_async方法,并以所需的延迟调用该lua脚本:

def apply_async(self, *args, **kwargs):
    now = int(time.time())

    # From django-redis
    conn = get_redis_connection('default')

    cache_key = 'something'

    eta = conn.eval(self.rate_limit_script, 1, cache_key, now, rate_limiter.get_delay())

    if eta:
        eta = datetime.fromtimestamp(float(eta), tz=timezone.get_current_timezone())
        kwargs['eta'] = eta
    return super().apply_async(*args, **kwargs)

答案 1 :(得分:0)

您可以在运行时通过 celery_app.control.rate_limit() 更新应用程序部分中的 rate_limit,该部分有权访问 Celery 应用程序实例。

./task.py

from celery import Celery

app = Celery("sample")

app.conf.update(
    broker_url='amqp://guest:guest@localhost:5672',
    task_annotations={
        'task.func1': {
            'rate_limit': '10/s'  # Default is 10 per second
        }
    },
)


@app.task
def func1(ctr):
    print(f"I have now processed task {ctr}")

./runner.py

import task

print(f"Current rate_limit is 10/s")

for ctr in range(7):
    print(f"Enqueue task {ctr}")
    task.func1.delay(ctr)

    if ctr == 3:
        choice = input("Let's update the rate limit setting [1/2]: ")
        if choice == "1":
            new_rate_limit = '1/m'
            print(f"Changing rate_limit to {new_rate_limit}")
            task.app.control.rate_limit('task.func1', new_rate_limit)
        elif choice == "2":
            new_rate_limit = '1/h'
            print(f"Changing rate_limit to {new_rate_limit}")
            task.app.control.rate_limit('task.func1', new_rate_limit)
        else:
            print("Retaining default rate_limit")
  • 为了简单起见,这里有一个原始的 Python 可运行脚本,它充当 celery 任务的调用者。在现实生活中的应用程序中,这可能是与 celery 集成的 Django 视图或其他任何东西。

执行任务监听器(消费者):

$ celery --app=task worker --loglevel=INFO

执行任务调用者(生产者):

$ python3 runner.py 
Current rate_limit is 10/s
Enqueue task 0
Enqueue task 1
Enqueue task 2
Enqueue task 3
Let's update the rate limit setting [1/2]: 1
Changing rate_limit to 1/m
Enqueue task 4
Enqueue task 5
Enqueue task 6
  • 在这里,我们可以看到前 4 次运行的速率为每秒 10 次。然后使用运行时输入,我们将剩余 3 次运行更新为每分钟 1 次。

任务监听器(消费者)的日志:

[2021-04-30 10:35:44,006: INFO/MainProcess] Received task: task.func1[60600074-16ad-41b1-afbf-7a89da5af2f0]  
[2021-04-30 10:35:44,007: INFO/MainProcess] Received task: task.func1[e93f9936-4d56-49a7-bb8b-757817235aa2]  
[2021-04-30 10:35:44,007: WARNING/ForkPoolWorker-2] I have now processed task 0
[2021-04-30 10:35:44,008: INFO/ForkPoolWorker-2] Task task.func1[60600074-16ad-41b1-afbf-7a89da5af2f0] succeeded in 0.000337354000293999s: None
[2021-04-30 10:35:44,010: INFO/MainProcess] Received task: task.func1[c0c369c4-dbcf-43db-b79c-49d5866b136f]  
[2021-04-30 10:35:44,010: INFO/MainProcess] Received task: task.func1[38b32102-7313-4e64-be77-f9565ce04683]  
[2021-04-30 10:35:44,217: WARNING/ForkPoolWorker-3] I have now processed task 2
[2021-04-30 10:35:44,218: INFO/ForkPoolWorker-3] Task task.func1[c0c369c4-dbcf-43db-b79c-49d5866b136f] succeeded in 0.0006413599985535257s: None
[2021-04-30 10:35:44,217: WARNING/ForkPoolWorker-2] I have now processed task 1
[2021-04-30 10:35:44,219: INFO/ForkPoolWorker-2] Task task.func1[e93f9936-4d56-49a7-bb8b-757817235aa2] succeeded in 0.0021943179999652784s: None
[2021-04-30 10:35:44,726: WARNING/ForkPoolWorker-2] I have now processed task 3
[2021-04-30 10:35:44,727: INFO/ForkPoolWorker-2] Task task.func1[38b32102-7313-4e64-be77-f9565ce04683] succeeded in 0.00125738899987482s: None
[2021-04-30 10:35:44,809: INFO/MainProcess] New rate limit for tasks of type task.func1: 1/m.
[2021-04-30 10:35:44,810: INFO/MainProcess] Received task: task.func1[1acb9b7e-755e-4773-a3db-0a284c7024bb]  
[2021-04-30 10:35:44,811: INFO/MainProcess] Received task: task.func1[b861a33a-0856-4044-a498-250c0da48d53]  
[2021-04-30 10:35:44,811: WARNING/ForkPoolWorker-2] I have now processed task 4
[2021-04-30 10:35:44,812: INFO/ForkPoolWorker-2] Task task.func1[1acb9b7e-755e-4773-a3db-0a284c7024bb] succeeded in 0.0006612189990846673s: None
[2021-04-30 10:35:44,812: INFO/MainProcess] Received task: task.func1[e2e79f75-7628-4449-b880-e3a03020da7e]  
[2021-04-30 10:36:44,892: WARNING/ForkPoolWorker-2] I have now processed task 5
[2021-04-30 10:36:44,892: INFO/ForkPoolWorker-2] Task task.func1[b861a33a-0856-4044-a498-250c0da48d53] succeeded in 0.00017851099983090535s: None
[2021-04-30 10:37:44,830: WARNING/ForkPoolWorker-2] I have now processed task 6
[2021-04-30 10:37:44,831: INFO/ForkPoolWorker-2] Task task.func1[e2e79f75-7628-4449-b880-e3a03020da7e] succeeded in 0.0007846450007491512s: None
  • 在这里,您可以看到前 4 个任务(以每秒 10 个的速率)在 10:35:44 全部处理,而其他 3 个任务(以每分钟 1 个的更新速率)在 10 :35:44、10:36:44 和 10:37:44。

参考:https://docs.celeryproject.org/en/latest/userguide/workers.html#changing-rate-limits-at-run-time