【问题标题】:How to run a Django post_save signal as a background process?如何将 Django post_save 信号作为后台进程运行?
【发布时间】:2015-11-24 01:53:51
【问题描述】:

我有一个post_save 附加到一个模型,该模型在创建时通过来自外部 API 的 json 响应加载大量信息。不幸的是,这需要足够长的时间,以至于 heroku 在它有机会完成之前就超时了。

在做一些研究之后,似乎最好的方法是作为后台进程。

异步运行 post_save 信号的最佳方式是什么?

在阅读了有关 celery 的信息后,它对于一个进程来说似乎太重了。还有其他值得信赖的方法吗?

from django.db.models.signals import post_save
import urllib2
import json

class Foobar(models.Model):
    # ... fields ... #

def foobar_post_save(sender, instance, created, *args, **kwargs):
    """Load info from external API

    data
    ----
    sender - The model class. (Foobar)
    instance - The actual instance being saved.
    created - Boolean; True if a new record was created.
    *args, **kwargs - Capture the unneeded `raw` and `using`(1.3) arguments.
    """

    if created:
        url_to_open = <api_url>
        resp = urllib2.urlopen(url_to_open)
        data = json.loads(resp.read())
        # ... load data ... #

post_save.connect(foobar_post_save, sender=Foobar)

【问题讨论】:

    标签: django asynchronous heroku


    【解决方案1】:

    我最终找到了rq 的答案,Heroku documentation 很好

    并像这样实现:

    from django.db.models.signals import post_save
    import urllib2
    import json
    from rq import Queue
    from worker import conn
    
    q = Queue(connection=conn)
    
    class Foobar(models.Model):
        # ... fields ... #
    
        def load_data(self):
            url_to_open = <api_url>
            resp = urllib2.urlopen(url_to_open)
            data = json.loads(resp.read())
            # ... load data ... #
    
    def foobar_post_save(sender, instance, created, *args, **kwargs):
        """Load info from external API
    
        data
        ----
        sender - The model class. (Foobar)
        instance - The actual instance being saved.
        created - Boolean; True if a new record was created.
        *args, **kwargs - Capture the unneeded `raw` and `using`(1.3) arguments.
        """
    
        if created:
            q.enqueue(instance.load_data, timeout=600)
    
    post_save.connect(foobar_post_save, sender=Foobar)
    

    【讨论】:

      【解决方案2】:

      我不能 100% 确定这正是你想要的,但我做过类似的事情,所以我认为这会满足你的需要。

      import multiprocessing
      
      proc = multiprocessing.Process(
          target = foobar_post_save,           #the function to be run
          args = (sender,instance,created),    #the arguments to pass to the function
          name="foobar post save")             #name not strictly necessary
      proc.start()     #starts the process
      proc.join(60)    #wait until this is done; optional time limit is in seconds
      

      有关更多信息,请参阅此处:Python 3 multiprocessing module docs

      【讨论】:

      • 我不确定这是否适用于这种情况。因为 django 在 models.Model.save() 某处处理 post_save 信号的调用,我想我必须重载该方法才能在进程上调用 .start()。我希望会有一些不那么难看的东西
      • 好吧,我不使用 Heroku,而且我做的类似事情没有涉及 post_save 信号,所以我认为我无法为您提供更多帮助。对不起。
      • +1 我不得不稍微重新组织我的代码,但看起来这种方法可行。但是 rq 更适合我的需求,所以我将其标记为解决方案。
      【解决方案3】:

      如果您需要一种异步方法,那么使用celery 来运行您长时间运行的任务,然后从您应用的post_save 触发该任务。

      【讨论】:

      • 我对 celery 不是很熟悉,但是正如我在问题中提到的,它似乎很重,仅用于执行一个进程,我想知道还有什么更简单的吗?
      • 我想问题在于,理想情况下,长时间运行的进程应该被排除在请求-响应周期之外,以改善用户体验,并阻止您的服务器进程被缓慢的任务阻塞。您正在超时的事实表明,对于请求-响应来说,它有点太长而不能被认为是合理的。可能还有其他更简单的队列,但我会接受。
      【解决方案4】:

      另一个想法使用Celery 或类似的东西在后台启动任务。因此,您在 post_save 之后在 Celery 上创建一个新任务,然后您可以监控该任务以停止/暂停/获取成功信号等。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-12
        • 1970-01-01
        • 1970-01-01
        • 2020-07-08
        • 1970-01-01
        相关资源
        最近更新 更多