您的位置:首页 > 运维架构 > Tomcat

Tomcat-connector的微调(1): acceptCount参数(socket的backlog)(重要)

2016-05-15 02:06 453 查看
对于
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-Q
Send-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-Q
Send-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来防御。

经过在linux系统下实测:

查看监听端口backlog数量:ss -ap | grep 9000 | grep LISTEN,recv_q是当前请求连接未被accept的数量,send_q是固定值,是listen(sockid, backlog)中的backlog值;recv_q最大可以比backlog(send_q)多1;但是当recv_q满的时候,仍然可以同时接受2个连接请求,但是连接短暂处于sync_recv状态之后即被断开,客户端收到如下错误信息:java.net.SocketException:
Software caused connection abort: recv failed,超过2个连接请求之外的请求,可能报以上错误,也可能等待太久,报超时错误:org.apache.http.conn.ConnectTimeoutException: Connect to 192.168.211.44:9000 timed out。


recv_q队列,除非上层应用调用accept(),否则不会减少,不管客户端是否关闭连接,或者服务器的keepalive设置超时自动关闭连接,即无论连接处于established状态还是close_wait等状态,都不会使recv_q队列减少。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: