nginx 平滑重啟的實(shí)現(xiàn)方法
一、背景
在服務(wù)器開(kāi)發(fā)過(guò)程中,難免需要重啟服務(wù)加載新的代碼或配置,如果能夠保證server重啟的過(guò)程中服務(wù)不間斷,那重啟對(duì)于業(yè)務(wù)的影響可以降為0。最近調(diào)研了一下nginx平滑重啟,覺(jué)得很有意思,記錄下來(lái)供有興趣的同學(xué)查閱。
二、重啟流程
- 重啟意味著新舊接替,在交接任務(wù)的過(guò)程中勢(shì)必會(huì)存在新舊server并存的情形,因此,重啟的流程大致為:
- 啟動(dòng)新的server
- 新舊server并存,兩者共同處理請(qǐng)求,提供服務(wù)
- 舊的server處理完所有的請(qǐng)求之后優(yōu)雅退出
- 這里,最主要的問(wèn)題在于如何保證新舊server可以并存,如果重啟前后的server端口一致,如何保證兩者可以監(jiān)聽(tīng)同一端口。
三、nginx實(shí)現(xiàn)
為了驗(yàn)證nginx平滑重啟,筆者首先嘗試nginx啟動(dòng)的情形下再次開(kāi)啟一個(gè)新的server實(shí)例,結(jié)果如圖:

很明顯,重新開(kāi)啟server實(shí)例是行不通的,原因在于新舊server使用了同一個(gè)端口80,在未開(kāi)始socket reuseport選項(xiàng)復(fù)用端口時(shí),bind系統(tǒng)調(diào)用會(huì)出錯(cuò)。nginx默認(rèn)bind重試5次,失敗后直接退出。而nginx需要監(jiān)聽(tīng)I(yíng)PV4地址0.0.0.0和IPV6地址[::],故圖中打印出10條emerg日志。
接下來(lái)就開(kāi)始嘗試平滑重啟命令了,一共兩條命令:
kill -USR2 `cat /var/run/nginx.pid` kill -QUIT `cat /var/run/nginx.pid.oldbin`
第一條命令是發(fā)送信號(hào)USR2給舊的master進(jìn)程,進(jìn)程的pid存放在/var/run/nginx.pid文件中,其中nginx.pid文件路徑由nginx.conf配置。
第二條命令是發(fā)送信號(hào)QUIT給舊的master進(jìn)程,進(jìn)程的pid存放在/var/run/nginx.pid.oldbin文件中,隨后舊的master進(jìn)程退出。
那么問(wèn)題來(lái)了,為什么舊的master進(jìn)程的pid存在于兩個(gè)pid文件之中?事實(shí)上,在發(fā)送信號(hào)USR2給舊的master進(jìn)程之后,舊的master進(jìn)程將pid重命名,原先的nginx.pid文件rename成nginx.pid.oldbin。這樣新的master進(jìn)行就可以使用nginx.pid這個(gè)文件名了。
先執(zhí)行第一條命令,結(jié)果如圖:

不錯(cuò),新舊master和worker進(jìn)程并存了。 再來(lái)第二條命令,結(jié)果如圖:

如你所見(jiàn),舊的master進(jìn)程8527和其worker進(jìn)程全部退出,只剩下新的master進(jìn)程12740。
不由得產(chǎn)生困惑,為什么手動(dòng)開(kāi)啟一個(gè)新的實(shí)例行不通,使用信號(hào)重啟就可以達(dá)到。先看下nginx log文件:

除了之前的錯(cuò)誤日志,還多了一條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)聽(tīng)的所有fd,拷貝至新master進(jìn)程的env環(huán)境變量NGINX_VAR。
- rename重命名pid文件
- ngx_execute函數(shù)fork子進(jìn)程,execve執(zhí)行命令行啟動(dòng)新的server。
- 在server啟動(dòng)流程之中,涉及到環(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對(duì)應(yīng)的socket設(shè)為ngx_inherited,保存這些socket的信息。
也就是說(shuō),新的server壓根就沒(méi)重新bind端口listen,這些fd狀態(tài)和值都是新的master進(jìn)程fork時(shí)帶過(guò)來(lái)的,新的master進(jìn)程監(jiān)聽(tīng)處理繼承來(lái)的文件描述符即可,這里比較關(guān)鍵的一點(diǎn)在于listen socket文件描述符通過(guò)ENV傳遞。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解nginx實(shí)現(xiàn)https網(wǎng)站設(shè)置
這篇文章主要介紹了詳解nginx實(shí)現(xiàn)https網(wǎng)站設(shè)置,詳細(xì)的介紹了HTTPS簡(jiǎn)介和證書(shū)生成等,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Nginx基礎(chǔ)學(xué)習(xí)之realip模塊的使用方法
這篇文章主要給大家介紹了關(guān)于Nginx基礎(chǔ)學(xué)習(xí)之realip模塊使用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Nginx具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Nginx反向代理location和proxy_pass配置規(guī)則詳細(xì)總結(jié)
nginx代理訪問(wèn)很好用,但是好多人不清楚location和proxy_pass組合在一起使用時(shí)訪問(wèn)的url被代理的url真實(shí)地址是什么,下面這篇文章主要給大家介紹了關(guān)于Nginx反向代理location和proxy_pass配置規(guī)則的相關(guān)資料,需要的朋友可以參考下2022-09-09
Nginx實(shí)現(xiàn)三種常見(jiàn)的虛擬主機(jī)配置方法
Nginx 是一款高性能的 Web 服務(wù)器,支持多種虛擬主機(jī)配置方式,能夠根據(jù)域名、IP 或端口區(qū)分不同的站點(diǎn),這種靈活性讓 Nginx 成為搭建多站點(diǎn)服務(wù)的首選工具,本文將帶你一步步實(shí)現(xiàn)三種常見(jiàn)的虛擬主機(jī)配置方法,需要的朋友可以參考下2025-03-03
如何利用nginx處理DDOS進(jìn)行系統(tǒng)優(yōu)化詳解
防御DDOS是一個(gè)系統(tǒng)工程,攻擊花樣多,防御的成本高瓶頸多,防御起來(lái)即被動(dòng)又無(wú)奈,下面這篇文章主要給大家介紹了關(guān)于如何利用nginx處理DDOS進(jìn)行系統(tǒng)優(yōu)化的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-08-08
Nginx通過(guò)geo模塊設(shè)置白名單的例子
今天小編就為大家分享一篇Nginx通過(guò)geo模塊設(shè)置白名單的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08
Nginx+Keepalive實(shí)現(xiàn)高可用負(fù)載均衡
在互聯(lián)網(wǎng)的高速發(fā)展下,網(wǎng)站的穩(wěn)定性與性能成為了企業(yè)核心競(jìng)爭(zhēng)力之一,負(fù)載均衡作為提高網(wǎng)站可用性和處理能力的關(guān)鍵技術(shù),被廣泛應(yīng)用于互聯(lián)網(wǎng)架構(gòu)中,本文將介紹如何利用 Nginx 的 Keepalive 功能來(lái)實(shí)現(xiàn)高可用的負(fù)載均衡策略,需要的朋友可以參考下2024-12-12
upstream模塊中常用options選項(xiàng)講解
這篇文章主要為大家介紹了upstream模塊中常用options選項(xiàng)講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

