在
這篇文章中,提到了Master進程對信號的處理函數,其中有兩個信號比較有意思。
SIGHUP:用來熱更新(Reload)應用
SIGUSR2:用來在線升級(upgrade on the fly)gunicorn
下面來詳細看一下:
SIGHUP:
Reload the configuration, start the new worker processes with a new configuration and gracefully shutdown older workers.
SIGUP對應的信號處理函數是Arbiter.reload。簡化后的核心代碼如下:
1 def reload(self): 2 old_address = self.cfg.address 3 4 # reload conf,重新加載配置 5 self.app.reload() 6 self.setup(self.app) 7 8 # reopen log files 9 self.log.reopen_files() 10 11 # do we need to change listener ?,處理監聽端口變化的情況 12 if old_address != self.cfg.address: 13 # close all listeners 14 [l.close() for l in self.LISTENERS] 15 # init new listeners 16 self.LISTENERS = create_sockets(self.cfg, self.log) 17 listeners_str = ",".join([str(l) for l in self.LISTENERS]) 18 self.log.info("Listening at: %s", listeners_str) 19 20 # spawn new workers,啟動新的Worker(數量和類型來自新的配置) 21 for i in range(self.cfg.workers): 22 self.spawn_worker() 23 24 # manage workers,這里會kill掉原來的Worker 25 self.manage_workers()
在上面的引用中提到,會“
優雅”(graceful)地kill掉老的worker進程。實現也很簡單,Arbiter為每一個fork出的worker進程設置一個自增的“worker_age”,worker中這個屬性越小,表明這個worker越老。在manage_workers中,如果已經啟動的worker進程數量大於配置中的數量,那么會kill掉比較老的worker進程。代碼如下
1 def manage_workers(self): 2 """\ 3 Maintain the number of workers by spawning or killing 4 as required. 5 """ 6 if len(self.WORKERS.keys()) < self.num_workers: 7 self.spawn_workers() 8 9 workers = self.WORKERS.items() 10 workers = sorted(workers, key=lambda w: w[1].age) # 按worker的age順序排序 11 while len(workers) > self.num_workers: # num_workers是配置的worker數量 12 (pid, _) = workers.pop(0) 13 self.kill_worker(pid, signal.SIGTERM)
SIGUSR2
Upgrade the Gunicorn on the fly. A separate TERM signal should be used to kill the old process.
對應的信號處理函數是Arbiter.reexec,該函數會重新fork出新的master-workers進程,但不會影響到原來的master,worker進程,所以上面提到需要將原來的進程kill掉。
1 def reexec(self): 2 """\ 3 Relaunch the master and workers. 4 """ 5 if self.reexec_pid != 0: 6 self.log.warning("USR2 signal ignored. Child exists.") 7 return 8 9 if self.master_pid != 0: 10 self.log.warning("USR2 signal ignored. Parent exists") 11 return 12 13 master_pid = os.getpid() 14 self.reexec_pid = os.fork() 15 if self.reexec_pid != 0: 16 return 17 18 self.cfg.pre_exec(self) 19 20 environ = self.cfg.env_orig.copy() 21 fds = [l.fileno() for l in self.LISTENERS] 22 environ['GUNICORN_FD'] = ",".join([str(fd) for fd in fds]) # 設置了一些環境變量,用來區分是正常啟動gunicorn還是通過fork重新啟動。 23 environ['GUNICORN_PID'] = str(master_pid) 24 25 os.chdir(self.START_CTX['cwd']) 26 27 # exec the process using the original environnement 28 os.execvpe(self.START_CTX[0], self.START_CTX['args'], environ)
下面做一下實驗,兩個終端,1號終端運行代碼,2號終端發送信號。
step1:
1號終端啟動gunirorn:
gunicorn -w 2 gunicorn_app:app ,
2號終端查看python進程:
ps -ef | grep python,
1號終端輸出
[2017-01-19 15:21:23 +0000] [6166] [INFO] Starting gunicorn 19.6.0
[2017-01-19 15:21:23 +0000] [6166] [INFO] Listening at:
http://127.0.0.1:8000 (6166)
[2017-01-19 15:21:23 +0000] [6166] [INFO] Using worker: sync
[2017-01-19 15:21:23 +0000] [6171] [INFO] Booting worker with pid: 6171
[2017-01-19 15:21:23 +0000] [6172] [INFO] Booting worker with pid: 6172
2號終端輸出
hzliumi+ 6166 5721 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6171 6166 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6172 6166 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
可以看到,
master進程的PID是6166,然后兩個worker進程的pid分別是6171、6172. 下面需要用到master進程的PID
step2:
2號終端發送信號:
kill -SIGUSR2 6166 , 然后查看python進程
1號終端追加輸出
[2017-01-19 15:27:08 +0000] [6166] [INFO] Handling signal: usr2
[2017-01-19 15:27:08 +0000] [6629] [INFO] Starting gunicorn 19.6.0
[2017-01-19 15:27:08 +0000] [6629] [INFO] Listening at:
http://127.0.0.1:8000 (6629)
[2017-01-19 15:27:08 +0000] [6629] [INFO] Using worker: sync
[2017-01-19 15:27:08 +0000] [6634] [INFO] Booting worker with pid: 6634
[2017-01-19 15:27:08 +0000] [6635] [INFO] Booting worker with pid: 6635
2號終端輸出
hzliumi+ 6166 5721 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6171 6166 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6172 6166 0 15:21 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6629 6166 3 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6634 6629 0 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6635 6629 0 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
從1號終端可以看出,基本是重新啟動了gunicorn,不過從2號終端輸出可以看到,
新的master進程(pid 6629)是老的master進程(pid 6166)的子進程
step3:
2號終端發送信號:
kill -SIGTERM 6166 , 稍等幾秒后查看python進程:
1號終端追加輸出
[2017-01-19 15:29:42 +0000] [6166] [INFO] Handling signal: term
[2017-01-19 15:29:42 +0000] [6171] [INFO] Worker exiting (pid: 6171)
[2017-01-19 15:29:42 +0000] [6172] [INFO] Worker exiting (pid: 6172)
[2017-01-19 15:29:42 +0000] [6166] [INFO] Shutting down: Master
[2017-01-19 15:29:42 +0000] [6629] [INFO] Master has been promoted.
2號終端輸出
hzliumi+ 6629 1 0 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6634 6629 0 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
hzliumi+ 6635 6629 0 15:27 pts/0 00:00:00 /usr/bin/python /usr/local/bin/gunicorn -w 2 gunicorn_app:app
可以看到,老的master進程(6166)及其fork出的worker子進程(pid分別是6171、6172)已經被kill掉了
references: