短鏈接的time_out的問題(我們組的人對線上業(yè)務(wù)的改進)
問題現(xiàn)象:
yp_fras前段時間經(jīng)常會大量出現(xiàn)錯誤號為110的connection time out 錯誤
691: yp_wp075v.add.bjdt.qihoo.net [2016/06/12:14:52:03] access [xxx.xxx.xxx.xxx:9869] table: yp_fras catch exception in __construct: [BadaSocket: Could not connect to xxx.xxx.xxx.xxx:9869 (連接超時 [110])]
692: yp_wp023v.add.bjdt.qihoo.net [2016/06/12:14:52:03] access [xxx.xxx.xxx.xxx:9869] table: yp_fras catch exception in __construct: [BadaSocket: Could not connect to xxx.xxx.xxx.xxx:9869 (連接超時 [110])]
693: yp_wp089v.add.bjdt.qihoo.net [2016/06/12:14:52:03] access [xxx.xxx.xxx.xxx:9869] table: yp_fras catch exception in __construct: [BadaSocket: Could not connect to xxx.xxx.xxx.xxx:9869 (連接超時 [110])]
694: yp_wp080v.add.bjdt.qihoo.net [2016/06/12:14:52:07] access [xxx.xxx.xxx.xxx:9869] table: yp_fras catch exception in __construct: [BadaSocket: Could not connect to xxx.xxx.xxx.xxx:9869 (連接超時 [110])]
1: yp_wp054v.add.bjdt.qihoo.net [2016/0
問題結(jié)論
這個問題是由大量的短鏈接造成的哩陕,據(jù)初步統(tǒng)計兼搏,有近百臺客戶機上部署有訪問bjdt機房yp_vrs的客戶端青灼,每個客戶機上又有不少的客戶端濒生。當(dāng)這些客戶端在某個時刻以短鏈接的方式集中訪問服務(wù)端時,TCP連接建立的壓力是比較驚人的登澜。
客戶端大量的短連接請求兆衅,使得服務(wù)端的listen端口的ACCEPT隊列產(chǎn)生溢出,從而不接受新的連接請求攀例,連接失敗,導(dǎo)致”[110][connection time out]”報錯顾腊。
改進意見
提高客戶端的鏈接超時限制粤铭。當(dāng)前是300ms,比如可以提升到3s等杂靶;(治標(biāo)不治本)
提高服務(wù)端的somaxconn限制梆惯,這也是個治標(biāo)不治本的方法酱鸭,只能是一定程度的緩解。(修改內(nèi)核的其他的網(wǎng)絡(luò)參數(shù)也是一樣加袋,只能是緩解,并不能解決根本問題)
在客戶端使用連接緩沖池抱既,將短鏈接轉(zhuǎn)換成長鏈接來使用(個人認(rèn)為這個才是更好的辦法职烧,一勞永逸)
問題分析
Linux的服務(wù)端從listen的端口建立的連接要經(jīng)過兩個隊列的過渡,分別是SYN隊列和ACCEPT隊列防泵。服務(wù)端接受到SYN請求后蚀之,會發(fā)送SYNACK,并把這個request sock存在SYN隊列內(nèi)捷泞;等到三次握手完成后足删,再存放到ACCEPT隊列內(nèi);然后再由accept系統(tǒng)調(diào)用锁右,從ACCEPT隊列內(nèi)拿出失受,交給用戶使用。
SYN隊列和ACCEPT隊列都是有長度限制的咏瑟,這個長度限制與以下三個參數(shù)有關(guān):
- a. 調(diào)用listen接口拂到,傳遞給back_log參數(shù);
- b. 內(nèi)核參數(shù)somaxconn; //與ACCEPT隊列相關(guān)
- c.內(nèi)核參數(shù)tcp_max_syn_backlog码泞; //與SYN隊列相關(guān)
我們線上的問題主要是ACCEPT隊列出現(xiàn)溢出造成的兄旬,所以這里主要分析ACCEPT隊列長度限制的情況
在調(diào)用listen接口的時候,內(nèi)核會用系統(tǒng)的somaxconn參數(shù)去截斷傳遞給listen的back_log參數(shù)余寥,下面是linux2.6.32-70的相關(guān)代碼片段
@sock.c
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
......
if ((unsigned)backlog > somaxconn)
backlog = somaxconn; //被截斷
......
err = sock->ops->listen(sock, backlog);//調(diào)用的就是下面的inet_listen函數(shù)
......
}
@af_inet.c
int inet_listen(struct socket *sock, int backlog)
{
......
sk->sk_max_ack_backlog = backlog;
......
}
上面的sk_max_ack_backlog就是listen端口的ACCEPT隊列的最大長度
當(dāng)短鏈接的量太大领铐,accept系統(tǒng)調(diào)用接口處理來不及時,ACCEPT隊列就可能會阻塞溢出宋舷,這個時候绪撵,Linux的TCP/IP協(xié)議棧的做法是把新來的SYN請求丟棄掉( Accept backlog is full. If we have already queued enough of warm entries in syn queue, drop request. It is better than clogging syn queue with openreqs with exponentially increasing timeout.),這樣當(dāng)客戶端設(shè)定的連接超時不夠發(fā)送第二次SYN請求時祝蝠,就會收不到服務(wù)端ack莲兢,連接建立失敗,這個時候報的錯誤是ETIMEDOUT续膳,也就是“[110][connection time out]“改艇。下面是linux.2.6.32-70的相關(guān)代碼片段
@tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
......
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) //inet_csk_reqsk_queue_young(sk) 表示SYN隊列中還沒有握手完成的請求數(shù),也就是young request sock的數(shù)量
goto drop;//丟棄這個SYN請求
......
}
@sock.h
static inline int sk_acceptq_is_full(struct sock *sk)
{
return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}
在上面的代碼段中坟岔,sk_acceptq_is_full(sk)是判斷ACCEPT隊列是否滿了(隊列長度限制已經(jīng)在listen系統(tǒng)調(diào)用中被截斷了谒兄,這也是為什么我們修改內(nèi)核somaxconn內(nèi)核參數(shù),對當(dāng)前應(yīng)用程序的已經(jīng)listen的端口的ACCEPT隊列長度限制不產(chǎn)生影響的原因社付,需要重起承疲,才能夠使用新的內(nèi)核參數(shù))邻耕,如果滿了,而且SYN隊列中又有新的沒有完成握手的連接請求燕鸽,則丟棄當(dāng)前這個鏈接請求兄世,這個時候的如果客戶端設(shè)置的鏈接超時只夠它發(fā)送一次SYN請求,則鏈接失敗啊研,發(fā)生“[110][connection time out]“報錯御滩。
驗證:
- 按照線上情況,設(shè)置somaxconn為128,listen接口的back_log為8192 運行一定數(shù)量的客戶端党远,頻繁的向服務(wù)端建立TCP鏈接削解,然后釋放,觀察情況
- 設(shè)置somaxconn為8192, 同時設(shè)置listen的接口的back_log參數(shù)也為8192,重復(fù)1的步驟
<?php
while (true) {
$fp = fsockopen ( "10.138.79.205" , 8221 , $errno , $errstr , 0.5 );
fclose ( $fp );
}
?>
上面是單個客戶端的代碼邏輯沟娱,很簡單氛驮。
somaxconn為128。
客戶端大量報錯
PHP Warning: fsockopen(): unable to connect to xxxxxxxxxxx:8221 (Connection refused) in /home/wxf/sample.php on line 3
PHP Warning: fsockopen(): unable to connect to xxxxxxxxxxx:8221 (Connection refused) in /home/wxy/sample.php on line 3
PHP Warning: fsockopen(): unable to connect to xxxxxxxxxxx:8221 (Connection refused) in /home/wxy/sample.php on line 3
....
服務(wù)端的現(xiàn)象
[wxf@host ~]$ for i in {1..6}; do netstat -s | grep -i listen; echo; sleep 1; done
2436905 times the listen queue of a socket overflowed
2436905 SYNs to LISTEN sockets ignored
2436927 times the listen queue of a socket overflowed
2436927 SYNs to LISTEN sockets ignored
2436950 times the listen queue of a socket overflowed
2436950 SYNs to LISTEN sockets ignored
2436985 times the listen queue of a socket overflowed
2436985 SYNs to LISTEN sockets ignored
2436999 times the listen queue of a socket overflowed
2436999 SYNs to LISTEN sockets ignored
2437018 times the listen queue of a socket overflowed
2437018 SYNs to LISTEN sockets ignored
從上面的結(jié)果可以看出济似,被丟棄的SYNs在不斷的增加
somaxconn為8192
客戶端沒有報錯
服務(wù)端
[wxy@host ~]$ for i in {1..6}; do netstat -s | grep -i listen; echo ;sleep 1; done
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
2439591 times the listen queue of a socket overflowed
2439591 SYNs to LISTEN sockets ignored
可以看出矫废,這段時間內(nèi)沒有被丟棄的SYNs
驗證的結(jié)果和內(nèi)核代碼以及我們的預(yù)想是吻合的
**??