ClientAbortException 問題分析

OkidoGreen發表於2020-04-05

https://luyiisme.github.io/2017/02/18/conn-abort-problom/

https://yq.aliyun.com/articles/560413

基礎知識:

預設 proxy_ignore_client_abort 是關閉的,此時在請求過程中如果客戶端端主動關閉請求或者客戶端網路斷掉,那麼 Nginx 會記錄 499,同時 request_time 是 「後端已經處理」的時間,而 upstream_response_time 為 “-“ (已驗證)。

如果使用了 proxy_ignore_client_abort on ;

那麼客戶端主動斷掉連線之後,Nginx 會等待後端處理完(或者超時),然後 記錄 「後端的返回資訊」 到日誌。所以,如果後端 返回 200, 就記錄 200 ;如果後端放回 5XX ,那麼就記錄 5XX 。

如果超時(預設60s,可以用 proxy_read_timeout 設定),Nginx 會主動斷開連線,記錄 504。

話題:

基於WEB層面來討論分析問題,引出tcp層面的問題本質;

背景:

筆者常被問到 ClientAbortException 問題,正好就一種場景的分析來簡單分析下(其他場景地分析也較為類似)。下面討論的是使用 nginx 反向代理 tomcat java專案時,有時java應用會發現頻繁列印 ClientAbortException 錯誤日誌問題。

  • 1、什麼情況下會出現“ClientAbortException: java.net.socketException: Broken pipe”日誌?

客戶端非正常(標準握手協議)退出連線,體現在http請求,可能是使用者等待頁面響應過程中,關閉瀏覽器,或停止了請求。

nginx 加上這個頭效果一樣,處理客戶端非正常退出情況,保持與應用伺服器端的conn,讓其轉發的伺服器端還是可以繼續對其寫;

  • 3、為什麼有的應用報這個問題,有的 nginx 無 proxy_ignore_client_abort 確沒有打錯誤日誌?

tomcat 的專門 coyoteWriter 的寫方法,已經替你忽略掉寫的時候發生的異常。因此,異常其實是產生的,只不過被吞了。但為什麼有的還列印出日誌呢?這些應用是直接使用response.getWriter()拿到的,而拿取 response.getOutputStream() 來構的writer,則沒有忽略異常,而是選擇了丟擲,因此你看到了這個問題;那麼更一般的情況下,我們會在 mvc 框架的 controller 裡(或者 servlet 裡,或其他場合),直接操作 response.getOutputStream() 來進行業務輸出響應流操作,異常會同樣丟擲

備註:一旦這個異常發生,如果你還嘗試去攔截處理的這個異常,並再次返回響應,但是因為你操作過了response.getOutputStream了,所有會再拋異常說...response has been commited! 那麼解決的思路就是,加 nginx 的頭,或者在 controller 裡攔截這種 ClientAbortException,並忽略。

  • 4、我自己在分析這個異常產生時,發現有的情況下套接字輸出會拋ClientAbortException,有的時候提交正常?

分析發現,對一個對端已經關閉的socket呼叫兩次write,即第二次嘗試寫才會生成SIGPIPE訊號(why? 因為tcp的四次揮手), 該訊號預設結束程式,會丟擲 Broken pipe 的 exception. 客戶端自己關閉 socket,但是伺服器端不知道,然後向客戶端寫一次資料,這時客戶端會回應伺服器端RST報文(Reset the connection), (如果你此時伺服器端,嘗試讀就會看到這個錯誤報文)。我們這裡是flush buffer,繼續寫第二次,那麼此時就會拋異常:ClientAbortException 而且cause by broken pipe;

  • 5、最後,有些應用正確配置了’proxy_ignore_client_abort on;’,但是還是會拋上述異常?

比較常見的情況是,被轉發的請求處理耗時過久,已經超過了‘proxy_read_timeout’的時限,預設 60s。這種情況下「轉發連線」已經讀超時了,’proxy_ignore_client_abort‘當然也就沒有效果了。’proxy_ignore_client_abort‘是防止 Nginx 前置的連線斷掉請求,而對Nginx 後置(連線實際應用伺服器)「轉發連線」是不能防止的。 解決方法:根本是解決應用該慢請求問題。 那麼怎麼確認這個問題呢?可以看 Nginx 的錯誤日誌,裡面會有錯誤資訊 ...[error] 14345#0: *5 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 10.15...

總結:

(1)建議使用nginx的應用,都加上proxy_ignore_client_abort on;(加在Location的配置區域內)

(2)這個異常如果在到JAVA應用伺服器,除了異常fillstacktrace,和列印日誌io消耗,其他對業務本身服務是沒什麼影響的

 

轉:

Nginx 499 原因:

最近發現伺服器上出現很多499的錯誤,出現499錯誤的原因是客戶端關閉了連線,在我這篇文章:服務端在執行時中途關閉瀏覽器退出之後php還會繼續執行嗎?個人實踐實驗得到結果(http://www.04007.cn/article/356.html )裡,測試中斷時,伺服器nginx的日誌就是499記錄。nginx報49*錯誤

400-499 用於指出客戶端的錯誤。 (自己電腦這邊的問題) 自己電腦這邊的問題) 

495 :https certificate error
496 :https no certificate
497 :http to https
498 :canceled
499 :client has closed connection
    即499錯誤是客戶端主動斷開了連線。 如何關閉報499這個錯誤碼呢?可以通過配置:proxy_ignore_client_abort來處理。
    proxy_ignore_client_abort:是否開啟proxy忽略客戶端中斷。即如果此項設定為on開啟,則伺服器會忽略客戶端中斷,一直等著代理服務執行返回。並且如果執行沒有發生錯誤,記錄的日誌是200日誌。如果超時則會記錄504。如果設定為off,則客戶端中斷後伺服器端nginx立即記錄499日誌,但要注意,此時代理端的PHP程式會依然繼續執行。可檢視上面寫的那篇文章。
    nginx的proxy_ignore_client_abort預設是關閉的,即請求過程中如果客戶端端主動關閉請求或者客戶端網路斷掉,那麼Nginx會記錄499。所以如果不想看到499報錯,可以修改配置:
proxy_ignore_client_abort on ;
    這樣來說,499錯誤並不是一個問題,如果出現了大量的499的話,需要考慮為什麼發生了這麼多的客戶端中斷的問題。

    另外需要注意的一項是:proxy_ignore_client_abort配置只會對代理的配置,如果請求的是當前nginx伺服器,直接執行PHP程式返回。則設定proxy_ignore_client_abort為on也不會起作用,仍會是記錄499日誌。proxy_ignore_client_abort的配置是配置在代理處理時用。如下:

 

location =/b.php { 
    proxy_ignore_client_abort   on; 
    proxy_pass  http://service_backends;}

相關文章