nginx 平滑重啟的實(shí)現(xiàn)方法
一、背景
在服務(wù)器開發(fā)過程中,難免需要重啟服務(wù)加載新的代碼或配置,如果能夠保證server重啟的過程中服務(wù)不間斷,那重啟對于業(yè)務(wù)的影響可以降為0。最近調(diào)研了一下nginx平滑重啟,覺得很有意思,記錄下來供有興趣的同學(xué)查閱。
二、重啟流程
- 重啟意味著新舊接替,在交接任務(wù)的過程中勢必會存在新舊server并存的情形,因此,重啟的流程大致為:
- 啟動新的server
- 新舊server并存,兩者共同處理請求,提供服務(wù)
- 舊的server處理完所有的請求之后優(yōu)雅退出
- 這里,最主要的問題在于如何保證新舊server可以并存,如果重啟前后的server端口一致,如何保證兩者可以監(jiān)聽同一端口。
三、nginx實(shí)現(xiàn)
為了驗(yàn)證nginx平滑重啟,筆者首先嘗試nginx啟動的情形下再次開啟一個新的server實(shí)例,結(jié)果如圖:
很明顯,重新開啟server實(shí)例是行不通的,原因在于新舊server使用了同一個端口80,在未開始socket reuseport選項(xiàng)復(fù)用端口時,bind系統(tǒng)調(diào)用會出錯。nginx默認(rèn)bind重試5次,失敗后直接退出。而nginx需要監(jiān)聽IPV4地址0.0.0.0和IPV6地址[::],故圖中打印出10條emerg日志。
接下來就開始嘗試平滑重啟命令了,一共兩條命令:
kill -USR2 `cat /var/run/nginx.pid` kill -QUIT `cat /var/run/nginx.pid.oldbin`
第一條命令是發(fā)送信號USR2給舊的master進(jìn)程,進(jìn)程的pid存放在/var/run/nginx.pid文件中,其中nginx.pid文件路徑由nginx.conf配置。
第二條命令是發(fā)送信號QUIT給舊的master進(jìn)程,進(jìn)程的pid存放在/var/run/nginx.pid.oldbin文件中,隨后舊的master進(jìn)程退出。
那么問題來了,為什么舊的master進(jìn)程的pid存在于兩個pid文件之中?事實(shí)上,在發(fā)送信號USR2給舊的master進(jìn)程之后,舊的master進(jìn)程將pid重命名,原先的nginx.pid文件rename成nginx.pid.oldbin。這樣新的master進(jìn)行就可以使用nginx.pid這個文件名了。
先執(zhí)行第一條命令,結(jié)果如圖:
不錯,新舊master和worker進(jìn)程并存了。 再來第二條命令,結(jié)果如圖:
如你所見,舊的master進(jìn)程8527和其worker進(jìn)程全部退出,只剩下新的master進(jìn)程12740。
不由得產(chǎn)生困惑,為什么手動開啟一個新的實(shí)例行不通,使用信號重啟就可以達(dá)到。先看下nginx log文件:
除了之前的錯誤日志,還多了一條notice,意思就是繼承了sockets,fd值為6,7。 隨著日志翻看nginx源碼,定位到nginx.c/ngx_exec_new_binary函數(shù)之中,
ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) { ... ctx.path = argv[0]; ctx.name = "new binary process"; ctx.argv = argv; n = 2; env = ngx_set_environment(cycle, &n); ... var = ngx_alloc(sizeof(NGINX_VAR) + cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2, cycle->log); ... p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR)); ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { p = ngx_sprintf(p, "%ud;", ls[i].fd); } *p = '\0'; env[n++] = var; ... env[n] = NULL; ... ctx.envp = (char *const *) env; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (ngx_rename_file(ccf->pid.data, ccf->oldpid.data) == NGX_FILE_ERROR) { ... return NGX_INVALID_PID; } pid = ngx_execute(cycle, &ctx); if (pid == NGX_INVALID_PID) { if (ngx_rename_file(ccf->oldpid.data, ccf->pid.data) == NGX_FILE_ERROR) { ... } } ... return pid; }
函數(shù)的流程為
- 將舊的master進(jìn)程監(jiān)聽的所有fd,拷貝至新master進(jìn)程的env環(huán)境變量NGINX_VAR。
- rename重命名pid文件
- ngx_execute函數(shù)fork子進(jìn)程,execve執(zhí)行命令行啟動新的server。
- 在server啟動流程之中,涉及到環(huán)境變量NGINX_VAR的解析,ngx_connection.c/ngx_add_inherited_sockets具體代碼為:
static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle) { ... inherited = (u_char *) getenv(NGINX_VAR); if (inherited == NULL) { return NGX_OK; } if (ngx_array_init(&cycle->listening, cycle->pool, 10, sizeof(ngx_listening_t)) != NGX_OK) { return NGX_ERROR; } for (p = inherited, v = p; *p; p++) { if (*p == ':' || *p == ';') { s = ngx_atoi(v, p - v); ... v = p + 1; ls = ngx_array_push(&cycle->listening); if (ls == NULL) { return NGX_ERROR; } ngx_memzero(ls, sizeof(ngx_listening_t)); ls->fd = (ngx_socket_t) s; } } ... ngx_inherited = 1; return ngx_set_inherited_sockets(cycle); }
函數(shù)流程為:
解析環(huán)境變量NGINX_VAR的值,獲取fd存入數(shù)組
fd對應(yīng)的socket設(shè)為ngx_inherited,保存這些socket的信息。
也就是說,新的server壓根就沒重新bind端口listen,這些fd狀態(tài)和值都是新的master進(jìn)程fork時帶過來的,新的master進(jìn)程監(jiān)聽處理繼承來的文件描述符即可,這里比較關(guān)鍵的一點(diǎn)在于listen socket文件描述符通過ENV傳遞。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
利用Nginx反向代理功能解決WEB網(wǎng)站80端口被封的解決方法
大陸的網(wǎng)絡(luò)環(huán)境,都在天朝神獸的制度下讓我等小P民悲劇一片;動不動就拔網(wǎng)線、封機(jī)房;現(xiàn)在更厲害的一招,從網(wǎng)關(guān)封殺你的80端口,一旦被封,網(wǎng)站域名就無法訪問2012-08-08Ubuntu下Nginx配置ThinkPHP的Pathinfo和URl Rewrite模式
這篇文章主要介紹了Ubuntu下Nginx配置ThinkPHP的Pathinfo和URl Rewrite模式,Ubuntu下的配置會有一些不同之處,需要的朋友可以參考下2015-07-07nginx支持codeigniter的pathinfo模式url重寫配置寫法示例
這篇文章主要介紹了nginx支持codeigniter的pathinfo模式url重寫配置寫法示例,pathinfo模式是一種開發(fā)框架都愛用的路由模式,需要的朋友可以參考下2014-07-07Nginx的安裝和多域名配置的實(shí)現(xiàn)方法
這篇文章主要介紹了Nginx的安裝和多域名配置的實(shí)現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09Nginx服務(wù)500:Internal Server Error原因之一
這篇文章主要介紹了Nginx服務(wù)500:Internal Server Error原因之一,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Nginx性能優(yōu)化之Gzip壓縮設(shè)置詳解(最大程度提高頁面打開速度)
這篇文章主要介紹了Nginx性能優(yōu)化之Gzip壓縮設(shè)置詳解(最大程度提高頁面打開速度),需要的朋友可以參考下2022-01-01