新聞資訊
行業(yè)資訊
Python程序中不同的重啟機制
2017-05-02 14:17:48
摘要:Gunicorn作為Python的Web容器之一,會接收用戶的請求,雖然我們通常都會使用nginx放在Gunicorn前方做反向代理,通常也可以使用nginx來做upstream offline、online的熱重啟,但那就不是一個層次的事情了。
  Gunicorn作為Python的Web容器之一,會接收用戶的請求,雖然我們通常都會使用nginx放在Gunicorn前方做反向代理,通常也可以使用nginx來做upstream offline、online的熱重啟,但那就不是一個層次的事情了。
Python程序中不同的重啟機制
  分析典型案例:   Celery 分布式異步任務框架   Gunicorn Web容器   之所以挑這兩個,不僅僅是應用廣泛,而且兩個的進程模型比較類似,都是Master、Worker的形式,在熱重啟上思路和做法又基本不同,比較有參考意義   知識點:   atexit   os.execv   模塊共享變量   信號處理   sleep原理:select   文件描述符共享   這幾個知識點不難,區(qū)別只在于Celery和Gunicorn的應用方式。如果腦海中有這樣的知識點,這篇文章也就是開闊下視野而已。。。   Celery和Gunicorn都會在接收到HUP信號時,進行熱重啟操作   Celery的重啟:關(guān)舊Worker,然后execv重新啟動整個進程   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ù)   設置全局變量   考慮到這個執(zhí)行順序,應該就能明白Celery 是Master和Worker都退出了,嶄新呈現(xiàn)。。   看過APUE的小伙伴,應該比較熟悉 atexit 了,這里也不多說。os.execv還挺有意思,根據(jù)Python文檔,這個函數(shù)會執(zhí)行一個新的函數(shù)用于替換掉 當前進程 ,在Unix里,新的進程直接把可執(zhí)行程序讀進進程,保留同樣的PID。   在Python os標準庫中,這是一整套基本一毛一樣的函數(shù),也許應該叫做函數(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自然就是在新進程中是否使用ENV環(huán)境變量了   然后給worker的state.should_stop變量設置成False。。。 模塊共享變量 這個是 Python FAQ里提到的一種方便的跨模塊消息傳遞的方式,運用了Python module的單例。我們都知道Python只有一個進程,所以單例的變量到處共享   而should_stop這個變量也是簡單粗暴,worker在執(zhí)行任務的循環(huán)中判斷這個變量,即執(zhí)行異步任務->查看變量->獲得異步任務->繼續(xù)執(zhí)行 的循環(huán)中,如果True就拋出一個【應該關(guān)閉】異常,worker由此退出。   這里面有一個不大不小的坑是:信號的發(fā)送對于外部的工具,例如kill,是非阻塞的,所以當HUP信號被發(fā)出后,worker可能并沒有完成重啟(等待正在執(zhí)行的舊任務完成 才退出和新建),因此如果整個系統(tǒng)中只使用HUP信號挨個灰度各個機器,那么很有可能出現(xiàn)全部worker離線的情況   接下來我們看看Gunicorn的重啟機制:   信號實質(zhì)上掛在在Arbiter上,Arbiter相當于master,守護和管理worker的,管理各種信號,事實上它init的時候就給自己起好Master的名字了,打印的時候會打出來:   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)本身沒什么復雜的,因為Gunicorn 是個Web容器,所以master里面是沒有業(yè)務邏輯的,worker都是master fork出來的,fork是可以帶著文件描述符(自然也包括socket)過去的。這也是Gunicorn可以隨意增減worker的根源   master只負責兩件事情:   拿著被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立即喚醒   總結(jié)   以上就介紹了Celery和Gunicorn的重啟機制差異。   從這兩者的設計來看,可以理解他們這樣實現(xiàn)的差異。   Celery是個分布式、異步的任務隊列,任務信息以及排隊信息實質(zhì)上是持久化在外部的MQ中的,例如RabbitMQ和redis,其中的持久化方式,這應該另外寫一篇《高級消息隊列協(xié)議AMQP介紹》,就不在這里說了,對于Celery的Master和Worker來說,可以說是完全沒有狀態(tài)的。由Celery的部署方式也可以知道,近似于一個服務發(fā)現(xiàn)的框架,下線的Worker不會對整個分布式系統(tǒng)帶來任何影響。唯一的例外可能是Beat組件,作為Celery定時任務的節(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的進程模型和Python大相近庭,go協(xié)程嘛,目前還沒有看到那個Web框架中真正實現(xiàn)Gunicorn類似的熱重啟。   Golang 在fcgi的操作應該就類似Python之于wsgi了。。我感覺我是選擇錯了一個web框架   也沒看見有人用syscall.Exec來用一下execve系統(tǒng)調(diào)用,Golang也沒看見有人用socket REUSE。。作為一個懶惰的人感覺很蛋疼。。。先讓nginx大法做這件事情好了。
USA-IDC為您提供免備案服務器 0元試用
立即聯(lián)系在線客服,即可申請免費產(chǎn)品試用服務
立即申請