1. 程式人生 > >關於 Nginx 和 Tomcat 的 http 長連線設定

關於 Nginx 和 Tomcat 的 http 長連線設定

一、為什麼要配置長連線

一個普通的請求是從按照下圖 1->2->3->4 的順序。從瀏覽器到 Nginx,再從 Nginx 到 Tomcat。Tomcat 處理完後,再返回給 Nginx,最後再從 Nginx 返回給瀏覽器。

+--------------+          +--------------+         +--------------+
|              |    1     |              |   2     |              |
|         +--------> |    Nginx     +
-------> | Tomcat | | | 4 | | 3 | | | | <--------+ | <-------+ | +--------------+ +--------------+ +--------------+

在這個請求過程中,一般從瀏覽器到 Nginx 是短連線(就是請求回去後就斷開連線),而 Nginx 到 Tomcat 可以是短連線 或是 長連線(請求返回後,連線還保持一段時間)。為什麼 Nginx 到 Tomcat 之間要設定成長連線呢?因為連線的建立(三次握手)是需要花費一些時間的,如果請求量非常大,不如像連線池一樣儲存一定的連線,當有請求進來時複用一些連線。而且還能減少 time wait,關於 time wait 請參看:

關於 time wait

一般來說,請求方被請求方都可以主動斷開連線。請求方斷開連線時機比較好判斷,如果要請求的內容都完成了,就可以斷開連線了。但被請求方的斷開連線的時機就不好判斷了,因為被請求方不知道請求方會發多少次請求。所以一般被請求方設定的引數相對多一些,例如:長連線在處理多少次請求後斷開、長連線在經過多少秒後斷開等等。

二、Nginx 和 Tomcat 配置

下面說一下瀏覽器->Nginx->Tomcat設定長連線的方法。首先說一下 Nginx 的設定方法。

1,Nginx 設定

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; } web_servers.conf: upstream web_server { server 127.0.0.1:8080; # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive # 連線到 upstream(也就是 Tomcat)的最大併發空閒keepalive長連線數,也就是最多隻能建立 # 這麼多長連線,在這之上再建立連線的話,都是短連線。 #(預設是未設定,建議與Tomcat Connector中的maxKeepAliveRequests值一樣)。 # 如果併發數大於這個數值的話,根據情況可能會建立一些`短連線`來處理請求。可以使用 # `netstat -an|grep TIME|awk '{if($4~"10001") print}'|wc -l`命令來檢視, # 其中`10001`是 Nginx 的埠號,改成自己 Nginx 的埠。 # # 當這個數被超過時,使用"最近最少使用演算法(LUR)"來淘汰並關閉連線。 keepalive 8; } 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。如果把這個 header 設定成空的話,Nginx 會向 Tomcat 傳遞`close`,這樣 Tomcat 就會在處理完請求後關閉連線(這個部分是推理的,沒有實際驗證是 Tomcat 關的,還是 Nginx 關的) proxy_set_header Connection ""; } }

上面每個引數的功能都在註釋中介紹了,下面簡單總結一下使用上注意的地方:

  • keepalive_timeout 和 keepalive_requests 是影響長連線如何關閉的引數。 如果監控長連線在執行中,一部分的連線被關閉了(也就是沒有了),可能是 keepalive_requests 影響的(因為超過了單個長連線的請求最大次數);如果大批量被關閉了,可能是和 keepalive_timeout 有關。
  • upstream 中的 keepalive 是限制對 Tomcat 建立長連線個數的限制。不是不能建立超過這個數的連線,而是建立的長連線數,不能超過這個限制,在這之上可以再建立連線,只不過建立的都是短連線。
  • proxy_http_version 和 proxy_set_header 根據 http 版本(1.0/1.1)設定有所不同,請注意。

2,Tomcat 設定

conf/server.xml:
    <!-- 
        maxThreads:由此聯結器建立的最大請求處理執行緒數,這決定可同時處理的最大併發請求數(預設為200)
        minSpareThreads:保持執行狀態的最小執行緒數(預設為10)
        acceptCount:接收傳入的連線請求的最大佇列長度(預設佇列長度為100)
        connectionTimeout:在接收一條連線之後,聯結器將會等待請求URI行的毫秒數(預設為60000,60秒)
        maxConnections:在任何給定的時間,伺服器能接收和處理的最大連線數(NIO的預設值為10000)
        keepAliveTimeout:在關閉這條連線之前,聯結器將等待另一個HTTP請求的毫秒數(預設使用connectionTimeout屬性值)
        maxKeepAliveRequests:和 Nginx 中的 upstream 中的 keepalive 屬性的功能是一樣的(預設為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" />

這裡要注意的是:

  • maxKeepAliveRequests 屬性和 Nginx 中的 upstream 中的 keepalive 屬性的功能是一樣的,是互相影響的,即達到兩邊中最小的設定後關閉連線。如果 Tomcat 這邊設定的是 5,而 Nginx 那邊設定的是 10 的話,Tomcat 到 5 時就會關閉這個長連線。
  • keepAliveTimeout 屬性和 Nginx 中的 keepalive_timeout 功能是一樣的,也是互相影響的,同上。
  • 如果使用是 Springboot 內嵌 Tomcat 的話,是無法在 application.properties 中設定 maxKeepAliveRequests 屬性的。如果使用 Bean 的方式來設定,其它在 Springboot 中無法設定的屬性也可以使用此方法來設定。另外,如果是在 application.properties 檔案中設定了屬性,也在 Bean 宣告時設定了屬性的話,application.properties 中設定的屬性優先生效。
    @Bean
    public EmbeddedServletContainerFactory servletContainerFactory() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.addConnectorCustomizers(connector -> {
            ((AbstractHttp11Protocol) connector.getProtocolHandler()).setMaxKeepAliveRequests(-1);
            ((AbstractHttp11Protocol) connector.getProtocolHandler()).setConnectionTimeout(20000);
        });
        return factory;
    }

三、測試

1,測試命令

為了測試長連線設定是否生效,可以使用下面兩個命令來測試。

  • ab:用來進行壓力測試的命令。例如:ab -c 8 -n 8000 127.0.0.1:10000/crm/,啟用 8 個非同步處理,傳送總共 8000 個請求。
  • netstat:用來檢視連線的狀態。例如:netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l,檢視 Nginx 到 Tomcat 的正在使用連線。如果想要每秒重新整理,可以這麼做:while [ 1 -eq 1 ] ; do netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l; sleep 1; echo ; done

2,測試準備

為了測試長連線是否起作用,最好把 Tomcat 部分按下面設定:

  • MaxKeepAliveRequests:設定成 -1。這樣就不會因為請求個數,造成連線的斷開重新建立。用 netstat 檢視連線時,看到的就是穩定的狀態。
  • ConnectionTimeout:設定成一個比較大的值,例如 60 秒,這樣不會因為壓測時間長,造成連線的斷開重新建立

Nginx 的 keepalive_requests 和 keepalive_timeout 和 Tomcat 上面兩個值作用相同,最好設定成一樣,以免造成不穩定,而不知道是哪出現的原因。

在按照上面的設定進行測試後,儲存長連線可用後,可以修改上面的引數,來檢視引數的作用和預想結果是否相同。

3,進行測試

1,啟動一個 Terminal,執行while [ 1 -eq 1 ] ; do netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l; sleep 1; echo ; done。這樣就可以檢視連線建立的個數了。顯示如下:

       0

       0

       0

       0

2,再開啟一個 Terminal,使用 ab 命令,進行壓測:ab -c 8 -n 8000 127.0.0.1:10000/crm/
ab 結果如下:

This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 800 requests
Completed 1600 requests
Completed 2400 requests
Completed 3200 requests
Completed 4000 requests
Completed 4800 requests
Completed 5600 requests
Completed 6400 requests
Completed 7200 requests
Completed 8000 requests
Finished 8000 requests


Server Software:        nginx/1.15.1
Server Hostname:        127.0.0.1
Server Port:            10000

Document Path:          /crm/
Document Length:        9 bytes

Concurrency Level:      8
Time taken for tests:   12.685 seconds
Complete requests:      8000
Failed requests:        0
Total transferred:      1304000 bytes
HTML transferred:       72000 bytes
Requests per second:    630.67 [#/sec] (mean)
Time per request:       12.685 [ms] (mean)
Time per request:       1.586 [ms] (mean, across all concurrent requests)
Transfer rate:          100.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   5.0      0     147
Processing:     1   11  16.4      7     316
Waiting:        0   10  15.8      6     310
Total:          1   12  17.5      8     316

Percentage of the requests served within a certain time (ms)
  50%      8
  66%     12
  75%     15
  80%     16
  90%     23
  95%     30
  98%     45
  99%     78
 100%    316 (longest request)

檢視連線狀態命令輸出如下。從下面輸出可以看出,建立了 8 個長連線。連線的個數,應該和 Nginx 中 upstream 中的 keepalive 的引數一致。

       0

       0

       8

       8

       8

       8

       8

       8

       8

3,結過 Nginx 的 keepalive_timeout 時間後,長連線數應該變成 0。

       0

       0

       0

參考