Python程式中不同的重啟機制

2021-09-23 07:19:01 字數 3976 閱讀 7907

分析典型案例:

celery 分布式非同步任務框架

gunicorn web容器

之所以挑這兩個,不僅僅是應用廣泛,而且兩個的程序模型比較類似,都是master、worker的形式,在熱重啟上思路和做法又基本不同,比較有參考意義

知識點:

這幾個知識點不難,區別只在於celery和gunicorn的應用方式。如果腦海中有這樣的知識點,這篇文章也就是開闊下視野而已。。。

celery和gunicorn都會在接收到hup訊號時,進行熱重啟操作

celery的重啟:關舊worker,然後execv重新啟動整個程序

gunicorn的重啟:建立新worker,再關舊worker,master不動

下面具體的看下它們的操作和核心**。

對於celery:

def _reload_current_worker(): 

platforms.close_open_fds([ 

sys.__stdin__, sys.__stdout__, sys.__stderr__, 

]) os.execv(sys.executable, [sys.executable] + sys.ar**) 

def install_worker_restart_handler(worker, sig='sighup'

): def restart_worker_sig_handler(*args): 

"""signal handler restarting the current python program."

""import atexit 

atexit.register(_reload_current_worker) 

from

celery.worker import state 

state.should_stop = ex_ok 

platforms.signals[sig] = restart_worker_sig_handler 

hup上掛的restart_worker_sig_handler 就做了兩件事:

註冊atexit函式

設定全域性變數

考慮到這個執行順序,應該就能明白celery 是master和worker都退出了,嶄新呈現。。

看過apue的小夥伴,應該比較熟悉 atexit 了,這裡也不多說。os.execv還挺有意思,根據python文件,這個函式會執行乙個新的函式用於替換掉 當前程序 ,在unix裡,新的程序直接把可執行程式讀程序式,保留同樣的pid。

在python os標準庫中,這是一整套基本一毛一樣的函式,也許應該叫做函式族了:

以exec開頭,字尾中的l和v兩種,代表命令列引數是否是變長的(前者不可變),p代表是否使用path定位程式檔案,e自然就是在新程序中是否使用env環境變數了

然後給worker的state.should_stop變數設定成false。。。 模組共享變數 這個是 python faq裡提到的一種方便的跨模組訊息傳遞的方式,運用了python module的單例。我們都知道python只有乙個程序,所以單例的變數到處共享

而should_stop這個變數也是簡單粗暴,worker在執行任務的迴圈中判斷這個變數,即執行非同步任務->檢視變數->獲得非同步任務->繼續執行 的迴圈中,如果true就丟擲乙個【應該關閉】異常,worker由此退出。

這裡面有乙個不大不小的坑是:訊號的傳送對於外部的工具,例如kill,是非阻塞的,所以當hup訊號被發出後,worker可能並沒有完成重啟(等待正在執行的舊任務完成 才退出和新建),因此如果整個系統中只使用hup訊號挨個灰度各個機器,那麼很有可能出現全部worker離線的情況

接下來我們看看gunicorn的重啟機制:

訊號實質上掛在在arbiter上,arbiter相當於master,守護和管理worker的,管理各種訊號,事實上它init的時候就給自己起好master的名字了,列印的時候會打出來:

class arbiter(object): 

#一部分略 

self.master_name = "master"

def handle_hup(self): 

"""\ 

hup handling. 

- reload configuration 

- start the new worker processes with

a new configuration 

- gracefully shutdown the old worker processes 

""" 

self.log.info("hang up: %s"

, self.master_name) 

self.reload() 

這裡的函式文件裡寫了處理hup訊號的過程了,簡單的三行:

讀取配置

開啟新worker

優雅關閉舊worker

reload函式的實現本身沒什麼複雜的,因為gunicorn 是個web容器,所以master裡面是沒有業務邏輯的,worker都是master fork出來的,fork是可以帶著檔案描述符(自然也包括socket)過去的。這也是gunicorn可以隨意增減worker的根源

master只負責兩件事情:

拿著被fork的worker的pid,以供關閉和處理

1秒醒來一次,看看有沒有worker超時了需要被乾掉

while 

true

: sig = self.sig_queue.pop(0) if len(self.sig_queue) else

none 

if sig is

none: 

self.sleep() 

self.murder_workers() 

self.manage_workers() 

continue

else

: #處理訊號 

在sleep函式中,使用了select.select+timeout實現,和time.sleep的原理是一樣的,但不同之處在於select監聽了自己建立的乙個pipe,以供wakeup立即喚醒

總結

以上就介紹了celery和gunicorn的重啟機制差異。

從這兩者的設計來看,可以理解他們這樣實現的差異。

celery是個分布式、非同步的任務佇列,任務資訊以及排隊資訊實質上是持久化在外部的mq中的,例如rabbitmq和redis,其中的持久化方式,這應該另外寫一篇《高階訊息佇列協議amqp介紹》,就不在這裡說了,對於celery的master和worker來說,可以說是完全沒有狀態的。由celery的部署方式也可以知道,近似於乙個服務發現的框架,下線的worker不會對整個分布式系統帶來任何影響。唯一的例外可能是beat元件,作為celery定時任務的節拍器,要做少許改造以適配分布式的架構,並且實現send once功能。

gunicorn作為python的web容器之一,會接收使用者的請求,雖然我們通常都會使用nginx放在gunicorn前方做反向**,通常也可以使用nginx來做upstream offline、online的熱重啟,但那就不是乙個層次的事情了

這裡回頭來再吐槽golang

專案中使用到了golang的乙個web框架,golang在1.8中也已經支援http.server的熱關閉了,但是一是因為剛出不就(竟然現在才出),二是因為golang的程序模型和python大相近庭,go協程嘛,目前還沒有看到那個web框架中真正實現gunicorn類似的熱重啟。

golang 在fcgi的操作應該就類似python之於wsgi了。。我感覺我是選擇錯了乙個web框架

也沒看見有人用syscall.exec來用一下execve系統呼叫,golang也沒看見有人用socket reuse。。作為乙個懶惰的人感覺很蛋疼。。。

先讓nginx**做這件事情好了。

python 重啟程式 重新啟動Python程式

我會繞過所有的焦慮,你可能會從試圖重新執行自己,把它交給環境。我的意思是 當程式以特定的 重新啟動 退出時,有乙個控制程式只在迴圈中執行程式 使用與給定引數相同的引數 這可以像cmd檔案一樣簡單,也可以像使用os.system的另乙個 非常簡單 python程式一樣複雜。基本上,只要控制程式得到 r...

python 匯入不同包中的模組

如果我們要匯入的模組在不同的包中,該如何匯入呢?可以用from 包名 import 模組名或者from 包名.模組名 import 模組中的 如變數 函式 方法等 方式一 匯入整個模組,即from 包名 import 模組名 如下圖結構,有package01包 test.py在其中 和package...

python中cls和self的不同

class a object a a staticmethod deffool1 name print name deffool2 self,name print name classmethod deffool3 cls,name print name 首先定義了乙個類a,類a中有3個函式,foo...