靜態語言在伺服器編程時都會遇到這樣的問題:如何保證已有的連接服務不中斷同時又升級版本?

最近花了點時間看了下nginx熱更新代碼流程,想了下結合之前的經驗一併總結下熱更新

1. 熱更新是什麼?

簡單翻譯成人類可讀的實例是如下這個樣子:

舉個例子,你現在在坐卡車,卡車開到了150KM/H

然後,有個輪胎,爆了

然後,司機說,你就直接換吧,我不停車。你小心點換嗯。就這個意思

2.網關中的熱更新

服務程序熱更新這個問題在層7網關中尤其嚴重,網關中承載著大量的請求,包括HTTP/HTTPS短連接HTTP/HTTPS長連接、甚至是websocket這種超長連接(websocket通常連接時間會很長,十幾分鐘到幾天不等)。這樣的話我們勢必不能講服務程序停止再啟動的冷更新服務進程熱更新是非常有必要的。

網關作為一個基礎組件,需要保證高可用,是很難將其先停下來再更新的;

有人說可以使用負載均衡將需要更新的組件先隔離,再停機更新,但是如果是一個很小的集群沒有負載均衡呢,又或者這樣手動一台一台升級也著實麻煩,部分情況下就算隔離了也不過是不會有新的連接過來,舊的連接/請求依舊需要處理完成,否則就會造成部分服務不可用。

不過實際上線上操作是集群隔離加熱更新一起操作

3.nginx熱更新(Upgrading Executable on the Fly)

nginx [engine x]Igor Sysoev編寫的一個HTTP和反向代理伺服器,另外它也可以作為郵件代理伺服器。 它已經在眾多流量很大的俄羅斯網站上使用了很長時間,這些網站包括YandexMail.RuVKontakte,以及Rambler。據Netcraft統計,在2012年8月份,世界上最繁忙的網站中有11.48%使用Nginx作為其伺服器或者代理伺服器。

NginX採用Master/Worker的多進程模型,Master進程負責整個NginX進程的管理。Nginx模塊化熱更新Http處理流程日誌等機制都非常經典。這裡將會簡要介紹一下熱更新的機制

3.1 nginx熱升級流程

步驟1、升級nginx二進位文件,需要先將新的nginx可執行文件替換原有舊的nginx文件,然後給nginx master進程發送USR2信號,告知其開始升級可執行文件;nginx master進程會將老的pid文件增加.oldbin後綴,然後拉起新的masterworker進程,並寫入新的master進程的pid

UID PID PPID C STIME TTY TIME CMD
root 4584 1 0 Oct17 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 12936 4584 0 Oct26 ? 00:03:24 nginx: worker process
root 12937 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 12938 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 23692 4584 0 21:28 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 23693 23692 3 21:28 ? 00:00:00 nginx: worker process
root 23694 23692 3 21:28 ? 00:00:00 nginx: worker process
root 23695 23692 3 21:28 ? 00:00:00 nginx: worker process

步驟2、在此之後,所有工作進程(包括舊進程和新進程)將會繼續接受請求。這時候,需要發送WINCH信號給nginx master進程master進程將會向worker進程發送消息,告知其需要進行graceful shutdownworker進程會在連接處理完之後進行退出。

UID PID PPID C STIME TTY TIME CMD
root 4584 1 0 Oct17 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
root 12936 4584 0 Oct26 ? 00:03:24 nginx: worker process
root 12937 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 12938 4584 0 Oct26 ? 00:00:04 nginx: worker process
root 23692 4584 0 21:28 ? 00:00:00 nginx: master process /usr/local/apigw/apigw_nginx/nginx
#若舊的worker進程還需要處理連接,則worker進程不會立即退出,需要待消息處理完後再退出

步驟3、經過一段時間之後,將會只會有新的worker進程處理新的連接。

注意,舊master進程並不會關閉它的listen socket;因為如果出問題後,需要回滾,master進程需要法重新啟動它的worker進程。

步驟4、如果升級成功,則可以向舊master進程發送QUIT信號,停止老的master進程;如果新的master進程(意外)退出,那麼舊master進程將會去掉自己的pid文件的.oldbin後綴。

3.2 nginx熱更新相關信號

master進程相關信號

USR2 升級可執行文件
WINCH 優雅停止worker進程
QUIT 優雅停止master進程

worker進程相關信號

TERM, INT 快速退出進程
QUIT 優雅停止進程

3.3 nginx相關代碼走讀

1、USR2流程

master收到USR2信號後,會拉起新的master nginx進程

新的master進程拉起新的worker進程

最終,老的worker進程新的worker進程共用一個listen socket,接受連接。

也就是此時這裡實現了埠復用的技術,也就是REUSEPORT

2、WINCH流程

master進程收到WINCH信號後,會給各個worker進程發送QUIT信號,讓其優雅退出;master進程並不再處理新的連接。

worker graceful shutdown流程,關閉listen socket,不再處理新的連接;待已有連接處理完後,清理連接,退出進程。

3、QUIT流程

master graceful shutdown流程,沒什麼好說的

3.4 nginx熱升級 QA

1、如何防止多次可執行文件觸發熱更新?

相關代碼
ngx_signal_handler
-->
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {

/*
* Ignore the signal in the new binary if its parent is
* not changed, i.e. the old binarys process is still
* running. Or ignore the signal in the old binarys
* process if the new binarys process is already running.
*/

action = ", ignoring";
ignore = 1;
break;
}

ngx_change_binary = 1;
action = ", changing binary";
break;

若老的nginx還在,nginx無法進行熱更新二進位文件

2、nginx升級過程中,發現新的可執行文件出現問題該如何回滾?

  • a、向舊master進程發送HUP信號。舊進程將啟動新的worker進程,而且不會重新讀取配置。之後,通過向新的主master進程發送QUIT信號,可以優雅地關閉新的masterworker進程
  • b、將TERM信號發送到新的master進程,然後新的master進程將向其worker進程發送一條消息,讓它們立即退出,這種退出不是graceful shutdown。當新的master進程退出時,舊的master進程將啟動新的worker進程
  • c、如果新的進程沒有退出,則應該向它們發送終止KILL信號。當新的master進程退出時,舊的master進程將啟動新的工作進程。

3、什麼是graceful shutdown

本文中的graceful shutdown是指server不再處理新的連接,但是進程不會立即退出,待所有連接斷開後再退出進程。

4. 總結

總結一下個人在nginx二進位文件熱升級時用的命令

cd /usr/local/nginx
cp nginx nginx_bak
mv /data/nginx/nginx ./nginx #需要使用mv來更新二進位文件
./nginx -t #嘗試啟動,查看其載入配置文件等初始化功能是否正常

netstat -anp | grep -E "80|443" | grep nginx #檢查連接狀態
kill -USR2 `cat /usr/local/nginx/nginx.pid` #升級nginx可執行文件,此時會有兩組nginx master和worker進程
kill -WINCH `cat /usr/local/nginx/nginx.pid.oldbin` #新的可執行文件啟動ok,且能夠正常處理數據流,告知老的master進程去通知其worker進程進行優雅退出

...
kill -QUIT `cat /usr/local/nginx/nginx.pid.oldbin` #待所有的老的nginx worker進程優雅退出後(處理完連接),停止老的master進程

TODO:nginx還會有依賴的so文件的熱升級–其實更應該屬於後台進程的so文件熱升級流程,我在使用它的時候也踩過坑–主要原因還是操作不規範,對so其載入運行原理不夠熟悉導致

5.額外

實際上,靜態語言後端server有一套固定的熱升級(單進程)流程,其基本流程如下:

若需要支持熱升級的是多進程,那麼nginx的熱升級過程是最值得參考的

  • 1、通過調用fork/exec啟動新的版本的進程
  • 2、子進程調用介面獲取從父進程繼承的socket 文件描述符重新監聽socket
  • 3、在此過程中,不會對用戶請求造成任何中斷。

nginx的熱升級流程也是類似,只不過由於nginx工作是多進程,故它會先啟動新版本的一組master/worker進程

然後停止老的worker進程,讓其不處理連接,由新的worker進程來處理連接;

升級完畢後,即可退出老的master進程,熱升級完成。

6.參考

tengine.taobao.org/ngin


推薦閱讀:
相关文章