1. 程式人生 > >Tomcat-connector的微調(1): acceptCount引數

Tomcat-connector的微調(1): acceptCount引數

對於acceptCount這個引數,含義跟字面意思並不是特別一致(個人感覺),容易跟maxConnections,maxThreads等引數混淆;實際上這個引數在tomcat裡會被對映成backlog:

static {
    replacements.put("acceptCount", "backlog");
    replacements.put("connectionLinger", "soLinger");
    replacements.put("connectionTimeout", "soTimeout");
    replacements.put("rootFile", "rootfile");
}

backlog表示積壓待處理的事物,是socket的引數,在bind的時候傳入的,比如在Endpoint裡的bind方法裡:

public void bind() throws Exception {

    serverSock = ServerSocketChannel.open();
    ...
    serverSock.socket().bind(addr,getBacklog());
    ...
}

這個引數跟tcp底層實現的半連線佇列和完全連線佇列有什麼關係呢?我們在tomcat預設BIO模式下模擬一下它的效果。

模擬的思路還是簡單的通過shell指令碼,建立一個長連線傳送請求,持有20秒再斷開,好有時間觀察網路狀態。注意BIO模式下預設超過75%的執行緒時會關閉keep-alive,需要把這個百分比調成100,這樣就不會關閉keep-alive了。修改後的connector如下,最後邊的三行引數是新增的:

<Connector port="8080" protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443"        

    maxThreads="1"
    disableKeepAlivePercentage="100"
    acceptCount="2"
/>

上面的配置裡我們把tomcat的最大執行緒數設定為1個,一直開啟keep-alive,acceptCount設定為2。在linux上可以通過ss命令檢測引數是否生效:

$ ss -ant  
State       Recv-Q Send-Q     Local Address:Port     Peer Address:Port
LISTEN      0      2          :::7001                :::*

可以看到7001埠是LISTEN狀態,send-q的值是2,也就是我們設定的backlog的值。如果我們不設定,tomcat預設會設定為100,java則預設是50。

然後用下面的指令碼模擬一次長連線:

$ { 
    echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n";
    sleep 20
  } | telnet localhost 7001

這個時候看伺服器端socket的狀況,是ESTABLISHED,並且Recv-QSend-Q都是沒有堆積的,說明請求已經處理完

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.7001         127.0.0.1.54453        ESTABLISHED

現在我們模擬多個連線:

$ for i in {1..5}; do 
    ( 
        {
          echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n"; 
          sleep 20
        } | telnet localhost 7001
    )&;  
  done 

上面發起了5個連結,伺服器端只有1個執行緒,只有第一個連線上的請求會被處理,另外4次連線,有2個連線還是完成了建立(ESTABLISHED狀態),還有2個連線則因為伺服器端的連線佇列已滿,沒有響應,傳送端處於SYN_SENT狀態。下面列出傳送端的tcp狀態:

$ netstat -an | awk 'NR==2 || $5~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.51389        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51388        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51387        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51386        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51385        127.0.0.1.7001         ESTABLISHED

再看tomcat端的狀態:

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51387        ESTABLISHED
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51386        ESTABLISHED
tcp4       0      0  127.0.0.1.7001         127.0.0.1.51385        ESTABLISHED

有3個連結,除了第一條連線請求的Recv-Q是0,另外兩個連線的Recv-Q則有資料堆積(大小表示傳送過來的位元組長度)。注意,在ESTABLISHED狀態下看到的Recv-QSend-Q的大小與在LISTEN狀態下的含義不同,在LISTEN狀態下的大小表示佇列的長度,而非資料的大小。

從上面的模擬可以看出acceptCount引數是指伺服器端執行緒都處於busy狀態時(執行緒池已滿),還可接受的連線數,即tcp的完全連線佇列的大小。對於完全佇列的計算,在linux上是:

min(backlog,somaxconn) 

backlog引數和proc/sys/net/core/somaxconn這兩個值哪個小選哪個。

不過acceptCount/backlog引數還不僅僅決定完全連線佇列的大小,對於半連線佇列也有影響。參考同事飄零的blog,在linux 2.6.20核心之後,它的計算方式大致是:

table_entries = min(min(somaxconn,backlog),tcp_max_syn_backlog)
roundup_pow_of_two(table_entries + 1)

第二行的函式roundup_pow_of_two表示取最近的2的n次方的值,舉例來說:假設somaxconn為128,backlog值為50,tcp_max_syn_backlog值為4096,則第一步計算出來的為50,然後roundup_pow_of_two(50 + 1),找到比51大的2的n次方的數為64,所以最終半連線佇列的長度是64。

所以對於acceptCount這個值,需要慎重對待,如果請求量不是很大,通常tomcat預設的100也ok,但若訪問量較大的情況,建議這個值設定的大一些,比如1024或更大。如果在tomcat前邊一層對synflood攻擊的防禦沒有把握的話,最好也開啟syn cookie來防禦。