[原創]TCP的backlog導致的HBase訪問超時問題排查(續)
接前一篇文章
TCP的backlog導致的HBase超時問題
https://yq.aliyun.com/articles/117801?spm=5176.8091938.0.0.kypXIC
問題場景
如上圖所示,用戶業務服務器(ApplicationServer)上麵發起HTTP GET/PUT請求,經過SLB到達後端服務器(HBase-Rest-Server), 一般請求鏈路耗時大概100ms左右,但是會有一定的概率出現耗時很長(超過3s)。
業務方提出問題:
1.為什麼slb到ecs連接多
2.為什麼過slb後耗時多了2s多
排查分析過程
首先查詢slb http層訪問日誌, 發現有很多超過3秒的訪問請求,包括put 和get方法, 其記錄表明slb 訪問業務ECS得到處理結果時間較長, 可以判斷出耗時較長的原因可能是在後端網絡或ECS服務處理上.因為業務ECS HBase並沒有記錄每條訪問請求的處理時間,所以不能排除服務本身處理的耗時.
另外分析日誌還存在有與業務ECS 5秒不能建立連接成功請求, 這一般可能是後端服務不穩定或服務器沒做優化會出現此情況網絡同事查看業務ECS 發現有TCP: time wait bucket table overflow內核日誌提示, 證明業務ECS沒有做TIME_WAIT內核參數的優化;
網絡同事抓包後發現有一些syn報文重傳情況,從網絡同事處拿到報文後分別用(tcp.analysis.retransmission), (tcp.flags.syn==1)&&(tcp.analysis.retransmission)過濾發現5分鍾內有3329條syn報文的重傳,且重傳時間都是3秒.
備注:
linux 2.6.32 內核 TCP_TIMEOUT_INIT 3秒是syn報文第一次重傳默認規定時間
linux 3.10內核 TCP_TIMEOUT_INIT 1秒是syn報文第一次重傳默認規定時間.
到此問題比較明確是TCP建立連接失敗, 不斷重傳SYN報文引起延時, 為了證明讓在業務ECS上用nstat以下命令查看一下建立連接失敗的統計和建立連接隊列是否存在溢出.
果然發現執行結果TcpAttemptFails 偶爾非0, TcpExtListenOverflows有10多的值出現.
關於連接數量的分析:
查看用戶slb實例前端流量與連接狀態圖,可知每秒平均259條新建連接
業務服務器兩台, 從上邊nstat輸出觀察可知每台每秒約100-130條TcpPassiveOpens連接,兩台的和值與前端連接數量符合.
問題解決建議
給出業務ECS端建立連接失敗優化調整建議:
net.ipv4.tcp_max_syn_backlog = 16384 原默認值1024
net.core.somaxconn = 65535 原默認值128
服務器程序中listen backlog = 8191
tw參數:
net.ipv4.tcp_tw_reuse改成1
net.ipv4.tcp_max_tw_buckets增加一倍,改為10000
tcp_fin_timeout 調低吧 15秒
但調整後還是發現有耗時3秒的情況, 隨後調查HBase服務發現jetty默認backlog 50, 沒有做修改, 修改後耗時較長現象消除.
下邊屬於上述參數內核代碼部分分析
建立連接半連接與全連接隊列
內核參數somaxconn,sysctl_max_syn_backlog 和listen backlog參數之間的關係分析
要搞清楚內核中與建立連接有關tcp_max_syn_backlog,somaxconn兩個參數的作用,需要細化說明tcp建立連接階段兩個隊列的含義:
linux內核中用struct request_sock_queue -> struct listen_sock->struct request_sock結構存儲當前正在請求建立連接的sock,稱作半連接狀態(用syn_backlog表示)。request_sock有個成員變量指針指向對應的struct sock。struct request_sock_queue中rskq_accept_head和rskq_accept_tail分別指向已經建立完連接的request_sock,稱作全連接狀態(用backlog表示),這些sock都是完成了三次握手等待程序調用accept接受連接。半連接隊列在內核中的具體的變量是:
inet_csk(sk)->icsk_accept_queue-> listen_opt
使用了以下兩個變量維護半連接隊列長度:
已使用隊列長度 inet_csk(sk)->icsk_accept_queue->qlen
最大隊列長度限製 icsk_accept_queue->listen_opt->max_qlen_log (即長度限製2^ max_qlen_log)
全連接隊列在內核中的表示:
request_sock_queue結構中使用rskq_accept_head和rskq_accept_tail維護了全連接隊列。inet_csk(sk)->icsk_accept_queue->rskq_accept_head
inet_csk(sk)->icsk_accept_queue->rskq_accept_tail
使用了以下兩個變量維護全連接隊列長度
unsigned short sk_ack_backlog; //(全連接隊列)可接受連接隊列,使用過程中隊列的計數
unsigned short sk_max_ack_backlog; //(全連接隊列)最大的可接受連接隊列,默認值是listen時設置的backlog參數
服務器端程序通過listen函數設置監聽端口 backlog參數,內核理論上將允許該端口最大同時接收2*backlog參數個並發連接“請求”(不含已被應用程序接管的連接)——分別存放在 syn_backlog 和 backlog 隊列——每個隊列的最大長度為backlog值(為什麼是最大還取決於內核參數稍後解釋)。syn_backlog 隊列存儲 SYN_ACK 狀態的連接,backlog 則存儲 ESTABLISHED 狀態但尚未被應用程序接管的連接。accept係統調用時將從全連接backlog隊列的rskq_accept_head取出head節點req請求,並從此隊列中移除,具體的實現可以參考內核中的tcp_check_req()函數,此函數調用完成後也將握手完成後的連接放入了ehash散列表,就與連接階段的兩個隊列沒有關係了。
內核參數somaxconn,sysctl_max_syn_backlog 和listen backlog參數之間的關係分析
這部分的分析有興趣的可以查看內核中SYSCALL_DEFINE2(listen, int, fd, int, backlog)和reqsk_queue_alloc()函數的實現。
listen()參數backlog,內核參數somaxconn, 內核參數tcp_max_syn_backlog中最小值加1後,向上擴展為2整數次冪後 做為半連接隊列的長度。代碼的實現如下:
if ((unsigned)backlog > somaxconn)//listen調用的backlog大於somaxconn則取兩者之小值
backlog = somaxconn;
nr_table_entries = min_t(u32, backlog, sysctl_max_syn_backlog);//取與max_syn_backlog之最小值
nr_table_entries = max_t(u32, nr_table_entries, 8);// nr_table_entries小於8時取為8
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); //roundup_pow_of_two - round the given value up to nearest power of two 作用是:計算出最接近2的n次方並且大於size的值即向上擴展為2整數次冪
最終nr_table_entries的值就是半連接隊列的長度。
listen()參數backlog,內核參數somaxconn取二者的最小值做為全連接隊列的長度。
if ((unsigned)backlog > somaxconn)//listen調用的backlog大於somaxconn則取兩者之小值backlog = somaxconn;
sk->sk_max_ack_backlog = backlog;
最後更新:2017-07-06 16:02:33