我想處理在Heroku上賽道重新根據其描述here:如何在使用Django時處理dyno重新啓動?
在這段時間裏,他們應該停止接受新的請求或工作,eattempt到完成他們的當前請求,或把工作回其他工作進程處理的隊列。
從外觀上來看,當接收蟒SIGTERM和信號處理程序被調用(每signal.signal
),當前線程的運行被停止,所以該請求被停止在運行的中間。
我該如何滿足這兩個要求? (停止接受新的請求+完成當前的請求)
我想處理在Heroku上賽道重新根據其描述here:如何在使用Django時處理dyno重新啓動?
在這段時間裏,他們應該停止接受新的請求或工作,eattempt到完成他們的當前請求,或把工作回其他工作進程處理的隊列。
從外觀上來看,當接收蟒SIGTERM和信號處理程序被調用(每signal.signal
),當前線程的運行被停止,所以該請求被停止在運行的中間。
我該如何滿足這兩個要求? (停止接受新的請求+完成當前的請求)
編輯:添加了簡化的示例代碼,更好地解釋了正在進行的請求/終止並添加了CrazyPython的要點。
面對它,你有4個問題需要解決。我會帶他們反過來再給出一些示例代碼,應有助於澄清:
處理SIGTERM
這很簡單。你只需要設置一個信號處理程序來注意你需要關閉。 PMOTW有一個很好的例子,如何捕捉信號。您可以使用此代碼的變體來捕獲SIGTERM並設置一個表明您正在關閉的全局標誌。
拒絕新的請求
Django middleware提供任何掛鉤HTTP請求到應用程序的一種巧妙的方法。你可以創建一個簡單的process_request()
鉤子,如果設置了全局標誌(從上面),它將返回一個錯誤頁面。
完成現有請求
任何新的請求停止,你現在必須讓你的當前請求完成。雖然你現在可能不相信,但這意味着你什麼都不做,讓程序在SIGTERM之後照常運行。讓我擴展一下...
與heroku的合同是你必須在SIGTERM的10s內完成,否則它將發送一個SIGKILL。這意味着你不能做任何事情(作爲一個行爲良好的應用程序)來確保所有請求總是完成。考慮兩種情況:
因此,在這兩種情況下,解決方案只是讓程序繼續運行,以便在終止之前完成許多當前請求。
終止您的應用程序
做可能是等待SIGKILL沿着從Heroku的10秒晚一點最簡單的事情。這不是優雅的,但它應該是好的,因爲你拒絕任何新的請求。
如果這還不夠好,您需要跟蹤未完成的請求並使用它來決定何時關閉應用程序。關閉應用程序的確切方式取決於託管它的任何東西,所以我不能在那裏給你確切的指導。不過,希望示例代碼能夠給你足夠的指針。
示例代碼
從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!'
我已經知道PMOTW的大部分內容,但我無法理解如何應用它,因爲它暫停處理請求。所以剩下的一個問題是:我如何完成我目前的要求? –
我可以等待一個操作系統警報1秒鐘,然後我收到SIGKILL並使用sys.exit() –
這個答案在改進之前不符合獎勵條件,儘管非常接近被授予賞金。我與OP有同樣的看法,改進這一點,我可以授予它。 –
我從來沒有聽說過任何特殊的要求。正如Django的前BDFL爲Heroku工作,如果是這樣,你會認爲它會被記錄下來。 –
@DanielRoseman更新了與鏈接 –
的問題*爲了滿足應用程序的乾淨關閉,* *用於服務請求的底層併發模型必須支持從SIGTERM/SIGINT'排隊關閉請求,完成當前請求正在進行並終止。至少在異步web框架中,我是這麼做的。 –