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

标签 python django heroku

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

During this time they should stop accepting new requests or jobs and eattempt to finish their current requests, or put jobs back on the queue for other worker processes to handle.

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

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

最佳答案

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

从表面上看,您有 4 个问题需要解决。我将依次介绍它们,然后给出一些有助于阐明的示例代码:

处理 SIGTERM

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

拒绝新请求

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

完成现有请求

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

与 heroku 的约定是您必须在 SIGTERM 的 10 秒内完成,否则无论如何它都会发送 SIGKILL。这意味着您无法(作为一个行为良好的应用程序)确保所有请求始终完成。考虑两种情况:

  1. 您的应用程序会在 10 秒内处理所有现有请求。在这种情况下,只要让你的程序运行就可以让请求完成。不需要特殊代码来运行请求 - 所有线程/进程都已经在执行您需要的操作!
  2. 您的应用程序的某些请求需要超过 10 秒的时间。在这种情况下,您无能为力 - 它会在长请求完成之前被 heroku 以终极力量终止。如果您认为可以忽略 SIGKILL,请另想一想...这是不允许的 - 请参阅 signals documentation .

因此,在这两种情况下,解决方案都是让您的程序继续运行,让尽可能多的当前请求在终止前完成。

终止您的应用程序

最简单的做法可能是等待 10 秒后来自 heroku 的 SIGKILL。这并不优雅,但应该没问题,因为您正在拒绝任何新请求。

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

示例代码

从 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!'

关于python - 使用 Django 时如何处理 dyno 重启?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30969597/

相关文章:

python - 需要 Numpy 花哨的索引建议

python - 在 Mongoose 中运行 Python 脚本

python - Django - 加载 css 的问题

python - 将数千条记录插入表中的最有效方法是什么(MySQL、Python、Django)

security - Heroku 和 ip 掩码

python - 如何在 Heroku 上使用supervisord

python - 将 python 对象转储为 yaml 文件

Python3 最高效的 String 到 Float 转换

python - Dreamhost + Passenger + Django 故障排除

ruby-on-rails - Heroku 应用程序无法正确加载图片