溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Nginx信號(hào)集有什么用

發(fā)布時(shí)間:2021-12-13 09:47:34 來源:億速云 閱讀:122 作者:iii 欄目:服務(wù)器

本篇內(nèi)容介紹了“Nginx信號(hào)集有什么用”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

Nginx信號(hào)操作在日常運(yùn)維中是最常見的,也是非常重要的,這個(gè)環(huán)節(jié)如果出現(xiàn)失誤則可能造成業(yè)務(wù)異常,帶來損失。所以理清楚 Nginx信號(hào)集是非常必要的,能幫助我們更好地處理這些工作。

前言

之前工作時(shí)候,一臺(tái)引流測(cè)試機(jī)器的一個(gè) ngx_lua 服務(wù)突然出現(xiàn)了一些 HTTP/500 響應(yīng),從錯(cuò)誤日志打印的堆棧來看,是不久前新發(fā)布的版本里添加的一個(gè) Lua table 不存在,而有代碼向其進(jìn)行索引導(dǎo)致的。這令人百思不得其解,如果是版本回退導(dǎo)致的,那么為什么使用這個(gè) Lua table 的代碼沒有被回退,偏偏定義這個(gè) table 的代碼被回退了呢?

經(jīng)過排查發(fā)現(xiàn),當(dāng)時(shí) Nginx剛剛完成熱更新操作,舊的 master 進(jìn)程還存在,因?yàn)橐獪?zhǔn)備機(jī)器重啟,先切掉了引流流量(但有些請(qǐng)求還在),同時(shí)系統(tǒng)觸發(fā)了 Nginx-s stop,這才導(dǎo)致了這個(gè)問題。

場(chǎng)景復(fù)現(xiàn)

下面我將使用一個(gè)原生的 Nginx,在我的安裝了 fedora26 的虛擬機(jī)上復(fù)現(xiàn)這個(gè)過程,我使用的 Nginx版本是目前***的 1.13.4

首先啟動(dòng) Nginx:

alex@Fedora26-64: ~/bin_install/nginx ./sbin/nginx alex@Fedora26-64: ~/bin_install/nginx ps auxf | grep nginx alex      6174  0.0  0.0  28876   428 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx alex      6175  0.0  0.2  29364  2060 ?        S    14:35   0:00  \_ nginx: worker process

可以看到 master 和 worker 都已經(jīng)在運(yùn)行。

接著我們向 master 發(fā)送一個(gè) SIGUSR2 信號(hào),當(dāng) Nginx核心收到這個(gè)信號(hào)后,就會(huì)觸發(fā)熱更新。

alex@Fedora26-64: ~/bin_install/nginx kill -USR2 6174 alex@Fedora26-64: ~/bin_install/nginx ps auxf | grep nginx alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx alex      6175  0.0  0.2  29364  2060 ?        S    14:35   0:00  \_ nginx: worker process alex      6209  0.0  0.2  28876  2804 ?        S    14:37   0:00  \_ nginx: master process ./sbin/nginx alex      6213  0.0  0.1  29364  2004 ?        S    14:37   0:00      \_ nginx: worker process

可以看到新的 master 和該 master fork 出來的 worker 已經(jīng)在運(yùn)行了,此時(shí)我們接著向舊 master 發(fā)送一個(gè) SIGWINCH 信號(hào),舊 master 收到這個(gè)信號(hào)后,會(huì)向它的 worker 發(fā)送 SIGQUIT,于是舊 master 的 worker 進(jìn)程就會(huì)退出:

alex@Fedora26-64: ~/bin_install/nginx kill -WINCH 6174 alex@Fedora26-64: ~/bin_install/nginx ps auxf | grep nginx alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx alex      6209  0.0  0.2  28876  2804 ?        S    14:37   0:00  \_ nginx: master process ./sbin/nginx alex      6213  0.0  0.1  29364  2004 ?        S    14:37   0:00      \_ nginx: worker process

此時(shí)只剩下舊的 master,新的 master 和新 master 的 worker 在運(yùn)行,這和當(dāng)時(shí)線上運(yùn)行的情況類似。

接著我們使用 stop 命令:

alex@Fedora26-64: ~/bin_install/nginx ./sbin/nginx -s stop alex@Fedora26-64: ~/bin_install/nginx ps auxf | grep nginx alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx alex      6301  0.0  0.2  29364  2124 ?        S    14:49   0:00  \_ nginx: worker process

我們會(huì)發(fā)現(xiàn),新的 master 和它的 worker 都已經(jīng)退出,而舊的 master 還在運(yùn)行,并產(chǎn)生了 worker 出來。這就是當(dāng)時(shí)線上的情況了。

事實(shí)上,這個(gè)現(xiàn)象和 Nginx自身的設(shè)計(jì)有關(guān):當(dāng)舊的 master 準(zhǔn)備產(chǎn)生 fork 新的 master 之前,它會(huì)把 Nginx.pid 這個(gè)文件重命名為 Nginx.pid.oldbin,然后再由 fork 出來的新的 master 去創(chuàng)建新的 Nginx.pid,這個(gè)文件將會(huì)記錄新 master 的 pid。Nginx認(rèn)為熱更新完成之后,舊 master 的使命幾乎已經(jīng)結(jié)束,之后它隨時(shí)會(huì)退出,因此之后的操作都應(yīng)該由新 master 接管。當(dāng)然,在舊 master 沒有退出的情況下通過向新 master 發(fā)送 SIGUSR2 企圖再次熱更新是無效的,新 master 只會(huì)忽略掉這個(gè)信號(hào)然后繼續(xù)它自己的工作。

問題分析

更不巧的是,我們上面提到的這個(gè) Lua table,定義它的 Lua 文件早在運(yùn)行 init_by_lua 這個(gè) hook 的時(shí)候,就已經(jīng)被 LuaJIT 加載到內(nèi)存并編譯成字節(jié)碼了,那么顯然舊的 master 必然沒有這個(gè) Lua table,因?yàn)樗虞d那部分 Lua 代碼是舊版本的。

而索引該 table 的 Lua 代碼并沒有在 init_by_lua 的時(shí)候使用到,這些代碼都是在 worker 進(jìn)程里被加載起來的,這時(shí)候項(xiàng)目目錄里的代碼都是***的,所以 worker 進(jìn)程加載的都是***的代碼,如果這些 worker 進(jìn)程處理到相關(guān)的請(qǐng)求,就會(huì)出現(xiàn) Lua 運(yùn)行時(shí)錯(cuò)誤,外部表現(xiàn)則是對(duì)應(yīng)的 HTTP 500。

吸收了這個(gè)教訓(xùn)之后,我們需要更加合理地關(guān)閉我們的 Nginx服務(wù)。 所以一個(gè)更加合理的 Nginx服務(wù)啟動(dòng)關(guān)閉腳本是必需的,網(wǎng)上流傳的一些腳本并沒有對(duì)這個(gè)現(xiàn)象做處理,我們更應(yīng)該參考 Nginx官方提供的腳本。

stop() {     echo -n $"Stopping $prog: "     killproc $prog -QUIT     retval=$?     echo     [ $retval -eq 0 ] && rm -f $lockfile     return $retval }

這段代碼引自 Nginx官方的 /etc/init.d/Nginx[1] 。

Nginx信號(hào)集

接下來我們來全面梳理下 Nginx信號(hào)集,這里不會(huì)涉及到源碼細(xì)節(jié),感興趣的同學(xué)可以自行閱讀相關(guān)源碼。

我們有兩種方式來向 master 進(jìn)程發(fā)送信號(hào),一種是通過 Nginx-s signal 來操作,另一種是通過 kill 命令手動(dòng)發(fā)送。

***種方式的原理是,產(chǎn)生一個(gè)新進(jìn)程,該進(jìn)程通過 Nginx.pid 文件得到 master 進(jìn)程的 pid,然后把對(duì)應(yīng)的信號(hào)發(fā)送到 master,之后退出,這種進(jìn)程被稱為 signaller。

第二種方式要求我們了解 Nginx-s signal 到真實(shí)信號(hào)的映射。下表是它們的映射關(guān)系:

Nginx信號(hào)集有什么用

stop vs quit

stop 發(fā)送 SIGTERM 信號(hào),表示要求強(qiáng)制退出,quit 發(fā)送 SIGQUIT,表示優(yōu)雅地退出。 具體區(qū)別在于,worker 進(jìn)程在收到 SIGQUIT 消息(注意不是直接發(fā)送信號(hào),所以這里用消息替代)后,會(huì)關(guān)閉監(jiān)聽的套接字,關(guān)閉當(dāng)前空閑的連接(可以被搶占的連接),然后提前處理所有的定時(shí)器事件,***退出。沒有特殊情況,都應(yīng)該使用 quit 而不是 stop。

reload

master 進(jìn)程收到 SIGHUP 后,會(huì)重新進(jìn)行配置文件解析、共享內(nèi)存申請(qǐng),等一系列其他的工作,然后產(chǎn)生一批新的 worker 進(jìn)程,***向舊的 worker 進(jìn)程發(fā)送 SIGQUIT 對(duì)應(yīng)的消息,最終無縫實(shí)現(xiàn)了重啟操作。

reopen

master 進(jìn)程收到 SIGUSR1 后,會(huì)重新打開所有已經(jīng)打開的文件(比如日志),然后向每個(gè) worker 進(jìn)程發(fā)送 SIGUSR1 信息,worker 進(jìn)程收到信號(hào)后,會(huì)執(zhí)行同樣的操作。reopen 可用于日志切割,比如 Nginx官方就提供了一個(gè)方案:

$ mv access.log access.log.0 $ kill -USR1 `cat master.nginx.pid` $ sleep 1 $ gzip access.log.0    # do something with access.log.0

這里 sleep 1 是必須的,因?yàn)樵?master 進(jìn)程向 worker 進(jìn)程發(fā)送 SIGUSR1 消息到 worker 進(jìn)程真正重新打開 access.log 之間,有一段時(shí)間窗口,此時(shí) worker 進(jìn)程還是向文件 access.log.0 里寫入日志的。通過 sleep 1s,保證了 access.log.0 日志信息的完整性(如果沒有 sleep 而直接進(jìn)行壓縮,很有可能出現(xiàn)日志丟失的情況)。

hot update

某些時(shí)候我們需要進(jìn)行二進(jìn)制熱更新,Nginx在設(shè)計(jì)的時(shí)候就包含了這種功能,不過無法通過 Nginx提供的命令行完成,我們需要手動(dòng)發(fā)送信號(hào)。

通過上面的問題復(fù)現(xiàn),大家應(yīng)該已經(jīng)了解到如何進(jìn)行熱更新了,我們首先需要給當(dāng)前的 master 進(jìn)程發(fā)送 SIGUSR2,之后 master 會(huì)重命名 Nginx.pid 到 Nginx.pid.oldbin,然后 fork 一個(gè)新的進(jìn)程,新進(jìn)程會(huì)通過 execve 這個(gè)系統(tǒng)調(diào)用,使用新的 NginxELF 文件替換當(dāng)前的進(jìn)程映像,成為新的 master 進(jìn)程。新 master 進(jìn)程起來之后,就會(huì)進(jìn)行配置文件解析等操作,然后 fork 出新的 worker 進(jìn)程開始工作。

接著我們向舊的 master 發(fā)送 SIGWINCH 信號(hào),然后舊的 master 進(jìn)程則會(huì)向它的 worker 進(jìn)程發(fā)送 SIGQUIT 信息,從而使得 worker 進(jìn)程退出。向 master 進(jìn)程發(fā)送 SIGWINCH 和 SIGQUIT 都會(huì)使得 worker 進(jìn)程退出,但是前者不會(huì)使得 master 進(jìn)程也退出。

***,如果我們覺得舊的 master 進(jìn)程使命完成,就可以向它發(fā)送 SIGQUIT 信號(hào),讓其退出了。

worker 進(jìn)程如何處理來自 master 的信號(hào)消息

實(shí)際上,master 進(jìn)程再向 worker 進(jìn)程通訊,不是使用 kill 函數(shù),而是使用了通過管道實(shí)現(xiàn)的 Nginxchannel,master 進(jìn)程向管道一端寫入信息(比如信號(hào)信息),worker 進(jìn)程則從另外一端收取信息,Nginxchannel 事件,在 worker 進(jìn)程剛剛起來的時(shí)候,就被加入事件調(diào)度器中(比如 epoll,kqueue),所以當(dāng)有數(shù)據(jù)從 master 發(fā)來時(shí),即可被事件調(diào)度器通知到。

Nginx這么設(shè)計(jì)是有理由的,作為一個(gè)優(yōu)秀的反向代理服務(wù)器,Nginx追求的就是***的高性能,而 signal handler 會(huì)中斷 worker 進(jìn)程的運(yùn)行,使得所有的事件都被暫停一個(gè)時(shí)間窗口,這對(duì)性能是有一定損失的。

很多人可能會(huì)認(rèn)為當(dāng) master 進(jìn)程向 worker 進(jìn)程發(fā)送信息之后,worker 進(jìn)程立刻會(huì)有對(duì)應(yīng)操作回應(yīng),然而 worker 進(jìn)程是非常繁忙的,它不斷地處理著網(wǎng)絡(luò)事件和定時(shí)器事件,當(dāng)調(diào)用 Nginxchannel 事件的 handler 之后,Nginx僅僅只是處理了一些標(biāo)志位。真正執(zhí)行這些動(dòng)作是在一輪事件調(diào)度完成之后。所以這之間存在一個(gè)時(shí)間窗口,尤其是業(yè)務(wù)復(fù)雜且流量巨大的時(shí)候,這個(gè)窗口就有可能被放大,這也就是為什么 Nginx官方提供的日志切割方案里要求 sleep 1s 的原因。

當(dāng)然,我們也可以繞過 master 進(jìn)程,直接向 worker 進(jìn)程發(fā)送信號(hào),worker 可以處理的信號(hào)有

Nginx信號(hào)集有什么用

“Nginx信號(hào)集有什么用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI