科多大數(shù)據(jù)帶你看看匙监,大數(shù)據(jù)開發(fā)技術(shù)學(xué)習(xí)之Python程序中不同的重啟機制
分析典型案例:
Celery 分布式異步任務(wù)框架
Gunicorn Web容器
之所以挑這兩個香伴,不僅僅是應(yīng)用廣泛,而且兩個的進(jìn)程模型比較類似呛踊,都是Master砾淌、Worker的形式,在熱重啟上思路和做法又基本不同谭网,比較有參考意義
知識點:
atexit
os.execv
模塊共享變量
信號處理
sleep原理:select
文件描述符共享
這幾個知識點不難汪厨,區(qū)別只在于Celery和Gunicorn的應(yīng)用方式。如果腦海中有這樣的知識點愉择,這篇文章也就是開闊下視野而已劫乱。织中。。
Celery和Gunicorn都會在接收到HUP信號時衷戈,進(jìn)行熱重啟操作
Celery的重啟:關(guān)舊Worker狭吼,然后execv重新啟動整個進(jìn)程
Gunicorn的重啟:建立新Worker,再關(guān)舊Worker殖妇,Master不動
下面具體的看下它們的操作和核心代碼刁笙。
對于Celery:
def _reload_current_worker():
platforms.close_open_fds([
sys.__stdin__, sys.__stdout__, sys.__stderr__,
])
os.execv(sys.executable, [sys.executable] + sys.argv)
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函數(shù)
設(shè)置全局變量
考慮到這個執(zhí)行順序,應(yīng)該就能明白Celery 是Master和Worker都退出了谦趣,嶄新呈現(xiàn)疲吸。。
看過APUE的小伙伴前鹅,應(yīng)該比較熟悉 atexit 了摘悴,這里也不多說。os.execv還挺有意思舰绘,根據(jù)Python文檔蹂喻,這個函數(shù)會執(zhí)行一個新的函數(shù)用于替換掉 當(dāng)前進(jìn)程 ,在Unix里除盏,新的進(jìn)程直接把可執(zhí)行程序讀進(jìn)進(jìn)程叉橱,保留同樣的PID。
在Python os標(biāo)準(zhǔn)庫中者蠕,這是一整套基本一毛一樣的函數(shù),也許應(yīng)該叫做函數(shù)族了:
os. execl ( path , arg0 , arg1 , … )
os. execle ( path , arg0 , arg1 , … , env )
os. execlp ( file , arg0 , arg1 , … )
os. execlpe ( file , arg0 , arg1 , … , env )
os. execv ( path , args )
os. execve ( path , args , env )
os. execvp ( file , args )
os. execvpe ( file , args , env )
以exec開頭掐松,后綴中的l和v兩種踱侣,代表命令行參數(shù)是否是變長的(前者不可變),p代表是否使用PATH定位程序文件,e自然就是在新進(jìn)程中是否使用ENV環(huán)境變量了
然后給worker的state.should_stop變量設(shè)置成False大磺。抡句。。 模塊共享變量 這個是PythonFAQ里提到的一種方便的跨模塊消息傳遞的方式杠愧,運用了Python module的單例待榔。我們都知道Python只有一個進(jìn)程,所以單例的變量到處共享
而should_stop這個變量也是簡單粗暴流济,worker在執(zhí)行任務(wù)的循環(huán)中判斷這個變量锐锣,即執(zhí)行異步任務(wù)->查看變量->獲得異步任務(wù)->繼續(xù)執(zhí)行 的循環(huán)中,如果True就拋出一個【應(yīng)該關(guān)閉】異常绳瘟,worker由此退出雕憔。
這里面有一個不大不小的坑是:信號的發(fā)送對于外部的工具,例如kill糖声,是非阻塞的斤彼,所以當(dāng)HUP信號被發(fā)出后分瘦,worker可能并沒有完成重啟(等待正在執(zhí)行的舊任務(wù)完成 才退出和新建),因此如果整個系統(tǒng)中只使用HUP信號挨個灰度各個機器琉苇,那么很有可能出現(xiàn)全部worker離線的情況
接下來我們看看Gunicorn的重啟機制:
信號實質(zhì)上掛在在Arbiter上嘲玫,Arbiter相當(dāng)于master,守護(hù)和管理worker的并扇,管理各種信號去团,事實上它init的時候就給自己起好Master的名字了,打印的時候會打出來點擊鏈接加入群【我愛python大神】:https://jq.qq.com/
class Arbiter(object):
def __init__(self, app):
#一部分略
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()
這里的函數(shù)文檔里寫了處理HUP信號的過程了拜马,簡單的三行:
讀取配置
開啟新worker
優(yōu)雅關(guān)閉舊Worker
reload函數(shù)的實現(xiàn)本身沒什么復(fù)雜的渗勘,因為Gunicorn 是個Web容器,所以master里面是沒有業(yè)務(wù)邏輯的俩莽,worker都是master fork出來的旺坠,fork是可以帶著文件描述符(自然也包括socket)過去的。這也是Gunicorn可以隨意增減worker的根源
master只負(fù)責(zé)兩件事情:
拿著被Fork的worker的PID扮超,以供關(guān)閉和處理
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函數(shù)中,使用了select.select+timeout實現(xiàn)出刷,和time.sleep的原理是一樣的璧疗,但不同之處在于select監(jiān)聽了自己創(chuàng)建的一個PIPE,以供wakeup立即喚醒點擊鏈接加入群【我愛python大神】:https://jq.qq.com/
總結(jié)
以上就介紹了Celery和Gunicorn的重啟機制差異馁龟。
從這兩者的設(shè)計來看崩侠,可以理解他們這樣實現(xiàn)的差異。
Celery是個分布式坷檩、異步的任務(wù)隊列却音,任務(wù)信息以及排隊信息實質(zhì)上是持久化在外部的MQ中的,例如RabbitMQ和redis矢炼,其中的持久化方式系瓢,這應(yīng)該另外寫一篇《高級消息隊列協(xié)議AMQP介紹》,就不在這里說了句灌,對于Celery的Master和Worker來說夷陋,可以說是完全沒有狀態(tài)的。由Celery的部署方式也可以知道胰锌,近似于一個服務(wù)發(fā)現(xiàn)的框架骗绕,下線的Worker不會對整個分布式系統(tǒng)帶來任何影響。唯一的例外可能是Beat組件匕荸,作為Celery定時任務(wù)的節(jié)拍器爹谭,要做少許改造以適配分布式的架構(gòu),并且實現(xiàn)Send Once功能榛搔。
Gunicorn作為Python的Web容器之一诺凡,會接收用戶的請求东揣,雖然我們通常都會使用nginx放在Gunicorn前方做反向代理,通常也可以使用nginx來做upstream offline腹泌、online的熱重啟嘶卧,但那就不是一個層次的事情了
這里回頭來再吐槽Golang
項目中使用到了Golang的一個Web框架,Golang在1.8中也已經(jīng)支持Http.Server的熱關(guān)閉了凉袱,但是一是因為剛出不就(竟然現(xiàn)在才出)芥吟,二是因為Golang的進(jìn)程模型和Python大相近庭,go協(xié)程嘛专甩,目前還沒有看到那個Web框架中真正實現(xiàn)Gunicorn類似的熱重啟钟鸵。
Golang 在fcgi的操作應(yīng)該就類似Python之于wsgi了。涤躲。我感覺我是選擇錯了一個web框架
也沒看見有人用syscall.Exec來用一下execve系統(tǒng)調(diào)用棺耍,Golang也沒看見有人用socket REUSE。种樱。作為一個懶惰的人感覺很蛋疼蒙袍。。嫩挤。
先讓nginx大法做這件事情好了害幅。
也可以加下python大神QQ群:304-050-799