日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

由STGW下载慢问题引发的网络传输学习之旅

發(fā)布時間:2024/2/28 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 由STGW下载慢问题引发的网络传输学习之旅 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

導語:本文分享了筆者現(xiàn)網(wǎng)遇到的一個文件下載慢的問題。最開始嘗試過很多辦法,包括域名解析,網(wǎng)絡鏈路分析,AB環(huán)境測試,網(wǎng)絡抓包等,但依然找不到原因。然后利用網(wǎng)絡命令和報文得到的蛛絲馬跡,結(jié)合內(nèi)核網(wǎng)絡協(xié)議棧的實現(xiàn)代碼,找到了一個內(nèi)核隱藏很久但在最近版本解決了的BUG。如果你也想了解如何分析和解決詭異的網(wǎng)絡問題,如果你也想溫習一下課堂上曾經(jīng)學習過的慢啟動、擁塞避免、快速重傳、AIMD等老掉牙的知識,如果你也渴望學習課本上完全沒介紹過的TCP的一系列優(yōu)化比如混合慢啟動、尾包探測甚至BBR等,那么本文或許可以給你一些經(jīng)驗和啟發(fā)。

問題背景

線上用戶經(jīng)過STGW(Secure Tencent Gateway,騰訊安全網(wǎng)關(guān)-七層轉(zhuǎn)發(fā)代理)下載一個50M左右的文件,與直連用戶自己的服務器相比,下載速度明顯變慢,需要定位原因。在了解到用戶的問題之后,相關(guān)的同事在線下做了如下嘗試:

1. 從廣州和上海直接訪問用戶的回源VIP(Virtual IP,提供服務的公網(wǎng)IP地址)下載,都耗時4s+,正常;

2. 只經(jīng)過TGW(Tencent Gateway,騰訊網(wǎng)關(guān)-四層負載均衡系統(tǒng)),不經(jīng)過STGW訪問,從廣州和上海訪問上海的TGW,耗時都是4s+,正常;

3. 經(jīng)過STGW,從上海訪問上海的STGW VIP,耗時4s+,正常;

4. 經(jīng)過STGW,從廣州訪問上海的STGW VIP,耗時12s+,異常。

前面的三種情況都是符合預期,而第四種情況是不符合預期的,這個也是本文要討論的問題。

前期定位排查

發(fā)現(xiàn)下載慢的問題后,我們分析了整體的鏈路情況,按照鏈路經(jīng)過的節(jié)點順序有了如下的排查思路:

(1)從客戶端側(cè)來排查,DNS解析慢,客戶端讀取響應慢或者接受窗口小等;

(2)從鏈路側(cè)來排查,公網(wǎng)鏈路問題,中間交換機設備問題,丟包等;

(3)從業(yè)務服務側(cè)來排查,業(yè)務服務側(cè)發(fā)送響應較慢,發(fā)送窗口較小等;

(4)從自身轉(zhuǎn)發(fā)服務來排查,TGW或STGW轉(zhuǎn)發(fā)程序問題,STGW擁塞窗口緩存等;

按照上面的這些思路,我們分別做了如下的排查:

1.是否是由于異常客戶端的DNS服務器解析慢導致的?

用戶下載小文件沒有問題,并且直接訪問VIP,配置hosts訪問,發(fā)現(xiàn)問題依然復現(xiàn),排除。

2.是否是由于客戶端讀取響應慢或者接收窗口較小導致的?

抓包分析客戶端的數(shù)據(jù)包處理情況,發(fā)現(xiàn)客戶端收包處理很快,并且接收窗口一直都是有很大空間。排除。

3.是否是廣州到上海的公網(wǎng)鏈路或者交換機等設備問題,導致訪問變慢?

從廣州的客戶端上ping上海的VIP,延時很低,并且測試不經(jīng)過STGW,從該客戶端直接訪問TGW再到回源服務器,下載正常,排除。

4.是否是STGW到回源VIP這條鏈路上有問題?

在STGW上直接訪問用戶的回源VIP,耗時4s+,是正常的。并且打開了STGW LD(LoadBalance Director,負載均衡節(jié)點)與后端server之間的響應緩存,抓包可以看到,后端數(shù)據(jù)4s左右全部發(fā)送到STGW LD上,是STGW LD往客戶端回包比較慢,基本可以確認是Client->STGW這條鏈路上有問題。排除。

5.是否是由于TGW或STGW轉(zhuǎn)發(fā)程序有問題?

由于異地訪問必定會復現(xiàn),同城訪問就是正常的。而TGW只做四層轉(zhuǎn)發(fā),無法感知源IP的地域信息,并且抓包也確認TGW上并沒有出現(xiàn)大量丟包或者重傳的現(xiàn)象。STGW是一個應用層的反向代理轉(zhuǎn)發(fā),也不會對于不同地域的cip有不同的處理邏輯。排除。

6.是否是由于TGW是fullnat影響了擁塞窗口緩存?

因為之前由于fullnat出現(xiàn)過一些類似于本例中下載慢的問題,當時定位的原因是由于STGW LD上開啟了擁塞窗口緩存,在fullnat的情況下,會影響擁塞窗口緩存的準確性,導致部分請求下載慢。但是這里將擁塞窗口緩存選項 sysctl -w net.ipv4.tcp_no_metrics_save=1 關(guān)閉之后測試,發(fā)現(xiàn)問題依然存在,并且線下用另外一個fullnat的vip測試,發(fā)現(xiàn)并沒有復現(xiàn)用戶的問題。排除。

根據(jù)一些以往的經(jīng)驗和常規(guī)的定位手段都嘗試了以后,發(fā)現(xiàn)仍然還是沒有找到原因,那到底是什么導致的呢

問題分析

首先,在復現(xiàn)的STGW LD上抓包,抓到Client與STGW LD的包如下圖,從抓包的信息來看是STGW回包給客戶端很慢,每次都只發(fā)很少的一部分到Client。

這里有一個很奇怪的地方就是為什么第7號包發(fā)生了重傳?不過暫時可以先將這個疑問放到一邊,因為就算7號包發(fā)生了一個包的重傳,這中間也并沒有發(fā)生丟包,LD發(fā)送數(shù)據(jù)也并不應該這么慢。那既然LD發(fā)送數(shù)據(jù)這么慢,肯定要么是Client的接收窗口小,要么是LD的擁塞窗口比較小。

對端的接收窗口,抓包就可以看到,實際上Client的接收窗口并不小,而且有很大的空間。那是否有辦法可以看到LD的發(fā)送窗口呢?答案是肯定的:ss -it,這個指令可以看到每條連接的rtt,ssthresh,cwnd等信息。有了這些信息就好辦了,再次復現(xiàn),并寫了個命令將cwnd等信息記錄到文件:

while true; do date +"%T.%6N" >> cwnd.log; ss -it >> cwnd.log; done


復現(xiàn)得到的cwnd.log如上圖,找到對應的連接,grep出來后對照來看。果然發(fā)現(xiàn)在前面幾個包中,擁塞窗口就直接被置為7,并且ssthresh也等于7,并且可以看到后面窗口增加的很慢,直接進入了擁塞避免,這么小的發(fā)送窗口,增長又很緩慢,自然發(fā)送數(shù)據(jù)就會很慢了。

那么到底是什么原因?qū)е逻@里直接在前幾個包就進入擁塞避免呢?從現(xiàn)有的信息來看,沒辦法直接確定原因,只能去啃代碼了,但tcp擁塞控制相關(guān)的代碼這么多,如何能快速定位呢

觀察上面異常數(shù)據(jù)包的cwnd信息,可以看到一個很明顯的特征,最開始ssthresh是沒有顯示出來的,經(jīng)過了幾個數(shù)據(jù)包之后,ssthresh與cwnd是相等的,所以嘗試按照"snd_ssthresh ="和"snd_cwnd ="的關(guān)鍵字來搜索,按照snd_cwnd = snd_ssthresh的原則來找,排除掉一些不太可能的函數(shù)之后,最后找到了tcp_end_cwnd_reduction這個函數(shù)。

再查找這個函數(shù)引用的地方,有兩處:tcp_fastretrans_alert和tcp_process_tlp_ack這兩個函數(shù)。

tcp_fastretrans_alert看名字就知道是跟快速重傳相關(guān)的函數(shù),我們知道快速重傳觸發(fā)的條件是收到了三個重復的ack包。但根據(jù)前面的抓包及分析來看,并不滿足快速重傳的條件,所以疑點就落在了這個tcp_process_tlp_ack函數(shù)上面。那么到底什么是TLP呢

什么是TLP(Tail Loss Probe)

在講TLP之前,我們先來回顧下大學課本里學到的擁塞控制算法,祭出這張經(jīng)典的擁塞控制圖。?

TCP的擁塞控制主要分為四個階段:慢啟動,擁塞避免,快重傳,快恢復。長久以來,我們聽到的說法都是,最開始擁塞窗口從1開始慢啟動,以指數(shù)級遞增,收到三個重復的ack后,將ssthresh設置為當前cwnd的一半,并且置cwnd=ssthresh,開始執(zhí)行擁塞避免,cwnd加法遞增。

這里我們來思考一個問題,發(fā)生丟包時,為什么要將ssthresh設置為cwnd的一半?

想象一個場景,A與B之間發(fā)送數(shù)據(jù),假設二者發(fā)包和收包頻率是一致的,由于A與B之間存在空間距離,中間要經(jīng)過很多個路由器,交換機等,A在持續(xù)發(fā)包,當B收到第一個包時,這時A與B之間的鏈路里的包的個數(shù)為N,此時由于B一直在接收包,因此A還可以繼續(xù)發(fā),直到第一個包的ack回到A,這時A發(fā)送的包的個數(shù)就是當前A與B之間最大的擁塞窗口,即為2N,因為如果這時A多發(fā)送,肯定就丟包了。

ssthresh代表的就是當前鏈路上可以發(fā)送的最大的擁塞窗口大小,理想情況下,ssthresh就是2N,但現(xiàn)實的環(huán)境很復雜,不可能剛好cwnd經(jīng)過慢啟動就可以直接到達2N,發(fā)送丟包的時候,肯定是N<1/2*cwnd<2N,因此此時將ssthresh設置為1/2*cwnd,然后再從此處加法增加慢慢的達到理想窗口,不能增長過快,因為要“避免擁塞”。

實際上,各個擁塞控制算法都有自己的實現(xiàn),初始cwnd的值也一直在優(yōu)化,在linux 3.0版本以后,內(nèi)核CUBIC的實現(xiàn)里,采用了Google在RFC6928的建議,將初始的cwnd的值設置為10。而在linux 3.0版本之前,采取的是RFC3390中的策略,根據(jù)不同的MSS,設置了不同的初始化cwnd。具體的策略為:

If (MSS <= 1095 bytes)

? ? then cwnd=4;

If (1095 bytes < MSS < 2190 bytes)

? ? then cwnd=3;

If (2190 bytes <= MSS)

? ? then cwnd=2;

并且在執(zhí)行擁塞避免時,當前CUBIC的實現(xiàn)里也不是將ssthresh設置為cwnd的一半,而是717/1024≈0.7左右,RFC8312也提到了這樣做的原因。

Principle 4: To balance between the scalability and convergence speed, CUBIC sets the multiplicative window decrease factor to 0.7 while Standard TCP uses 0.5. While this improves the scalability of CUBIC, a side effect of this decision is slower convergence, especially under low statistical multiplexing environments.

從上面的描述可以看到,在TCP的擁塞控制算法里,最核心的點就是ssthresh的確定,如何能快速準確的確定ssthresh,就可以更加高效的傳輸。而現(xiàn)實的網(wǎng)絡環(huán)境很復雜,在有些情況下,沒有辦法滿足快速重傳的條件,如果每次都以丟包作為反饋,代價太大。比如,考慮如下的幾個場景:

  • 是否可以探測到ssthresh的值,不依賴丟包來觸發(fā)進入擁塞避免,主動退出慢啟動?

  • 如果沒有足夠的dup ack(大于0,小于3)來觸發(fā)快速重傳,如何處理?

  • 如果沒有任何的dup ack(等于0),比如尾丟包的情況,如何處理?

  • 是否可以主動探測網(wǎng)絡帶寬,基于反饋驅(qū)動來調(diào)整窗口,而不是丟包等事件驅(qū)動來執(zhí)行擁塞控制?

針對上面的前三種情況,TCP協(xié)議棧分別都做了相應的優(yōu)化,對應的優(yōu)化算法分別為:hystart(Hybrid Slow Start),ER(Early Retransmit)和TLP(Tail Loss Probe)。對于第四種情況,Google給出了答案,創(chuàng)造了一種新的擁塞控制算法,它的名字叫BBR,從linux 4.19開始,內(nèi)核已經(jīng)將默認的擁塞控制算法從CUBIC改成了BBR。受限于本文的篇幅有限,無法對BBR算法做詳盡的介紹,下面僅結(jié)合內(nèi)核CUBIC的代碼來分別介紹前面的這三種優(yōu)化算法。

1. 慢啟動的hystart優(yōu)化

混合慢啟動的思想是在論文《Hybrid Slow Start for High-Bandwidth and Long-Distance Networks》里首次提出的,前面我也說過,如果每次判斷擁塞都依賴丟包來作為反饋,代價太大,hystart也是在這個方向上做優(yōu)化,它主要想解決的問題就是不依賴丟包作為反饋來退出慢啟動,它提出的退出條件有兩類:

  • 判斷在同一批發(fā)出去的數(shù)據(jù)包收到的ack包(對應論文中的acks train length)的總時間大于min(rtt)/2;

  • 判斷一批樣本中的最小rtt是否大于全局最小rtt加一個閾值的和;

內(nèi)核CUBIC的實現(xiàn)里默認都是開啟了hystart,在bictcp_init函數(shù)里判斷是否開啟并做初始化

static inline void bictcp_hystart_reset(struct sock *sk) {struct tcp_sock *tp = tcp_sk(sk);struct bictcp *ca = inet_csk_ca(sk);ca->round_start = ca->last_ack = bictcp_clock();ca->end_seq = tp->snd_nxt;ca->curr_rtt = 0;ca->sample_cnt = 0; } static void bictcp_init(struct sock *sk) {struct bictcp *ca = inet_csk_ca(sk);bictcp_reset(ca);ca->loss_cwnd = 0;if (hystart)//如果開啟了hystart,那么做初始化bictcp_hystart_reset(sk);if (!hystart && initial_ssthresh)tcp_sk(sk)->snd_ssthresh = initial_ssthresh; }

核心的判斷是否退出慢啟動的函數(shù)在hystart_update里

static void hystart_update(struct sock *sk, u32 delay) {struct tcp_sock *tp = tcp_sk(sk);struct bictcp *ca = inet_csk_ca(sk);if (!(ca->found & hystart_detect)) {u32 now = bictcp_clock();/* first detection parameter - ack-train detection *///判斷如果連續(xù)兩個ack的間隔小于hystart_ack_delta(2ms),則為一個acks trainif ((s32)(now - ca->last_ack) <= hystart_ack_delta) {ca->last_ack = now;//如果ack_train的總長度大于1/2 * min_rtt,則退出慢啟動,ca->delay_min = 8*min_rttif ((s32)(now - ca->round_start) > ca->delay_min >> 4)ca->found |= HYSTART_ACK_TRAIN;}/* obtain the minimum delay of more than sampling packets *///如果小于HYSTART_MIN_SAMPLES(8)個樣本則直接計數(shù)if (ca->sample_cnt < HYSTART_MIN_SAMPLES) {if (ca->curr_rtt == 0 || ca->curr_rtt > delay)ca->curr_rtt = delay;ca->sample_cnt++;} else {/** 否則,判斷這些樣本中的最小rtt是否要大于全局的最小rtt+有范圍變化的閾值,* 如果是,則說明發(fā)生了擁塞*/if (ca->curr_rtt > ca->delay_min +HYSTART_DELAY_THRESH(ca->delay_min>>4))ca->found |= HYSTART_DELAY;}/** Either one of two conditions are met,* we exit from slow start immediately.*///判斷ca->found如果為真,則退出慢啟動,進入擁塞避免if (ca->found & hystart_detect)tp->snd_ssthresh = tp->snd_cwnd;} }

2. ER(Early?Retransmit)算法

我們知道,快重傳的條件是必須收到三個相同的dup ack,才會觸發(fā),那如果在有些情況下,沒有足夠的dup ack,只能依賴rto超時,再進行重傳,并且開始執(zhí)行慢啟動,這樣的代價太大,ER算法就是為了解決這樣的場景,RFC5827詳細介紹了這個算法。

算法的基本思想:

ER_ssthresh = 3 //ER_ssthresh代表觸發(fā)快速重傳的dup ack的個數(shù) if (unacked segments < 4 && no new data send)if (sack is unable) // 如果SACK選項不支持,則使用還未ack包的個數(shù)減一作為閾值ER_ssthresh = unacked segments - 1elif (sacked packets == unacked segments - 1) // 否則,只有當還有一個包還未sack,才能啟用ER,并且置閾值為還未ack包的個數(shù)減一ER_ssthresh = unacked segments - 1

對應到代碼里的函數(shù)為tcp_time_to_recover:

static bool tcp_time_to_recover(struct sock *sk, int flag) {.../* Trick#6: TCP early retransmit, per RFC5827. To avoid spurious* retransmissions due to small network reorderings, we implement* Mitigation A.3 in the RFC and delay the retransmission for a short* interval if appropriate.*/if (tp->do_early_retrans //開啟ER算法&& !tp->retrans_out //沒有重傳數(shù)據(jù)&& tp->sacked_out //當前收到了dupack包&& (tp->packets_out >= (tp->sacked_out + 1) && tp->packets_out < 4) //滿足ER的觸發(fā)條件&& !tcp_may_send_now(sk)) //沒有新的數(shù)據(jù)發(fā)送return !tcp_pause_early_retransmit(sk, flag);//判斷是立即進入ER還是需要delay 1/4 rttreturn false; } /** 這里內(nèi)核的實現(xiàn)與rfc5827有一點不同,就是引入了delay ER的概念,主要是防止過多減小的dupack 閾值帶來的* 無效的重傳,所以默認加了一個1/4 RTT的delay,在ER的基礎上又做了一個折中,等一段時間再判斷是否要重傳。* 如果是false,則立即進入ER,如果是true,則delay max(RTT/4,2msec)再進入ER*/ static bool tcp_pause_early_retransmit(struct sock *sk, int flag) {struct tcp_sock *tp = tcp_sk(sk);unsigned long delay;/* Delay early retransmit and entering fast recovery for* max(RTT/4, 2msec) unless ack has ECE mark, no RTT samples* available, or RTO is scheduled to fire first.*///內(nèi)核提供了一個參數(shù)tcp_early_retrans來控制ER和delay ER,等于2和3時,是打開了delay ERif (sysctl_tcp_early_retrans < 2 || sysctl_tcp_early_retrans > 3 ||(flag & FLAG_ECE) || !tp->srtt)return false;delay = max_t(unsigned long, (tp->srtt >> 5), msecs_to_jiffies(2));if (!time_after(inet_csk(sk)->icsk_timeout, (jiffies + delay)))return false;//設置delay ER的定時器inet_csk_reset_xmit_timer(sk, ICSK_TIME_EARLY_RETRANS, delay,TCP_RTO_MAX);return true; }

delay ER的定時器超時的處理函數(shù)tcp_resume_early_retransmit。

void tcp_resume_early_retransmit(struct sock *sk) {struct tcp_sock *tp = tcp_sk(sk);tcp_rearm_rto(sk);/* Stop if ER is disabled after the delayed ER timer is scheduled */if (!tp->do_early_retrans)return;//執(zhí)行快速重傳tcp_enter_recovery(sk, false);tcp_update_scoreboard(sk, 1);tcp_xmit_retransmit_queue(sk); }

內(nèi)核提供了一個開關(guān),tcp_early_retrans用于開啟和關(guān)閉TLP和ER算法,默認是3,即打開了delay ER和TLP算法。

sysctl_tcp_early_retrans (defalut:3)0 disables ER1 enables ER2 enables ER but delays fast recovery and fast retransmit by a fourth of RTT.3 enables delayed ER and TLP.4 enables TLP only.

到此,這就是內(nèi)核設計ER算法的相關(guān)的代碼。ER算法在cwnd比較小的情況下,是可以有一些改善的,但個人認為,實際的效果可能一般。因為如果cwnd較小,執(zhí)行慢啟動與執(zhí)行快速重傳再進入擁塞避免相比,二者的實際傳輸效率可能相差并不大。

3.TLP(Tail Loss Probe)算法

TLP想解決的問題是:如果尾包發(fā)生了丟包,沒有新包可發(fā)送觸發(fā)多余的dup ack來實現(xiàn)快速重傳,如果完全依賴RTO超時來重傳,代價太大,那如何能優(yōu)化解決這種尾丟包的情況。

TLP算法是2013年谷歌在論文《Tail Loss Probe (TLP): An Algorithm for Fast Recovery of Tail Losses》中提出來的,它提出的基本思想是:

在每個發(fā)送的數(shù)據(jù)包的時候,都更新一個定時器PTO(probe timeout),這個PTO是動態(tài)變化的,當發(fā)出的包中存在未ack的包,并且在PTO時間內(nèi)都未收到一個ack,那么就會發(fā)送一個新包或者重傳最后的一個數(shù)據(jù)包,探測一下當前網(wǎng)絡是否真的擁塞發(fā)生丟包了。

如果收到了tail包的dup ack,則說明沒有發(fā)生丟包,繼續(xù)執(zhí)行當前的流程;否則說明發(fā)生了丟包,需要執(zhí)行減窗,并且進入擁塞避免。

這里其中一個比較重要的點是PTO如何設置,設置的策略如下:

if unacked packets == 0:no need set PTO else if unacked packets == 1:PTO=max(2rtt, 1.5*rtt+TCP_DELACK_MAX, 10ms) else:PTO=max(2rtt, 10ms) 注:TCP_DELACK_MAX = 200ms

對應到代碼里的tcp_schedule_loss_probe函數(shù):

bool tcp_schedule_loss_probe(struct sock *sk) {struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);u32 timeout, tlp_time_stamp, rto_time_stamp;u32 rtt = tp->srtt >> 3;if (WARN_ON(icsk->icsk_pending == ICSK_TIME_EARLY_RETRANS))return false;/* No consecutive loss probes. */if (WARN_ON(icsk->icsk_pending == ICSK_TIME_LOSS_PROBE)) {tcp_rearm_rto(sk);return false;}/* Don't do any loss probe on a Fast Open connection before 3WHS* finishes.*/if (sk->sk_state == TCP_SYN_RECV)return false;/* TLP is only scheduled when next timer event is RTO. */if (icsk->icsk_pending != ICSK_TIME_RETRANS)return false;/* Schedule a loss probe in 2*RTT for SACK capable connections* in Open state, that are either limited by cwnd or application.*///判斷是否開啟了TLP及一些觸發(fā)條件if (sysctl_tcp_early_retrans < 3 || !rtt || !tp->packets_out ||!tcp_is_sack(tp) || inet_csk(sk)->icsk_ca_state != TCP_CA_Open)return false;if ((tp->snd_cwnd > tcp_packets_in_flight(tp)) &&tcp_send_head(sk))return false;/* Probe timeout is at least 1.5*rtt + TCP_DELACK_MAX to account* for delayed ack when there's one outstanding packet.*///這個與上面描述的策略是一致的timeout = rtt << 1;if (tp->packets_out == 1)timeout = max_t(u32, timeout,(rtt + (rtt >> 1) + TCP_DELACK_MAX));timeout = max_t(u32, timeout, msecs_to_jiffies(10));/* If RTO is shorter, just schedule TLP in its place. */tlp_time_stamp = tcp_time_stamp + timeout;rto_time_stamp = (u32)inet_csk(sk)->icsk_timeout;if ((s32)(tlp_time_stamp - rto_time_stamp) > 0) {s32 delta = rto_time_stamp - tcp_time_stamp;if (delta > 0)timeout = delta;}//設置PTO定時器inet_csk_reset_xmit_timer(sk, ICSK_TIME_LOSS_PROBE, timeout,TCP_RTO_MAX);return true; }

?PTO超時之后,會觸發(fā)tcp_send_loss_probe發(fā)送TLP包:

/* When probe timeout (PTO) fires, send a new segment if one exists, else* retransmit the last segment.*/ void tcp_send_loss_probe(struct sock *sk) {struct tcp_sock *tp = tcp_sk(sk);struct sk_buff *skb;int pcount;int mss = tcp_current_mss(sk);int err = -1;//如果還可以發(fā)送新數(shù)據(jù),那么就發(fā)送新數(shù)據(jù)if (tcp_send_head(sk) != NULL) {err = tcp_write_xmit(sk, mss, TCP_NAGLE_OFF, 2, GFP_ATOMIC);goto rearm_timer;}/* At most one outstanding TLP retransmission. *///一次最多只有一個TLP探測包if (tp->tlp_high_seq)goto rearm_timer;/* Retransmit last segment. *///如果沒有新數(shù)據(jù)可發(fā)送,就重新發(fā)送最后的一個數(shù)據(jù)包skb = tcp_write_queue_tail(sk);if (WARN_ON(!skb))goto rearm_timer;pcount = tcp_skb_pcount(skb);if (WARN_ON(!pcount))goto rearm_timer;if ((pcount > 1) && (skb->len > (pcount - 1) * mss)) {if (unlikely(tcp_fragment(sk, skb, (pcount - 1) * mss, mss)))goto rearm_timer;skb = tcp_write_queue_tail(sk);}if (WARN_ON(!skb || !tcp_skb_pcount(skb)))goto rearm_timer;err = __tcp_retransmit_skb(sk, skb);/* Record snd_nxt for loss detection. */if (likely(!err))tp->tlp_high_seq = tp->snd_nxt; rearm_timer:inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,inet_csk(sk)->icsk_rto,TCP_RTO_MAX);if (likely(!err))NET_INC_STATS_BH(sock_net(sk),LINUX_MIB_TCPLOSSPROBES);return; }

發(fā)送TLP探測包后,在tcp_process_tlp_ack里判斷是否發(fā)生了丟包,做相應的處理:

/* This routine deals with acks during a TLP episode.* Ref: loss detection algorithm in draft-dukkipati-tcpm-tcp-loss-probe.*/ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) {struct tcp_sock *tp = tcp_sk(sk);//判斷這個包是否是tlp包的dup ack包bool is_tlp_dupack = (ack == tp->tlp_high_seq) &&!(flag & (FLAG_SND_UNA_ADVANCED |FLAG_NOT_DUP | FLAG_DATA_SACKED));/* Mark the end of TLP episode on receiving TLP dupack or when* ack is after tlp_high_seq.*///如果是dup ack,說明沒有發(fā)生丟包,繼續(xù)當前的流程if (is_tlp_dupack) {tp->tlp_high_seq = 0;return;}//否則,減窗,并進入擁塞避免if (after(ack, tp->tlp_high_seq)) {tp->tlp_high_seq = 0;/* Don't reduce cwnd if DSACK arrives for TLP retrans. */if (!(flag & FLAG_DSACKING_ACK)) {tcp_init_cwnd_reduction(sk, true);tcp_set_ca_state(sk, TCP_CA_CWR);tcp_end_cwnd_reduction(sk);tcp_try_keep_open(sk);NET_INC_STATS_BH(sock_net(sk),LINUX_MIB_TCPLOSSPROBERECOVERY);}} }

TLP算法的設計思路還是挺好的,主動提前發(fā)現(xiàn)網(wǎng)絡是否擁塞,而不是被動的去依賴丟包來作為反饋。在大多數(shù)情況下是可以提高網(wǎng)絡傳輸?shù)男实?#xff0c;但在某些情況下可能會"適得其反",而本文遇到的問題就是"適得其反"的一個例子。

問題的解決

回到我們的這個問題上,如何確認確實是由于TLP引起的呢

繼續(xù)查看代碼可以看到,TLP的loss probe和loss recovery次數(shù),內(nèi)核都有相應的計數(shù)器跟蹤。

既然有計數(shù)器就好辦了,復現(xiàn)的時候netstat -s就可以查看是否命中TLP了。寫了個腳本將結(jié)果寫入到文件里。

while true; do date +"%T.%6N" >> loss.log; netstat -s | grep Loss >> loss.log; done

?

查看計數(shù)器增長的情況,結(jié)合抓包文件來看,基本確認肯定是命中TLP了。知道原因那就好辦了,關(guān)掉TLP驗證一下應該就可以解決了。

如上面介紹ER算法時提到,內(nèi)核提供了一個開關(guān),tcp_early_retrans可用于開啟和關(guān)閉ER和TLP,默認是3(enable TLP and delayed ER),sysctl -w net.ipv4.tcp_early_retrans=2 關(guān)掉TLP,再次重新測試,發(fā)現(xiàn)問題解決了:

窗口增加的很快,最終的ssthresh為941,下載速度4s+,也是符合預期,到此用戶的問題已經(jīng)解決,但所有的疑問都得到了正確的解答了嗎

真正的真相

雖然用戶的問題已經(jīng)得到了解決,但至少還有兩個問題沒有得到答案:

1. 為什么會每次都在握手完的前幾個包里就會觸發(fā)TLP?

2. 雖然觸發(fā)了TLP,但從抓包來看,已經(jīng)收到了尾包的dup ack包,那說明沒有發(fā)生丟包,為什么還是進入了擁塞避免?

先回答第一個問題,根據(jù)文章最前面的網(wǎng)絡結(jié)構(gòu)圖可以看到,STGW是掛在TGW的后面。在本場景中,用戶訪問的是TGW的高防VIP,高防VIP有一個默認開啟的功能就是SYN代理。

syn代理指的是client發(fā)起連接時,首先是由tgw代答syn ack包,client真正開始發(fā)送數(shù)據(jù)包時,tgw再發(fā)送三次握手的包到rs,并轉(zhuǎn)發(fā)數(shù)據(jù)包。

在本例中,tgw的rs就是stgw,也就是說,stgw的收到三次握手包的rtt是基于與tgw計算出來的,而后面的數(shù)據(jù)包才是真正與client之間的通信。前面背景描述中提到,用戶同城訪問(上海client訪問上海的vip)也是沒有問題的,跨城訪問就有問題。

這是因為同城訪問的情況下,tgw與stgw之間的rtt與client與stgw之間的rtt,相差并不大,并沒有滿足觸發(fā)tlp的條件。而跨城訪問后,三次握手的數(shù)據(jù)包的rtt是基于與tgw來計算的,比較小,后面收到數(shù)據(jù)包后,計算的是client到stgw之間的rtt,一下子增大了很多,并且滿足了tlp的觸發(fā)條件

PTO=max(2rtt, 10ms)

設置的PTO定時器超時了,協(xié)議棧認為是不是由于網(wǎng)絡發(fā)生了擁塞,所以重傳了尾包探測一下查看是否真的發(fā)生了擁塞,這就是為什么每次都是在握手完隨后的幾個包里就會有重傳包,觸發(fā)了TLP的原因。

再回到第二個問題,從抓包來看,很明顯,網(wǎng)絡并沒有發(fā)生擁塞或丟包,stgw已經(jīng)收到了尾包的dup ack包,按照TLP的原理來看,不應該進入擁塞避免的,到底是什么原因?qū)е碌摹0偎疾坏闷浣?#xff0c;只能再繼續(xù)啃代碼了,再回到tlp_ack的這一部分代碼來看。

只有當is_tlp_dupack為false時,才會進入到下面部分,進入擁塞避免,也就是說這里is_tlp_dupack肯定是為false的。ack == tp->tlp_high_seq這個條件是滿足的,那么問題就出在了幾個flag上面,看下幾個flag的定義:

#define FLAG_SND_UNA_ADVANCED 0x400 #define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) #define FLAG_DATA_SACKED 0x20 /* New SACK.

也就是說,只要flag包含了上面幾個中的任意一個,都會將is_tlp_dupack置為false,那到底flag包含了哪一個呢?如何繼續(xù)排查呢

調(diào)試內(nèi)核信息,最常用的工具就是ftracesystemtap

這里首先嘗試了ftrace,發(fā)現(xiàn)它并不能滿足我的需求。ftrace最主要的功能是可以跟蹤函數(shù)的調(diào)用信息,并且可以知道各個函數(shù)的執(zhí)行時間,在有些場景下非常好用,但原生的ftrace命令用起來很不方便,ftrace團隊也意識到了這個問題,因此提供了另外一個工具trace-cmd,使用起來非常簡單。

trace-cmd record -p function_graph -P 3252 //跟蹤pid 3252的函數(shù)調(diào)用情況 trace-cmd report > report.log //以可視化的方式展示ftrace的結(jié)果并重定向到文件里

下圖是使用trace-cmd跟蹤的一個例子部分截圖,可以看到完整打印了內(nèi)核函數(shù)的調(diào)用信息及對應的執(zhí)行時間。

但在當前的這個問題里,主要是想確認flag這個變量的值,ftrace沒有辦法打印出變量的值,因此考慮下一個強大的工具:systemtap

systemtap是一個很強大的動態(tài)追蹤工具,利用它可以很方便的調(diào)試內(nèi)核信息,跟蹤內(nèi)核函數(shù),打印變量信息等,很顯然它是符合我們的需求的。systemptap的使用需要安裝內(nèi)核調(diào)試信息包(kernel-debuginfo),但由于復現(xiàn)的那臺機器上的內(nèi)核版本較老,沒有debug包,無法使用stap工具,因此這條路也走不通。

最后,聯(lián)系了h_tlinux_Helper尋求幫助,他幫忙找到了復現(xiàn)機器內(nèi)核版本的dev包,并在tcp_process_tlp_ack函數(shù)里打印了一些變量,并輸出堆棧信息。重新安裝了調(diào)試的內(nèi)核,復現(xiàn)后打印了如下的堆棧及變量信息:

綠色標記處的那一行,就是收到的dup ack的那個包,可以看到flag的標記為0x4902,換算成宏定義為:

FLAG_UPDATE_TS_RECENT | FLAG_DSACKING_ACK | FLAG_SLOWPATH | FLAG_WIN_UPDATE

再對照tcp_process_tlp_ack函數(shù)看一下,正是FLAG_WIN_UPDATE這個標記導致了is_tlp_dupack = false。那在什么情況下,flag會被置為FLAG_WIN_UPDATE呢

繼續(xù)看代碼,對端回復的每個ack包基本會進入到tcp_ack_update_window函數(shù)。

看到這里flag被置為FLAG_WIN_UPDATE的條件是tcp_may_update_window返回true。

?

再看到tcp_may_update_window函數(shù)這里,after(ack_seq, tp->snd_wl1)?是基本都會命中的,因為不管窗口有沒有變化,ack_seq都會比snd_wl1 大的,ack_seq都是遞增的,snd_wl1在tcp_update_wl中又會被更新成上一次的ack_seq。因此絕大多數(shù)的包的flag都會被打上FLAG_WIN_UPDATE標記。

如果是這樣的話,那is_tlp_dupack不就是都為false了嗎?不管有沒有收到dup ack包,TLP都會進入擁塞避免,這個就不符合TLP的設計初衷了,這里是否是內(nèi)核實現(xiàn)的Bug

隨后我查看了linux 4.14內(nèi)核代碼:

發(fā)現(xiàn)從內(nèi)核版本linux 4.0開始,BUG就已經(jīng)被修復了,去掉了flag的一些不合理的判斷條件,這才是真正的符合TLP的設計原理。

到此,整個問題的所有疑點才都得到了解釋。

總結(jié)

本文從一個下載慢的線上問題入手,首先介紹了一些常規(guī)的排查思路和手段,發(fā)現(xiàn)仍然不能定位到原因。然后分享了一個可以查詢每條連接的擁塞窗口命令,結(jié)合內(nèi)核代碼分析了TCP擁塞控制ssthresh的設計理念及混合慢啟動,ER和尾包探測(TLP)等優(yōu)化算法,并介紹了兩個常用的內(nèi)核調(diào)試工具:ftrace和systemtap,最終定位到是內(nèi)核的TLP實現(xiàn)BUG導致的下載慢的問題,從內(nèi)核4.0版本之后已經(jīng)修復了這個問題。

總結(jié)

以上是生活随笔為你收集整理的由STGW下载慢问题引发的网络传输学习之旅的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 在线免费观看a视频 | 玖玖伊人 | 久久久久极品 | 国产无套精品一区二区三区 | 午夜影音 | 成人乱人乱一区二区三区 | 蜜桃久久久久久久 | 中文字幕永久在线播放 | 国产亚洲精久久久久久无码苍井空 | 草久久 | 国产偷人妻精品一区 | 少妇又紧又色又爽又刺激视频 | 亚洲精品日韩av | 精品色哟哟 | 国产伦精品一区二区三区四区免费 | 免费av手机在线观看 | 丰满人妻在公车被猛烈进入电影 | 亚洲伊人影院 | 成人久久久精品乱码一区二区三区 | 丁香花高清在线观看完整动漫 | 久久久久亚洲精品中文字幕 | 国产传媒第一页 | 男女互插视频 | 成人羞羞国产免费动态 | 欧美射射 | 一级黄色欧美 | 国产在线观看网站 | 精品少妇一区二区三区 | 日日碰日日操 | 亚洲天堂五月天 | 国产免费aa | 日韩av三级在线 | 久久久久久国产精品日本 | 欧美在线精品一区二区三区 | 日日夜夜欧美 | 自拍视频在线 | 美女视频在线观看免费 | 欧美不卡在线观看 | 九九九在线视频 | 超碰人人人人 | 免费成人美女在线观看 | 亚洲色图制服诱惑 | 日日操夜夜 | 亚洲熟女乱色一区二区三区久久久 | 国语精品久久 | 国产人与禽zoz0性伦 | 欧美日韩成人 | 一区二区高清在线观看 | 国产精品麻豆入口 | 亚洲国产免费看 | 中国毛片网站 | 国产成人精品一区二区三区在线 | 奇米精品一区二区三区在线观看 | 日韩av一区二区在线播放 | 法国空姐电影在线观看 | 亚洲AV成人无码电影在线观看 | 亚洲欧美综合视频 | 欧美成人免费观看视频 | 东方av在线免费观看 | 天天操天天爽天天干 | 欧洲黄色录像 | 精品一区二区三区免费 | 午夜国产福利在线 | 国产婷| 国产不卡a | 日韩不卡视频在线 | 爱看av在线 | 亚洲福利电影 | 看黄色网址 | 性按摩玩人妻hd中文字幕 | 美女被爆操网站 | 日韩中文字幕一区二区 | 扒开伸进免费视频 | 天码人妻一区二区三区在线看 | 欧美激情二区三区 | av中文一区 | 亚洲成人久久久 | 日韩一区二区a片免费观看 伊人网综合在线 | 国产视频三级 | 日韩在线不卡 | 欧色丰满女同hd | 日韩精品在线免费视频 | 男人插女人网站 | 亚洲精品免费在线视频 | 全部免费毛片在线播放高潮 | 欧美成人小视频 | 国产综合在线视频 | 老头吃奶性行交 | 久在线视频 | 97久久人澡人人添人人爽 | 岛国激情 | 丝袜熟女一区二区三区 | 一区二区在线不卡 | 欧美10p | 果冻传媒18禁免费视频 | 久久国产精品久久久久久电车 | 国产人人爱 | 日本三级黄在线观看 | av在线免费播放 |