使用Django时如何处理dyno重启?

时间:2015-06-21 21:39:20

标签: python django heroku

我想根据他们的描述here来处理Heroku上的dyno重启:

  

在此期间,他们应停止接受新请求或作业,并尝试完成当前请求,或将作业放回队列以供其他工作进程处理。

从外观上看,当python收到SIGTERM并调用信号处理程序时(per signal.signal),当前运行的线程停止,因此请求在运行过程中停止。

我如何满足这两个要求? (停止接受新请求+完成当前请求)

1 个答案:

答案 0 :(得分:2)

编辑:添加了简化的示例代码,更好地解释了正在进行的请求/终止,并添加了CrazyPython中的要点。

从表面上看,你有4个问题需要解决。我将轮流接受它们,然后提供一些示例代码,以帮助澄清:

处理SIGTERM

这很简单。您只需设置一个信号处理程序即可注意到您需要关闭。 PMOTW有很多关于如何捕捉信号的例子。您可以使用此代码的变体来捕获SIGTERM并设置一个表示您正在关闭的全局标志。

拒绝新请求

Django middleware提供了一种将任何HTTP请求挂钩到应用程序的简洁方法。您可以创建一个简单的process_request()挂钩,如果设置了全局标志(从上面),则会返回错误页面。

完成现有请求

停止任何新请求后,您现在必须完成当前请求。虽然你现在可能不相信,但这意味着你什么都不做,让程序在SIGTERM之后继续像往常一样运行。让我扩展一下......

与heroku的合同是你必须在SIGTERM的10s内完成,否则它将发送SIGKILL。这意味着您无法做任何事情(作为一个表现良好的应用程序)来确保所有请求始终完成。考虑以下两种情况:

  1. 您的应用程序会在10秒内处理所有现有请求。在这种情况下,只需让程序运行就可以完成请求。不需要运行请求的特殊代码 - 所有线程/进程已经在满足您的需求!
  2. 对于某些请求,您的应用程序需要超过10个。在这种情况下,你可以做 nothing - 在长期请求完成之前,它将以heroku终结。如果你认为你可以忽略SIGKILL,那么请考虑......这是不允许的 - 请参阅signals documentation
  3. 因此,在这两种情况下,解决方案只是让程序继续运行以在终止之前完成尽可能多的当前请求。

    终止您的申请

    最简单的事情可能是等待SIGKILL在10秒后从heroku出现。它并不优雅,但应该没问题,因为你拒绝任何新的请求。

    如果这还不够好,您需要跟踪未完成的请求并使用它来决定何时可以关闭您的应用程序。关闭您的应用程序的确切方式将取决于托管它的任何内容,因此我无法在那里为您提供准确的指导。希望示例代码为您提供足够的指针。

    示例代码

    从PMOTW中的信号处理程序示例开始,我已经加强了代码以添加多个线程处理请求和终止管理器以捕获信号并允许应用程序正常关闭。您应该能够在Python2.7中运行它,然后尝试终止该过程。

    在此示例的基础上,CrazyPython创建了此gist以在django中提供具体实现。

    import signal
    import os
    import time
    import threading
    import random
    
    
    class TerminationManager(object):
    
        def __init__(self):
            self._running = True
            self._requests = 0
            self._lock = threading.Lock()
            signal.signal(signal.SIGTERM, self._start_shutdown)
    
        def _start_shutdown(self, signum, stack):
            print 'Received:', signum
            self._running = False
    
        def start_request(self):
            with self._lock:
                self._requests += 1
    
        def stop_request(self):
            with self._lock:
                self._requests -= 1
    
        def is_running(self):
            return self._running or self._requests > 0
    
        def running_requests(self):
            return self._requests
    
    
    class DummyWorker(threading.Thread):
    
        def __init__(self, app_manager):
            super(DummyWorker, self).__init__()
            self._manager = app_manager
    
        def run(self):
            while self._manager.is_running():
                # Emulate random work and delay between requests.
                if random.random() > 0.9:
                    self._manager.start_request()
                    time.sleep(random.randint(1, 3))
                    self._manager.stop_request()
                else:
                    time.sleep(1)
            print "Stopping worker"
    
    
    manager = TerminationManager()
    print 'My PID is:', os.getpid()
    
    for _ in xrange(10):
        t = DummyWorker(manager)
        t.start()
    
    while manager.is_running():
        print 'Waiting with {} running requests'.format(manager.running_requests())
        time.sleep(5)
    
    print 'All done!'
    
相关问题