從nginx熱更新聊一聊Golang中的伺服器熱更新(上)
從nginx熱更新聊一聊Golang中的熱更新(上)
靜態語言在伺服器程式設計時都會遇到這樣的問題:如何保證已有的連線服務不中斷同時又升級版本?
最近花了點時間看了下nginx熱更新程式碼流程,想了下結合之前的經驗一併總結下熱更新
熱更新是什麼?
舉個例子,你現在在坐卡車,卡車開到了150KM/H
然後,有個輪胎,爆了
然後,司機說,你就直接換吧,我不停車。你小心點換
嗯。就這個意思
閘道器中的熱更新
服務程式熱更新這個問題在層7閘道器中尤其嚴重,閘道器中承載著大量的請求,包括HTTP/HTTPS短連線、HTTP/HTTPS長連線、甚至是websocket這種超長連線(websocket通常連線時間會很長,十幾分鍾到幾天不等)。服務程序熱更新是非常有必要的。
閘道器作為一個基礎元件,需要保證高可用,是很難將其先停下來再更新的;
有人說可以使用負載均衡將需要更新的元件先隔離,再停機更新,但是如果是一個很小的叢集沒有負載均衡呢,又或者這樣手動一臺一臺升級也著實麻煩,部分情況下就算隔離了也不過是不會有新的連線過來,舊的連線/請求依舊需要處理完成,否則就會造成部分服務不可用
不過實際上線上操作是叢集隔離加熱更新一起操作
nginx熱更新(Upgrading Executable on the Fly)
nginx [engine x]是Igor Sysoev編寫的一個HTTP和反向代理伺服器,另外它也可以作為郵件代理伺服器。 它已經在眾多流量很大的俄羅斯網站上使用了很長時間,這些網站包括Yandex、
NginX採用Master/Worker的多程序模型,Master程序負責整個NginX程序的管理。Nginx的模組化、熱更新、Http處理流程、日誌等機制都非常經典。這裡將會簡要介紹一下熱更新的機制
nginx熱升級流程
步驟1、升級nginx二進位制檔案,需要先將新的nginx可執行檔案替換原有舊的nginx檔案,然後給nginx master程序傳送USR2訊號,告知其開始升級可執行檔案;nginx master程序會將老的pid檔案增加.oldbin字尾,然後拉起新的master和worker程序,並寫入新的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 shutdown,worker程序會在連線處理完之後進行退出。
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字尾。
nginx熱更新相關訊號
master程序相關訊號
USR2 升級可執行檔案
WINCH 優雅停止worker程序
QUIT 優雅停止master程序
worker程序相關訊號
TERM, INT 快速退出程序
QUIT 優雅停止程序
nginx相關程式碼走讀
1、USR2流程
master收到USR2訊號後,會拉起新的master nginx程序;
新的master程序拉起新的worker程序;
最終,老的worker程序和新的worker程序共用一個listen socket,接受連線
若打開了REUSEPORT開關,則socket繼承情況會有些區別,感興趣的可以自行翻看程式碼
2、WINCH流程
master程序收到WINCH訊號後,會給各個worker程序傳送QUIT訊號,讓其優雅退出;master程序並不再處理新的連線。
worker graceful shutdown流程,關閉listen socket,不再處理新的連線;待已有連線處理完後,清理連線,退出程序。
3、QUIT流程
master graceful shutdown流程,沒什麼好說的
nginx升級過程中若出現問題如何回滾?
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 binary's process is still
* running. Or ignore the signal in the old binary's
* process if the new binary's 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訊號,可以優雅地關閉新的master和worker程序。
- b、將TERM訊號傳送到新的master程序,然後新的master程序將向其worker程序傳送一條訊息,讓它們立即退出,這種退出不是graceful shutdown。當新的master程序退出時,舊的master程序將啟動新的worker程序。
- c、如果新的程序沒有退出,則應該向它們傳送終止KILL訊號。當新的master程序退出時,舊的master程序將啟動新的工作程序。
3、什麼是graceful shutdown
本文中的graceful shutdown是指server不再處理新的連線,但是程序不會立即退出,待所有連線斷開後再退出程序。
總結一下個人在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其載入執行原理不夠熟悉導致
熱升級
實際上,靜態語言後端server有一套固定的熱升級(單程序)流程,其基本流程如下:
若需要支援熱升級的是多程序,那麼nginx的熱升級過程是最值得參考的
-
1、通過呼叫 fork/exec 啟動新的版本的程序,
-
2、子程序呼叫介面獲取從父程序繼承的 socket 檔案描述符重新監聽 socket
-
3、在此過程中,不會對使用者請求造成任何中斷。
nginx的熱升級流程也是類似,只不過由於nginx工作是多程序,故它會先啟動新版本的一組master/worker程序;
然後停止老的worker程序,讓其不處理連線,由新的worker程序來處理連線;
升級完畢後,即可退出老的master程序,熱升級完成。