您好,登錄后才能下訂單哦!
本篇內容主要講解“nginx怎么實現平滑重啟”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“nginx怎么實現平滑重啟”吧!
一、背景
在服務器開發過程中,難免需要重啟服務加載新的代碼或配置,如果能夠保證server重啟的過程中服務不間斷,那重啟對于業務的影響可以降為0。
二、重啟流程
重啟意味著新舊接替,在交接任務的過程中勢必會存在新舊server并存的情形,因此,重啟的流程大致為:
啟動新的server
新舊server并存,兩者共同處理請求,提供服務
舊的server處理完所有的請求之后優雅退出
這里,最主要的問題在于如何保證新舊server可以并存,如果重啟前后的server端口一致,如何保證兩者可以監聽同一端口。
三、nginx實現
為了驗證nginx平滑重啟,筆者首先嘗試nginx啟動的情形下再次開啟一個新的server實例,結果如圖:
很明顯,重新開啟server實例是行不通的,原因在于新舊server使用了同一個端口80,在未開始socket reuseport選項復用端口時,bind系統調用會出錯。nginx默認bind重試5次,失敗后直接退出。而nginx需要監聽ipv4地址0.0.0.0和ipv6地址[::],故圖中打印出10條emerg日志。
接下來就開始嘗試平滑重啟命令了,一共兩條命令:
kill -usr2 `cat /var/run/nginx.pid` kill -quit `cat /var/run/nginx.pid.oldbin`
第一條命令是發送信號usr2給舊的master進程,進程的pid存放在/var/run/nginx.pid文件中,其中nginx.pid文件路徑由nginx.conf配置。
第二條命令是發送信號quit給舊的master進程,進程的pid存放在/var/run/nginx.pid.oldbin文件中,隨后舊的master進程退出。
那么問題來了,為什么舊的master進程的pid存在于兩個pid文件之中?事實上,在發送信號usr2給舊的master進程之后,舊的master進程將pid重命名,原先的nginx.pid文件rename成nginx.pid.oldbin。這樣新的master進行就可以使用nginx.pid這個文件名了。
先執行第一條命令,結果如圖:
不錯,新舊master和worker進程并存了。 再來第二條命令,結果如圖:
如你所見,舊的master進程8527和其worker進程全部退出,只剩下新的master進程12740。
不由得產生困惑,為什么手動開啟一個新的實例行不通,使用信號重啟就可以達到。先看下nginx log文件:
除了之前的錯誤日志,還多了一條notice,意思就是繼承了sockets,fd值為6,7。 隨著日志翻看nginx源碼,定位到nginx.c/ngx_exec_new_binary函數之中,
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; }
函數的流程為
將舊的master進程監聽的所有fd,拷貝至新master進程的env環境變量nginx_var。
rename重命名pid文件
ngx_execute函數fork子進程,execve執行命令行啟動新的server。
在server啟動流程之中,涉及到環境變量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); }
函數流程為:
解析環境變量nginx_var的值,獲取fd存入數組
fd對應的socket設為ngx_inherited,保存這些socket的信息。
也就是說,新的server壓根就沒重新bind端口listen,這些fd狀態和值都是新的master進程fork時帶過來的,新的master進程監聽處理繼承來的文件描述符即可,這里比較關鍵的一點在于listen socket文件描述符通過env傳遞。
到此,相信大家對“nginx怎么實現平滑重啟”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。