有限数量的用户启动的后台进程

时间:2016-09-09 17:12:55

标签: python django asynchronous celery background-process

我需要允许用户提交非常大的作业请求。我们正在谈论100千兆字节的内存和20小时的计算时间。这给我们公司带来了很多钱,因此规定任何时候只能运行2个作业,并且当2个已经运行时对新作业的请求将被拒绝(并且用户通知服务器正忙)。 / p>

我当前的解决方案使用来自concurrent.futures的Executor,并且需要将Apache服务器设置为仅运行一个进程,从而降低响应速度(当前用户数非常低,所以现在可以。)

如果可能的话,我想使用Celery,但我没有在文档中看到任何方法来完成这个特定的设置。

如何在Django应用程序中在后台运行有限数量的作业,并在因服务器忙而拒绝作业时通知用户?

3 个答案:

答案 0 :(得分:9)

对于这个特殊情况我有两个解决方案,一个是芹菜的开箱即用解决方案,另一个是你自己实现的解决方案。

  1. 芹菜工人可以这样做。特别是,只创建两个并发= 1 的工作进程(或者,一个并发= 2,但这将是线程,而不是不同的进程),这样,只能完成两个作业异步。现在,如果两个作业都被占用,您需要一种方法来引发异常,然后使用inspect来计算活动任务的数量,并在需要时抛出异常。要实施,您可以结帐this SO post
  2. 您可能也对rate limits感兴趣。

    1. 您可以使用选择的锁定解决方案自己完成所有操作。特别是,确保只有两个进程使用redis(和redis-py)运行的一个很好的实现就像下面这样简单。 (考虑到你知道redis,因为你知道芹菜)

      from redis import StrictRedis
      
      redis = StrictRedis('localhost', '6379')
      locks = ['compute:lock1', 'compute:lock2']
      for key in locks:
          lock = redis.lock(key, blocking_timeout=5)
          acquired = lock.acquire()
          if acquired:
              do_huge_computation()
              lock.release()
              break
          print("Gonna try next possible slot")
      
      if not acquired:
          raise SystemLimitsReached("Already at max capacity !")
      
    2. 这样,您可以确保系统中只能存在两个正在运行的进程。第三个进程将在lock.acquire()行中阻止 blocking_timeout 秒,如果锁定成功,acquired将为True,否则为False,您将告诉用户等等!

      我过去的某个时候有同样的要求,我最终编码的是上面的解决方案。特别是

      1. 这种竞争条件最少
      2. 易于阅读
      3. 不依赖于系统管理员,突然加倍负载下的工作人员的并发性并炸毁整个系统。
      4. 您还可以 实施每个用户的限制 ,这意味着每个用户可以同时运行2个作业,只需更改 compute:lock1 <的锁定键/ em>到 compute:userId:lock1 和lock2。你不能用香草芹菜做这个。

答案 1 :(得分:3)

首先,您需要限制工作人员的并发性(docs):

celery -A proj worker --loglevel=INFO --concurrency=2 -n <worker_name>

这将有助于确保即使您的代码中存在错误,也不会有超过2个活动任务。

现在您有两种方法可以实现任务编号验证:

  1. 您可以使用inspect获取有效和计划任务的数量:

     from celery import current_app
    
     def start_job():
          inspect = current_app.control.inspect()
          active_tasks = inspect.active() or {}
          scheduled_tasks = inspect.scheduled() or {}
          worker_key = 'celery@%s' % <worker_name>
          worker_tasks = active_tasks.get(worker_key, []) + scheduled_tasks.get(worker_key, [])
          if len(worker_tasks) >= 2:
              raise MyCustomException('It is impossible to start more than 2 tasks.') 
          else:
              my_task.delay()
    
  2. 您可以在DB中存储当前正在执行的任务数,并根据它来验证任务执行。

  3. 如果您想扩展您的功能,第二种方法可能会更好 - 引入高级用户或不允许一个用户执行2个请求。

答案 2 :(得分:2)

<强>第一

您需要SpiXel's solution的第一部分。据他说,&#34; 你只创建两个并发= 1 &#34;的工作进程。

<强>第二

根据time out为队列中等待的任务设置CELERY_EVENT_QUEUE_TTLhow to limit number of tasks in queue and stop feeding when full?设置为Dead Letter Exchanges队列长度限制

因此,当两个工作正在运行的作业,并且队列中的任务等待10秒或您喜欢的任何时间段时,任务将超时。或者如果队列已经完成,则新的到达任务将被取消。

<强>第三

当因为服务器忙碌而拒绝工作时,您需要额外的事情来处理通知&#34;用户#34;

https://github.com/bjedrzejewski/tasklist-service就是您所需要的。每次由于队列长度限制或消息超时而导致任务失败。 &#34;一旦达到限制,消息将从队列的前面删除或删除字母,以便为新消息腾出空间。&#34;

您可以将&#34; x-dead-letter-exchange&#34; 设置为路由到另一个队列,一旦此队列收到死信函,您就可以向用户发送通知消息