問題排查:nginx能跑,但是隻能跑一會,不能跑多了

2023-08-29 12:00:49

背景

上週都是查測試環境的問題,比如,我上一篇寫的問題排查:nginx的反向代理感覺失效了一樣 ,就是說這個事的。在文章裡,最終查到是nginx的全連線佇列滿了(每個監聽埠有個佇列,完成三次握手的請求會進入這個監聽埠的全連線佇列,佇列大小是隻有128,比較小),我當時的解決方式,是把佇列大小調大到了512,然後重啟nginx,果然功能正常了。

但當時沒有去再多問一個為什麼:為什麼nginx的全連線佇列會滿呢?而且這個功能雖然用得少,但是之前應該都好好的,突然就抽風了?

nginx的全連線佇列,簡單,它就是一個佇列,網路協定棧負責將完成三次握手的連線放到該佇列,算是生產者;那麼,消費者是誰呢,自然是我們的應用程式,應用程式每次呼叫accept的時候,就會從佇列中獲取一個連線,獲取了之後,佇列也就空出來了。

我翻了下accept這個syscall的手冊(man accept),https://linux.die.net/man/2/accept:

It extracts the first connection request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket.

這塊是說,是從佇列頭部獲取,先進先出;獲取到了之後,給呼叫方返回一個檔案描述符,指向該socket。

If no pending connections are present on the queue, and the socket is not marked as nonblocking, accept() blocks the caller until a connection is present. If the socket is marked nonblocking and no pending connections are present on the queue, accept() fails with the error EAGAIN or EWOULDBLOCK.

In order to be notified of incoming connections on a socket, you can use select(2) or poll(2). A readable event will be delivered when a new connection is attempted and you may then call accept() to get a socket for that connection. Alternatively, you can set the socket to deliver SIGIO when activity occurs on a socket; see socket(7) for details.

這裡又提到,如果佇列裡沒有連線,且該監聽socket不是非阻塞的,那麼accpet呼叫會阻塞;如果socket是非阻塞的,那麼此時就會返回錯誤:EAGAIN or EWOULDBLOCK。

為了在新的連線到達時(進入佇列時)能夠得到提醒,我們可以使用select或者poll機制。當新連線到達時,會有一個可讀事件傳送給程式,此時再去呼叫accept就肯定能獲取到連線,而不會阻塞。

我發現,檔案寫得還是非常清楚,很有價值,總的來說,應用程式就是那個消費者,佇列會滿,那肯定是消費者有問題,消費者是nginx,nginx能有啥問題呢,還真不知道,我當時以為猜測可能是請求處理有點慢吧,我把佇列給你加大了,應該就沒事了。

當天下午有事,也沒管了,結果第二天,測試同事說,又不行了,我去看了下佇列(ss -lnt|grep 8088),512的佇列,又滿了。

排查

nginx 紀錄檔

說實話,當時真的有點無語,因為手裡還有別的事,也不想一直耗在這個事情上,但是,我們也不能阻礙測試同事工作開展,這也是份內事。

因為當時這個nginx,除了監聽了我們關注的8088埠,還監聽了8082 8080等埠,我以為是被其他埠影響到了,也看了下這兩個埠的佇列長度,ss -lnt|grep 8082,發現佇列長度是0,只有我們8088埠是滿的。

我當時想過,怎麼看看這些佇列裡都是啥內容,但感覺意義不大,無非就是那些socket,所以沒有深入去看。

然後又去看nginx紀錄檔,首先奇怪的一點是,我是週四的下午2點多處理完佇列長度,重啟nginx,然後access紀錄檔也只打到了2點20分左右就沒了,error紀錄檔也是。

另外,error紀錄檔倒是列印了一些內容,就是有很多獲取圖片的請求,處理失敗了,就是說open xxx.jpg failed之類的,我當時想著估計是圖片找不到吧,404啥的,這種見多了,一般也不怎麼理。

我還是去看了下那個檔案,結果,ssh卡死了:

[root@168assi log]# ll /hxspace/production/files/unsafty/hxtg_dd
^C^C^C^C

我之前就是感覺這個機器有點問題,經常各種命令都卡死,現在ll都不行了。。

常規檢查

然後就是開始檢查系統資源,首先是top,按cpu排序和按記憶體排序,都沒發現很離譜的佔用很高的應用。

但是,top中看到1、5/15分鐘的平均負載基本在12左右,我們是8核,按我理解,12/8=1.5,那基本上,每個核上有1個執行緒在執行,還有0.5個執行緒在等待執行。

感覺是有點高,但我不太擅長排查cpu問題,也沒有再深入。

然後free -h檢查了下記憶體,空閒記憶體也還很多,8個g,感覺沒問題。

然後是磁碟,df一執行,結果直接卡死了,也不知道咋回事,之前就是感覺這機器有問題,之前lsof命令也是執行卡死。

當時都懷疑是不是磁碟有問題,還是機器哪裡有問題,要不要換臺機器部署算了。

核心紀錄檔

再檢查下核心紀錄檔吧,dmesg -T,瀏覽了下。沒看到nginx的相關紀錄檔。但是看到一些亂七八糟的內容:

[五 8月 25 11:06:58 2023] nfs: server 10.0.251.111 not responding, timed out
[五 8月 25 11:06:58 2023] nfs: server 10.0.251.111 not responding, timed out
[五 8月 25 11:12:04 2023] nfs: server 10.0.251.111 not responding, still trying

然後又去看了/var/log/messages,也沒啥特別的。

strace檢視df阻塞點

之前不是執行df,把我ssh卡死了嘛,我這次想看看到底是哪裡卡住了,於是用strace跟蹤了下。

[root@168assi servers]# strace -q -f -s 500 df

結果,這個命令卡在了下面這個地方:

stat("/run/user/42", {st_mode=S_IFDIR|0700, st_size=180, ...}) = 0
stat("/sys/fs/fuse/connections", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
stat("/run/user/42/gvfs", 0x7ffe818bf1a0) = -1 EACCES (Permission denied)
stat("/run/user/0", {st_mode=S_IFDIR|0700, st_size=40, ...}) = 0
stat("/hxspace/production/files/unsafty/hxtg_dd", ^C^C^C^C

可以看到我狂按ctrl c嗎,就是因為卡死了

檢查掛載

這個目錄有問題啊,因為這個目錄我還是很有印象的,nginx讀圖片就是在這個目錄下讀,然後還失敗了。

然後我想起來那個核心紀錄檔裡nfs報錯的相關資訊,我他麼感覺,這個目錄是不是網路儲存啊,就是掛載到了nfs server。

然後我執行mount -l看了下,果然找到了nfs的蹤跡:

...
10.0.251.111:/home/app on /hxspace/production/files/unsafty/hxtg_dd type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=10.0.251.192,local_lock=none,addr=10.0.251.111)

這個掛載也就是說,/hxspace/production/files/unsafty/hxtg_dd掛載的是nfs伺服器10.0.251.111的/home/app目錄.

然後,我ping了下這個機器,不通。此時,基本確定就是這個問題了。

解決

所以,我猜測,df、lsof等各種要遍歷資料夾的命令都卡死了,那估計nginx去讀取那個目錄下的檔案,也卡死了,worker如果卡死,那麼nginx負責accept的程序,應該就會停止去accept連線,進而導致全連線佇列滿。

但是,怎麼驗證是這個問題呢?

我們把nginx.conf中讀取那個目錄的設定先刪了,然後再試,果然,這次全連線佇列再沒有積壓了,肉眼看到的一直都是0.

但是,把別人設定刪了也不合適,那看看能不能恢復nfs吧?

我們先去找伺服器管理的同事,結果跟我們說,這個nfs伺服器已經被回收了,果然,主打一個混亂。

行吧,反正是測試環境,既然nfs伺服器沒了,我們也沒打算再搭一個,後邊問到相關業務同事,已經沒在用這臺機器了,那就不用顧忌他們了,那這個掛載就得想辦法去掉,不然各種命令都卡死,實在不爽。

去掉mount掛載,採用的umount命令,結果執行失敗,提示device is busy。

後邊查網上文章,需要ps aux找出狀態為D的(也就是TASK_UNINTERRUPTIBLE,是一種不可中斷的狀態),我發現查出來的基本就是我之前卡死的那些df等命令。

kill 9強殺這些程序後,再去執行取消掛載,成功了:

[root@168assi ~]# umount -f -v 10.0.251.111:/home/app
/hxspace/production/files/unsafty/hxtg_dd: nfs4 mount point detected
/hxspace/production/files/unsafty/hxtg_dd: umounted

擴充套件:nfs

我們這裡作為nfs使用者端,掛載時,其實是可以指定選項的,其中有個選項如下:

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/deployment_guide/s1-nfs-client-config-options

hard or soft — Specifies whether the program using a file via an NFS connection should stop and wait (hard) for the server to come back online, if the host serving the exported file system is unavailable, or if it should report an error (soft).

If hard is specified, the user cannot terminate the process waiting for the NFS communication to resume unless the intr option is also specified.

If soft is specified, the user can set an additional timeo= option, where specifies the number of seconds to pass before the error is reported.

也就是可以指定hard或soft,hard就是nfs伺服器端如果像我們這種情況,直接ip都不通了,使用者端會一直死等,直到nfs server上線;而soft就是會超時,然後報錯。

我們這邊,這次就是掛載用了hard模式,不知道怎麼考慮的。

10.0.251.111:/home//app on /hxspace/production/files/unsafty/hxtg_dd type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=10.0.251.192,local_lock=none,addr=10.0.251.111)

總結

這次遇到的這個問題,說白了,最終就是因為關閉了nfs伺服器的問題,也就能解釋,為啥以前沒問題了。

另外,我在寫本篇的時候,感覺幾年前好像研究過全連線佇列的問題,去找了下之前文章,發現現象竟然是一模一樣,幾年下來忘得好乾淨,果然是唯手熟爾:

https://www.cnblogs.com/grey-wolf/p/10999342.html

參考文章

https://www.cnblogs.com/yuboyue/archive/2011/07/18/2109867.html

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/deployment_guide/s1-nfs-client-config-options

https://superuser.com/questions/1562153/shutdown-of-rhel-6-9-7-7-nfs-client-hangs-when-nfs-server-has-become-unavailab

https://unix.stackexchange.com/questions/328746/how-can-i-monitor-the-length-of-the-accept-queue (檢視accept佇列內容)

https://krisnova.net/posts/linux-accept-queues/