宣告:本人原創文章,詳細內容已釋出在我的微信個人技術公眾號---網路技術修煉,公眾號總結普及網路基礎知識,包括基礎原理、網路方案、開發經驗和問題定位案例等,歡迎關注。
軟體工程中持續迭代和更新是必不可少的,在伺服器端軟體更新時,保持服務的連續性是一項關鍵任務。本文將從技術角度解析伺服器端軟體更新過程如何實現不停止服務的重要功能。
在進行熱升級時,程序的程式碼和資料都是非常重要的。為了實現程式碼的更新,同時又不丟失有用的資料,需要採取一些措施。有用的資料包括記憶體中的資料和檔案描述符。對於記憶體中的資料,例如設定資訊,可以通過將其落盤到組態檔中來實現保留。這樣,在升級過程中,新的程序可以讀取組態檔並繼續使用之前的設定。而對於檔案描述符,可以採用一種叫做UNIX域通訊端的機制,在程序之間進行遷移。通過這種方式,新程序可以接管原來程序的檔案描述符,從而保持之前開啟的檔案和網路連線的狀態。在某些情況下,專案可能會選擇不遷移檔案描述符,而是通過讓新舊程序共同處理一段時間的請求來逐步過渡。這樣,新程序可以逐漸接收和處理新的請求,而老程序則繼續處理舊的請求,直到所有請求都由新程序處理完畢。
另外,為了減輕對使用者端的影響,還可以採用一些HTTP協定的特性。例如,在HTTP1中可以使用"Connection: Close"頭部欄位,告知使用者端斷開連線並重新連線。而在HTTP2中,可以使用Goaway幀來類似地通知使用者端斷開連線。這樣一來,使用者端就能夠及時與新程序建立新的連線,以繼續進行請求和響應的處理。
通過這些措施和優化方法,可以實現熱升級過程中程式碼更新和資料保留的目標,並儘可能減少對系統和使用者端的影響。
nginx
官方檔案:http://nginx.org/en/docs/control.html
nginx中master程序為管理程序,woker程序為master程序fork出的子程序,是處理網路的程序。
master程序支援的訊號
TERM,INT |
快速退出 |
QUIT |
優雅退出master+worker程序(worker程序處理完存量請求再退出) |
KILL |
強子終止程序 |
HUP |
使用新的的設定啟動worker程序,並優雅退出老的worker程序 |
USR1 |
重新開啟紀錄檔檔案 |
USR2 |
升級可執行檔案(即啟動新的master程序) |
WINCH |
優雅退出woker程序 |
worker程序支援的訊號:
TERM,INT |
快速退出 |
QUIT |
優雅退出(處理完存量請求再退出) |
USR1 |
重新開啟紀錄檔檔案 |
#ps -ef | grep nginx root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx nginx 82562 82556 0 11:58 ? 00:00:00 nginx: worker process nginx 82563 82556 0 11:58 ? 00:00:00 nginx: worker process nginx 82564 82556 0 11:58 ? 00:00:00 nginx: worker process nginx 82565 82556 0 11:58 ? 00:00:00 nginx: worker process nginx 82566 82556 0 11:58 ? 00:00:00 nginx: worker process nginx 82567 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82569 82556 2 11:58 ? 00:00:03 nginx: worker process nginx 82570 82556 14 11:58 ? 00:00:24 nginx: worker process #cat /app/nginx/logs/nginx.pid 82556 可以看出nginx.pid記錄的是當前master的程序號。
kill -USR2 `cat /app/nginx/logs/nginx.pid`
執行後結果
#ps -ef | grep nginx root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx nginx 82562 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82563 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82564 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82565 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82566 82556 0 11:58 ? 00:00:01 nginx: worker process nginx 82567 82556 0 11:58 ? 00:00:02 nginx: worker process nginx 82569 82556 2 11:58 ? 00:00:06 nginx: worker process nginx 82570 82556 13 11:58 ? 00:00:43 nginx: worker process root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process #cat /app/nginx/logs/nginx.pid 85710 可以看出nginx.pid已經變成新master程序號 #cat /app/nginx/logs/nginx.pid.oldbin 82556 nginx.pid.oldbin存放老master程序號。
kill -WINCH `cat /app/nginx/logs/nginx.pid.oldbin`
#ps -ef | grep nginx root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx nginx 82569 82556 1 11:58 ? 00:00:06 nginx: worker process is shutting down nginx 82570 82556 11 11:58 ? 00:00:43 nginx: worker process is shutting down root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
此過程要不停有請求存取到nginx才能看到worker優雅退出過程,一段時間後存量請求全部處理完畢。
#ps -ef | grep nginx root 82556 1 0 11:58 ? 00:00:00 nginx: master process ./sbin/nginx root 85710 82556 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
kill -QUIT `cat /app/nginx/logs/nginx.pid.oldbin`
#ps -ef | grep nginx root 85710 1 0 12:04 ? 00:00:00 nginx: master process ./sbin/nginx nginx 85716 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85717 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85718 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85719 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85720 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85721 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85723 85710 0 12:04 ? 00:00:00 nginx: worker process nginx 85724 85710 0 12:04 ? 00:00:00 nginx: worker process
nginx訊號處理常式:ngx_signal_handler
envoy
mosn
linux環境可以使用下面函數在程序間傳遞fd。
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ps:下面程式碼均以v1.5.0版本為例。
涉及Domain Socket:
reconfig.sock記錄老程序的監聽
listen.sock記錄新程序的監聽
流程:
ReconfigureHandler
sendInheritListeners:老程序將已經存在的 fd 通過 listen.sock 傳送給新程序。
shutdownServers:老程序不再接收新連線,並優雅關閉。
WaitConnectionsDone:處理完存量請求後退出。
涉及Domain Socket:conn.sock
流程:
nginx官方檔案:http://nginx.org/en/docs/control.html
MOSN 平滑升級原理解析:https://mosn.io/docs/products/structure/smooth-upgrade/
MOSN 原始碼解析 - reconfig 機制:https://mosn.io/blog/code/mosn-reconfig-mechanism/
淺談長連線的平滑重啟:https://www.infoq.cn/article/Qfkq8Wk4FtVot46LaVkR?source=app_share
Nginx vs Envoy vs Mosn 平滑升級原理解析:https://ms2008.github.io/2019/12/28/hot-upgrade/