Nginx與Tomcat、Client之間請求的長連線配置不一致問題分析解決
本文引自:https://blog.csdn.net/enweitech/article/details/52858612#commentBox,感謝作者的無私整理,對我大有幫助,此處記錄,已被後查。
前些天,線上出現“服務端長連線與客戶端短連線引起Nginx產生大量"TIME_WAIT"狀態的執行緒”的問題分析解決”,這個是由於“服務端使用HTTPs長連線,而客戶端使用短連線”引起。這幾天,發現Nginx與Tomcat之間也存在同樣的問題,原因是兩邊的相關配置引數不一致引起的。(這是心細活!)
先說說服務為什麼使用HTTPs長連線技術?有如下幾個原因:
- 對響應時間要求較高;
- 服務走的是公網,客戶端與服務端的TCP建立的三次握手
- 每個接入方使用的IP就若干個,需要建立的請求連線有限。
使用長連線技術,可以大幅減少TCP頻繁握手的次數,極大提高響應時間;同時,即使使用長連線技術,也不需要消耗很多的系統資源用來快取sockets會話資訊。
以下是在自己電腦上驗證三者之間的長連線請求,連線存活時間都為5min。
【環境】
作業系統:Ubuntu 14.04 LTS
Nginx:1.6.2
Tomcat:7.0.51
JDK:1.7.0_51
Client:HttpClient 4.3.5
【相關配置】
1. Nginx - 反向代理
nginx.conf:
http {
...
##
# 與Client連線的長連線配置
##
# http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
# 設定通過"一個存活長連線"送達的最大請求數(預設是100,建議根據客戶端在"keepalive"存活時間內的總請求數來設定)
# 當送達的請求數超過該值後,該連線就會被關閉。(通過設定為5,驗證確實是這樣)
keepalive_requests 8192;
# http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
# 第一個引數設定"keep-alive客戶端長連線"將在"伺服器端"繼續開啟的超時時間(預設是75秒,建議根據具體業務要求來,但必須要求所有客戶端連線的"Keep-Alive"頭資訊與該值設定的相同(這裡是5分鐘),同時與上游伺服器(Tomcat)的設定是一樣的)
# 可選的第二個引數設定“Keep-Alive: timeout=time”響應頭欄位的值
keepalive_timeout 300s 300s;
...
include /etc/nginx/web_servers.conf;
include /etc/nginx/proxy_params;
}
web_servers.conf:
upstream web_server {
server 127.0.0.1:8080;
# http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
# 連線到上游伺服器的最大併發空閒keepalive長連線數(預設是未設定,建議與Tomcat Connector中的maxKeepAliveRequests值一樣)
# 當這個數被超過時,使用"最近最少使用演算法(LUR)"來淘汰並關閉連線。
keepalive 768;
}
server {
listen 80;
server_name lihg.com www.lihg.com;
location / {
proxy_pass http://web_server;
##
# 與上游伺服器(Tomcat)建立keepalive長連線的配置,可參考上面的keepalive連結裡的"For HTTP"部分
##
# http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
# 設定代理的HTTP協議版本(預設是1.0版本)
# 使用keepalive連線的話,建議使用1.1版本。
proxy_http_version 1.1;
# http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
# 允許重新定義或追加欄位到傳遞給代理伺服器的請求頭資訊(預設是close)
proxy_set_header Connection "";
proxy_redirect off;
}
}
[參考]
2. Tomcat
conf/server.xml:
<!--
maxThreads:由此聯結器建立的最大請求處理執行緒數,這決定可同時處理的最大併發請求數(預設為200)
minSpareThreads:保持執行狀態的最小執行緒數(預設為10)
acceptCount:接收傳入的連線請求的最大佇列長度(預設佇列長度為100)
connectionTimeout:在接收一條連線之後,聯結器將會等待請求URI行的毫秒數(預設為60000,60秒)
maxConnections:在任何給定的時間,伺服器能接收和處理的最大連線數(NIO的預設值為10000)
keepAliveTimeout:在關閉這條連線之前,聯結器將等待另一個HTTP請求的毫秒數(預設使用connectionTimeout屬性值)
maxKeepAliveRequests:在該連線被伺服器關閉之前,可被流水線化的最大HTTP請求數(預設為100)
enableLookups:啟用DNS查詢(預設是DNS查詢被禁用)
compression:聯結器是否啟用HTTP/1.1 GZIP壓縮,為了節省伺服器頻寬
compressionMinSize:指定輸出響應資料的最小大小(預設為2048,2KB)
compressableMimeType:可使用HTTP壓縮的檔案型別
server:覆蓋HTTP響應的Server頭資訊
-->
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="768"
minSpareThreads="512"
acceptCount="128"
connectionTimeout="1000"
maxConnections="1024"
keepAliveTimeout="300000"
maxKeepAliveRequests="768"
enableLookups="false"
URIEncoding="utf-8"
redirectPort="8443"
compression="on" compressionMinSize="1024" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,application/json,application/xml"
server="webserver" />
[參考]
The HTTP Connector - Tomcat 7 Configuration Reference
3. Client
客戶端HTTP "Keep-Alive"實現程式碼,請開啟下一行的連結。
KeepAliveHttpClientsTest -> httpclient-x
【結果驗證】
使用 "sudo netstat -antp | grep 80" 監控與Nginx相關的執行緒狀態
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
協議 接收佇列長度 傳送佇列長度 本地socket的地址和埠號 遠端socket的地址和埠號 socket狀態 程序id/程序名稱
套接字(socket)狀態
ESTABLISHED:含有一條已建立連線(connection)的socket
SYN_SENT:正在積極嘗試建立一條連線的socket
SYN_RECV:接收到來自網路的一個連線請求
FIN_WAIT1:socket已關閉,同時連線正在關閉中
FIN_WAIT2:連線已關閉,同時socket正在等待遠端終端的一個關閉請求
TIME_WAIT:socket正在等待關閉仍然在網路中的處理包
CLOSE:socket未被使用
CLOSE_WAIT:遠端終端已經關閉,等待本地socket關閉
LAST_ACK:遠端終端已經關閉,同時本地socket也關閉了。等待確認包
LISTEN:socket正在監聽傳入的連線
CLOSING:兩邊socket都已關閉,但仍然還沒有我們所需要的傳送資料
UNKNOWN:未知的socket狀態
=====================
單個請求的執行緒狀態
=====================
# 第1次請求,nginx分別與上游伺服器(tomcat)、client互相建立1條連線
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:47272 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker (nginx -> tomcat)
tcp 0 0 127.0.0.1:80 127.0.0.1:53240 ESTABLISHED 1014/nginx: worker (nginx -> client)
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53240 127.0.0.1:80 ESTABLISHED 13845/java (client -> nginx)
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47272 ESTABLISHED 10912/java (tomcat -> nginx)
# 休眠10秒鐘後,發起第2次請求
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:47272 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:80 127.0.0.1:53240 ESTABLISHED 1014/nginx: worker
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53240 127.0.0.1:80 ESTABLISHED 13845/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47272 ESTABLISHED 10912/java
# 超過keepalive存活時間(5min)後,nginx已斷開與上游伺服器(tomcat)的長連線,同時與client連線進入關閉過程
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:80 127.0.0.1:53240 FIN_WAIT2 - (nginx -> client)
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 1 0 127.0.0.1:53240 127.0.0.1:80 CLOSE_WAIT 13845/java (client -> nginx)
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47272 TIME_WAIT - (tomcat -> nginx)
# 休眠7分鐘後,發起第3次請求。nginx與上游伺服器(tomcat)、client重新建立新的長連線(不同的埠號)
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:80 127.0.0.1:53242 ESTABLISHED 1014/nginx: worker (nginx -> client)
tcp 0 0 127.0.0.1:47274 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker (nginx -> tomcat)
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53242 127.0.0.1:80 ESTABLISHED 13845/java (client -> nginx)
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 ESTABLISHED 10912/java (tomcat -> nginx)
# 休眠10秒鐘後,發起第4次請求
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:80 127.0.0.1:53242 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:47274 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53242 127.0.0.1:80 ESTABLISHED 13845/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 ESTABLISHED 10912/java
# 請求剛結束後,nginx斷開與client的長連線,但與上游伺服器(tomcat)的長連線還開啟著,直到超過keepalive存活時間(5min)後才會被關閉。若在keepalive存活時間內再次發起請求,nginx與上游伺服器(tomcat)的長連線會被重用
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:47274 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker (nginx -> tomcat)
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53242 127.0.0.1:80 TIME_WAIT - (client -> nginx)
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 ESTABLISHED 10912/java (tomcat -> nginx)
# 請求結束1分鐘後,client到nginx的TIME_WAIT長連線也被釋放
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:47274 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 ESTABLISHED 10912/java
# 請求結束5分鐘後,nginx斷開與上游伺服器(tomcat)的長連線
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 TIME_WAIT - (tomcat -> nginx)
========================
3個併發請求的執行緒狀態
========================
# 第1次請求,nginx分別與上游伺服器(tomcat)、client互相建立3條連線
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1010/nginx
tcp 0 0 127.0.0.1:80 127.0.0.1:53245 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:47279 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:80 127.0.0.1:53247 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:47281 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:80 127.0.0.1:53246 ESTABLISHED 1014/nginx: worker
tcp 0 0 127.0.0.1:47280 127.0.0.1:8080 ESTABLISHED 1014/nginx: worker
tcp6 0 0 127.0.0.1:8005 :::* LISTEN 10912/java
tcp6 0 0 :::8080 :::* LISTEN 10912/java
tcp6 0 0 127.0.0.1:53247 127.0.0.1:80 ESTABLISHED 13976/java
tcp6 0 0 127.0.0.1:53245 127.0.0.1:80 ESTABLISHED 13976/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47281 ESTABLISHED 10912/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47280 ESTABLISHED 10912/java
tcp6 0 0 127.0.0.1:53246 127.0.0.1:80 ESTABLISHED 13976/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47279 ESTABLISHED 10912/java
tcp6 0 0 127.0.0.1:8080 127.0.0.1:47274 TIME_WAIT -