原文
原文地址linux之TCP調優 (liu-kevin.com)
tcp握手
客戶端在等待服務器回復的 ACK 報文。正常情況下,服務器會在幾毫秒內返回 ACK,但如果客戶端遲遲沒有收到 ACK 客戶端會重發 SYN,重試的次數由 tcp_syn_retries 參數控制,默認是 6 次:
net.ipv4.tcp_syn_retries = 6
第 1 次重試發生在 1 秒鍾后,接着會以翻倍的方式在第 2、4、8、16、32 秒共做 6 次重試,最后一次重試會等待 64 秒,如果仍然沒有返回 ACK,才會終止三次握手。所以,總耗時是 1+2+4+8+16+32+64=127 秒,超過 2 分鍾。
如果這是一台有明確任務的服務器,你可以根據網絡的穩定性和目標服務器的繁忙程度修改重試次數,調整客戶端的三次握手時間上限。比如內網中通訊時,就可以適當調低重試次數,盡快把錯誤暴露給應用程序。
新連接建立失敗的原因有很多,怎樣獲得由於隊列已滿而引發的失敗次數呢?netstat -s 命令給出的統計結果中可以得到:
netstat -s | grep "SYNs to LISTEN"
這里給出的是隊列溢出導致 SYN 被丟棄的個數。注意這是一個累計值,如果數值在持續增加,則應該調大 SYN 半連接隊列。修改隊列大小的方法,是設置 Linux 的 tcp_max_syn_backlog 參數:
net.ipv4.tcp_max_syn_backlog = 1024
這里給出的是隊列溢出導致 SYN 被丟棄的個數。注意這是一個累計值,如果數值在持續增加,則應該調大 SYN 半連接隊列。修改隊列大小的方法,是設置 Linux 的 tcp_max_syn_backlog 參數:
net.ipv4.tcp_max_syn_backlog = 1024
如果 SYN 半連接隊列已滿,只能丟棄連接嗎?並不是這樣,開啟 syncookies 功能就可以在不使用 SYN 隊列的情況下成功建立連接。syncookies 是這么做的:服務器根據當前狀態計算出一個值,放在己方發出的 SYN+ACK 報文中發出,當客戶端返回 ACK 報文時,取出該值驗證,如果合法,就認為連接建立成功
Linux 下怎樣開啟 syncookies 功能呢?修改 tcp_syncookies 參數即可,其中值為 0 時表示關閉該功能,2 表示無條件開啟功能,而 1 則表示僅當 SYN 半連接隊列放不下時,再啟用它。由於 syncookie 僅用於應對 SYN 泛洪攻擊(攻擊者惡意構造大量的 SYN 報文發送給服務器,造成 SYN 半連接隊列溢出,導致正常客戶端的連接無法建立),這種方式建立的連接,許多 TCP 特性都無法使用。所以,應當把 tcp_syncookies 設置為 1,僅在隊列滿時再啟用。
net.ipv4.tcp_syncookies = 1
當客戶端接收到服務器發來的 SYN+ACK 報文后,就會回復 ACK 去通知服務器,同時己方連接狀態從 SYN_SENT 轉換為 ESTABLISHED,表示連接建立成功。服務器端連接成功建立的時間還要再往后,到它收到 ACK 后狀態才變為 ESTABLISHED。
如果服務器沒有收到 ACK,就會一直重發 SYN+ACK 報文。當網絡繁忙、不穩定時,報文丟失就會變嚴重,此時應該調大重發次數。反之則可以調小重發次數。修改重發次數的方法是,調整 tcp_synack_retries 參數:
net.ipv4.tcp_synack_retries = 5
tcp_synack_retries 的默認重試次數是 5 次,與客戶端重發 SYN 類似,它的重試會經歷 1、2、4、8、16 秒,最后一次重試后等待 32 秒,若仍然沒有收到 ACK,才會關閉連接,故共需要等待 63 秒。
服務器收到 ACK 后連接建立成功,此時,內核會把連接從 SYN 半連接隊列中移出,再移入 accept 隊列,等待進程調用 accept 函數時把連接取出來。如果進程不能及時地調用 accept 函數,就會造成 accept 隊列溢出,最終導致建立好的 TCP 連接被丟棄。
實際上,丟棄連接只是 Linux 的默認行為,我們還可以選擇向客戶端發送 RST 復位報文,告訴客戶端連接已經建立失敗。打開這一功能需要將 tcp_abort_on_overflow 參數設置為 1。
net.ipv4.tcp_abort_on_overflow = 0
通常情況下,應當把 tcp_abort_on_overflow 設置為 0,因為這樣更有利於應對突發流量。舉個例子,當 accept 隊列滿導致服務器丟掉了 ACK,與此同時,客戶端的連接狀態卻是 ESTABLISHED,進程就在建立好的連接上發送請求。只要服務器沒有為請求回復 ACK,請求就會被多次重發。如果服務器上的進程只是短暫的繁忙造成 accept 隊列滿,那么當 accept 隊列有空位時,再次接收到的請求報文由於含有 ACK,仍然會觸發服務器端成功建立連接。所以,tcp_abort_on_overflow 設為 0 可以提高連接建立的成功率,只有你非常肯定 accept 隊列會長期溢出時,才能設置為 1 以盡快通知客戶端。
那么,怎樣調整 accept 隊列的長度呢?listen 函數的 backlog 參數就可以設置 accept 隊列的大小。事實上,backlog 參數還受限於 Linux 系統級的隊列長度上限,當然這個上限閾值也可以通過 somaxconn 參數修改:
net.core.somaxconn = 128
當下各監聽端口上的 accept 隊列長度可以通過 ss -ltn 命令查看,但 accept 隊列長度是否需要調整該怎么判斷呢?還是通過 netstat -s 命令給出的統計結果,可以看到究竟有多少個連接因為隊列溢出而被丟棄:
netstat -s | grep "listen queue"
如果持續不斷地有連接因為 accept 隊列溢出被丟棄,就應該調大 backlog 以及 somaxconn 參數。
Linux 下怎么打開 TFO 功能呢?這要通過 tcp_fastopen 參數。由於只有客戶端和服務器同時支持時,TFO 功能才能使用,所以 tcp_fastopen 參數是按比特位控制的。其中,第 1 個比特位為 1 時,表示作為客戶端時支持 TFO;第 2 個比特位為 1 時,表示作為服務器時支持 TFO,所以當 tcp_fastopen 的值為 3 時(比特為 0x11)就表示完全支持 TFO 功能:
net.ipv4.tcp_fastopen = 3
tcp揮手
close與shutdown
close 和 shutdown 函數都可以關閉連接,但這兩種方式關閉的連接,不只功能上有差異,控制它們的 Linux 參數也不相同。close 函數會讓連接變為孤兒連接,shutdown 函數則允許在半關閉的連接上長時間傳輸數據。TCP 之所以具備這個功能,是因為它是全雙工協議,但這也造成四次揮手非常復雜。
安全關閉連接的方式必須通過四次揮手,它由進程調用 close 或者 shutdown 函數發起,這二者都會向對方發送 FIN 報文(shutdown 參數須傳入 SHUT_WR 或者 SHUT_RDWR 才會發送 FIN),區別在於 close 調用后,哪怕對方在半關閉狀態下發送的數據到達主動方,進程也無法接收。
此時,這個連接叫做孤兒連接,如果你用 netstat -p 命令,會發現連接對應的進程名為空。而 shutdown 函數調用后,即使連接進入了 FIN_WAIT1 或者 FIN_WAIT2 狀態,它也不是孤兒連接,進程仍然可以繼續接收數據。關於孤兒連接的概念,下文調優參數時還會用到。
為什么需要四次揮手
當主動方關閉連接時,被動方仍然可以在不調用 close 函數的狀態下,長時間發送數據,此時連接處於半關閉狀態。這一特性是 TCP 的雙向通道互相獨立所致,卻也使得關閉連接必須通過四次揮手才能做到。
其實四次揮手只涉及兩種報文:FIN 和 ACK。FIN 就是 Finish 結束連接的意思,誰發出 FIN 報文,就表示它將不再發送任何數據,關閉這一方向的傳輸通道。ACK 是 Acknowledge 確認的意思,它用來通知對方:你方的發送通道已經關閉。
ACK重試次數
主動方發送 FIN 報文后,連接就處於 FIN_WAIT1 狀態下,該狀態通常應在數十毫秒內轉為 FIN_WAIT2。只有遲遲收不到對方返回的 ACK 時,才能用 netstat 命令觀察到 FIN_WAIT1 狀態。此時,內核會定時重發 FIN 報文,其中重發次數由 tcp_orphan_retries 參數控制(注意,orphan 雖然是孤兒的意思,該參數卻不只對孤兒連接有效,事實上,它對所有 FIN_WAIT1 狀態下的連接都有效),默認值是 0,特指 8 次:
net.ipv4.tcp_orphan_retries = 0
如果 FIN_WAIT1 狀態連接有很多,你就需要考慮降低 tcp_orphan_retries 的值。當重試次數達到 tcp_orphan_retries 時,連接就會直接關閉掉。
對於正常情況來說,調低 tcp_orphan_retries 已經夠用,但如果遇到惡意攻擊,FIN 報文根本無法發送出去。這是由 TCP 的 2 個特性導致的。
- 首先,TCP 必須保證報文是有序發送的,FIN 報文也不例外,當發送緩沖區還有數據沒發送時,FIN 報文也不能提前發送。
- 其次,TCP 有流控功能,當接收方將接收窗口設為 0 時,發送方就不能再發送數據。所以,當攻擊者下載大文件時,就可以通過將接收窗口設為 0,導致 FIN 報文無法發送,進而導致連接一直處於 FIN_WAIT1 狀態
解決這種問題的方案是調整 tcp_max_orphans 參數:
net.ipv4.tcp_max_orphans = 16384
顧名思義,tcp_max_orphans 定義了孤兒連接的最大數量。當進程調用 close 函數關閉連接后,無論該連接是在 FIN_WAIT1 狀態,還是確實關閉了,這個連接都與該進程無關了,它變成了孤兒連接。Linux 系統為防止孤兒連接過多,導致系統資源長期被占用,就提供了 tcp_max_orphans 參數。如果孤兒連接數量大於它,新增的孤兒連接將不再走四次揮手,而是直接發送 RST 復位報文強制關閉。
當連接收到 ACK 進入 FIN_WAIT2 狀態后,就表示主動方的發送通道已經關閉,接下來將等待對方發送 FIN 報文,關閉對方的發送通道。這時,如果連接是用 shutdown 函數關閉的,連接可以一直處於 FIN_WAIT2 狀態。但對於 close 函數關閉的孤兒連接,這個狀態不可以持續太久,而 tcp_fin_timeout 控制了這個狀態下連接的持續時長。
net.ipv4.tcp_fin_timeout = 60
它的默認值是 60 秒。這意味着對於孤兒連接,如果 60 秒后還沒有收到 FIN 報文,連接就會直接關閉。這個 60 秒並不是拍腦袋決定的,它與接下來介紹的 TIME_WAIT 狀態的持續時間是相同的,我們稍后再來回答 60 秒的由來。
TIME_WAIT 是主動方四次揮手的最后一個狀態。當收到被動方發來的 FIN 報文時,主動方回復 ACK,表示確認對方的發送通道已經關閉,連接隨之進入 TIME_WAIT 狀態,等待 60 秒后關閉,為什么呢?我們必須站在整個網絡的角度上,才能回答這個問題。
TIME_WAIT 狀態的連接,在主動方看來確實已經關閉了。然而,被動方沒有收到 ACK 報文前,連接還處於 LAST_ACK 狀態。如果這個 ACK 報文沒有到達被動方,被動方就會重發 FIN 報文。重發次數仍然由前面介紹過的 tcp_orphan_retries 參數控制。
如果主動方不保留 TIME_WAIT 狀態,會發生什么呢?此時連接的端口恢復了自由身,可以復用於新連接了。然而,被動方的 FIN 報文可能再次到達,這既可能是網絡中的路由器重復發送,也有可能是被動方沒收到 ACK 時基於 tcp_orphan_retries 參數重發。這樣,正常通訊的新連接就可能被重復發送的 FIN 報文誤關閉。保留 TIME_WAIT 狀態,就可以應付重發的 FIN 報文,當然,其他數據報文也有可能重發,所以 TIME_WAIT 狀態還能避免數據錯亂。
我們再回過頭來看看,為什么 TIME_WAIT 狀態要保持 60 秒呢?這與孤兒連接 FIN_WAIT2 狀態默認保留 60 秒的原理是一樣的,因為這兩個狀態都需要保持 2MSL 時長。MSL 全稱是 Maximum Segment Lifetime,它定義了一個報文在網絡中的最長生存時間(報文每經過一次路由器的轉發,IP 頭部的 TTL 字段就會減 1,減到 0 時報文就被丟棄,這就限制了報文的最長存活時間)。
為什么是 2 MSL 的時長呢?這其實是相當於至少允許報文丟失一次。比如,若 ACK 在一個 MSL 內丟失,這樣被動方重發的 FIN 會在第 2 個 MSL 內到達,TIME_WAIT 狀態的連接可以應對。為什么不是 4 或者 8 MSL 的時長呢?你可以想象一個丟包率達到百分之一的糟糕網絡,連續兩次丟包的概率只有萬分之一,這個概率實在是太小了,忽略它比解決它更具性價比。
因此,TIME_WAIT 和 FIN_WAIT2 狀態的最大時長都是 2 MSL,由於在 Linux 系統中,MSL 的值固定為 30 秒,所以它們都是 60 秒。
雖然 TIME_WAIT 狀態的存在是有必要的,但它畢竟在消耗系統資源,比如 TIME_WAIT 狀態的端口就無法供新連接使用。怎樣解決這個問題呢?
Linux 提供了 tcp_max_tw_buckets 參數,當 TIME_WAIT 的連接數量超過該參數時,新關閉的連接就不再經歷 TIME_WAIT 而直接關閉。
net.ipv4.tcp_max_tw_buckets = 5000
當服務器的並發連接增多時,相應地,同時處於 TIME_WAIT 狀態的連接數量也會變多,此時就應當調大 tcp_max_tw_buckets 參數,減少不同連接間數據錯亂的概率。
當然,tcp_max_tw_buckets 也不是越大越好,畢竟內存和端口號都是有限的。有沒有辦法讓新連接復用 TIME_WAIT 狀態的端口呢?如果服務器會主動向上游服務器發起連接的話,就可以把 tcp_tw_reuse 參數設置為 1,它允許作為客戶端的新連接,在安全條件下使用 TIME_WAIT 狀態下的端口。
net.ipv4.tcp_tw_reuse = 1
當然,要想使 tcp_tw_reuse 生效,還得把 timestamps 參數設置為 1,它滿足安全復用的先決條(對方也要打開 tcp_timestamps ):
net.ipv4.tcp_timestamps = 1
老版本的 Linux 還提供了 tcp_tw_recycle 參數,它並不要求 TIME_WAIT 狀態存在 60 秒,很容易導致數據錯亂,不建議設置為 1:
net.ipv4.tcp_tw_recycle = 0
所以在 Linux 4.12 版本后,直接取消了這一參數。
tcp緩沖區
概述
如果你在 Linux 系統中用 free 命令查看內存占用情況,會發現一欄叫做 buff/cache,它是系統內存,似乎與應用進程無關。但每當進程新建一個 TCP 連接,buff/cache 中的內存都會上升 4K 左右。而且,當連接傳輸數據時,就遠不止增加 4K 內存了。這樣,幾十萬並發連接,就在進程內存外又增加了 GB 級別的系統內存消耗。
這是因為 TCP 連接是由內核維護的,內核為每個連接建立的內存緩沖區,既要為網絡傳輸服務,也要充當進程與網絡間的緩沖橋梁。如果連接的內存配置過小,就無法充分使用網絡帶寬,TCP 傳輸速度就會很慢;如果連接的內存配置過大,那么服務器內存會很快用盡,新連接就無法建立成功。因此,只有深入理解 Linux 下 TCP 內存的用途,才能正確地配置內存大小。
我們知道,TCP 必須保證每一個報文都能夠到達對方,它采用的機制就是:報文發出后,必須收到接收方返回的 ACK 確認報文(Acknowledge 確認的意思)。如果在一段時間內(稱為 RTO,retransmission timeout)沒有收到,這個報文還得重新發送,直到收到 ACK 為止。可見,TCP 報文發出去后,並不能立刻從內存中刪除,因為重發時還需要用到它。由於 TCP 是由內核實現的,所以報文存放在內核緩沖區中,這也是高並發下 buff/cache 內存增加很多的原因。
接收窗口
接收方根據它的緩沖區,可以計算出后續能夠接收多少字節的報文,這個數字叫做接收窗口。當內核接收到報文時,必須用緩沖區存放它們,這樣剩余緩沖區空間變小,接收窗口也就變小了;當進程調用 read 函數后,數據被讀入了用戶空間,內核緩沖區就被清空,這意味着主機可以接收更多的報文,接收窗口就會變大。
TCP 報文頭部中的窗口字段,就可以起到通知的作用。當發送方從報文中得到接收方的窗口大小時,就明白了最多能發送多少字節的報文,這個數字被稱為發送方的發送窗口。如果不考慮下一講將要介紹的擁塞控制,發送方的發送窗口就是接收方的接收窗口(由於報文有傳輸時延,t1 時刻的接收窗口在 t2 時刻才能到達發送端,因此這兩個窗口並不完全等價)。
從上圖中可以看到,窗口字段只有 2 個字節,因此它最多能表達 216 即 65535 字節大小的窗口(之所以不是 65536,是因為窗口可以為 0,此時叫做窗口關閉,上一講提到的關閉連接時讓 FIN 報文發不出去,以致於服務器的連接都處於 FIN_WAIT1 狀態,就是通過窗口關閉技術實現的),這在 RTT 為 10ms 的網絡中也只能到達 6MB/s 的最大速度,在當今的高速網絡中顯然並不夠用。
RFC1323 定義了擴充窗口的方法,但 Linux 中打開這一功能,需要把 tcp_window_scaling 配置設為 1,此時窗口的最大值可以達到 1GB(230)。
net.ipv4.tcp_window_scaling = 1
這樣看來,只要進程能及時地調用 read 函數讀取數據,並且接收緩沖區配置得足夠大,那么接收窗口就可以無限地放大,發送方也就無限地提升發送速度。很顯然,這是不可能的,因為網絡的傳輸能力是有限的,當發送方依據發送窗口,發送超過網絡處理能力的報文時,路由器會直接丟棄這些報文。因此,緩沖區的內存並不是越大越好。
滑動窗口
緩沖區的調節范圍是可以設置的。先來看發送緩沖區,它的范圍通過 tcp_wmem 配置
net.ipv4.tcp_wmem = 4096 16384 4194304
其中,第 1 個數值是動態范圍的下限,第 3 個數值是動態范圍的上限。而中間第 2 個數值,則是初始默認值。發送緩沖區完全根據需求自行調整。比如,一旦發送出的數據被確認,而且沒有新的數據要發送,就可以把發送緩沖區的內存釋放掉。而接收緩沖區的調整就要復雜一些,先來看設置接收緩沖區范圍的 tcp_rmem:
net.ipv4.tcp_rmem = 4096 87380 6291456
它的數值與 tcp_wmem 類似,第 1、3 個值是范圍的下限和上限,第 2 個值是初始默認值。發送緩沖區自動調節的依據是待發送的數據,接收緩沖區由於只能被動地等待接收數據,它該如何自動調整呢?可以依據空閑系統內存的數量來調節接收窗口。如果系統的空閑內存很多,就可以把緩沖區增大一些,這樣傳給對方的接收窗口也會變大,因而對方的發送速度就會通過增加飛行報文來提升。反之,內存緊張時就會縮小緩沖區,這雖然會減慢速度,但可以保證更多的並發連接正常工作。發送緩沖區的調節功能是自動開啟的,而接收緩沖區則需要配置 tcp_moderate_rcvbuf 為 1 來開啟調節功能:
net.ipv4.tcp_moderate_rcvbuf = 1
接收緩沖區調節時,怎么判斷空閑內存的多少呢?這是通過 tcp_mem 配置完成的:net.ipv4.tcp_mem = 88560 118080 177120tcp_mem 的 3 個值,是 Linux 判斷系統內存是否緊張的依據。當 TCP 內存小於第 1 個值時,不需要進行自動調節;在第 1 和第 2 個值之間時,內核開始調節接收緩沖區的大小;大於第 3 個值時,內核不再為 TCP 分配新內存,此時新連接是無法建立的。在高並發服務器中,為了兼顧網速與大量的並發連接,我們應當保證緩沖區的動態調整上限達到帶寬時延積,而下限保持默認的 4K 不變即可。而對於內存緊張的服務而言,調低默認值是提高並發的有效手段。
同時,如果這是網絡 IO 型服務器,那么,調大 tcp_mem 的上限可以讓 TCP 連接使用更多的系統內存,這有利於提升並發能力。需要注意的是,tcp_wmem 和 tcp_rmem 的單位是字節,而 tcp_mem 的單位是頁面大小。而且,千萬不要在 socket 上直接設置 SO_SNDBUF 或者 SO_RCVBUF,這樣會關閉緩沖區的動態調整功能。
擁塞控制
慢啟動
讓發送速度變慢是通過引入擁塞窗口(全稱為 congestion window,縮寫為 CWnd,類似地,接收窗口叫做 rwnd,發送窗口叫做 swnd)實現的,它用於避免網絡出現擁塞。如果不考慮網絡擁塞,發送窗口就等於對方的接收窗口,而考慮了網絡擁塞后,發送窗口則應當是擁塞窗口與對方接收窗口的最小值。
可以根據網絡狀況和傳輸對象的大小,調整初始擁塞窗口的大小。調整前,先要清楚你的服務器現在的初始擁塞窗口是多大。你可以通過 ss 命令查看當前擁塞窗口:
ss -nli|fgrep cwnd
通過 ip route change 命令修改初始擁塞窗口:
ip route | while read r; do ip route change $r initcwnd 10
done當然,更大的初始擁塞窗口以及指數級的提速,連接很快就會遭遇網絡擁塞,從而導致慢啟動階段的結束。
在規定時間內沒有收到 ACK 報文,這說明報文丟失了,網絡出現了嚴重的擁塞,必須先降低發送速度,再進入擁塞避免階段。不同的擁塞控制算法降低速度的幅度並不相同,比如 CUBIC 算法會把擁塞窗口降為原先的 0.8 倍(也就是發送速度降到 0.8 倍)。此時,我們知道了多大的窗口會導致擁塞,因此可以把慢啟動閾值設為發生擁塞前的窗口大小
擁塞避免
發送方已經達到了曾經發生網絡擁塞的速度(擁塞窗口達到了慢啟動閾值),接下來發生擁塞的概率很高,所以進入擁塞避免階段,此時擁塞窗口不能再以指數方式增長,而是要以線性方式增長。接下來,擁塞窗口會以每個 RTT 增加 1 個 MSS 的方式,代替慢啟動階段每收到 1 個 ACK 就增加 1 個 MSS 的方式。
快速重傳
當連續收到 3 個重復 ACK 時,發送方便得到了網絡發生擁塞的明確信號,通過重復 ACK 報文的序號,我們知道丟失了哪個報文,這樣,不等待定時器的觸發,立刻重發丟失的報文,可以讓發送速度下降得慢一些,這就是快速重傳算法。
快速恢復
每當接收方從網絡中取出一個報文,發送方就可以增加一個報文。當發送方接收到重復 ACK 時,可以推斷有失序報文離開了網絡,到達了接收方的緩沖區,因此可以再多發送一個報文
修改擁塞控制算法
慢啟動、擁塞避免、快速重傳、快速恢復,共同構成了擁塞控制算法。Linux 上提供了更改擁塞控制算法的配置,你可以通過 tcp_available_congestion_control 配置查看內核支持的算法列表:
sysctl -a |grep tcp_available_congestion_control
再通過 tcp_congestion_control 配置選擇一個具體的擁塞控制算法:
net.ipv4.tcp_congestion_control = cubic
擁塞控制是控制網絡流量的算法,主機間會互相影響,在生產環境更改之前必須經過完善的測試。
BBR 擁塞控制算法
當緩沖隊列為空時,傳輸速度最快。一旦隊列開始積壓,每個報文的傳輸時間需要增加排隊時間,網速就變慢了。而當隊列溢出時,才會出現丟包,基於丟包的擁塞控制算法在這個時間點進入擁塞避免階段,顯然太晚了。因為升高的網絡時延降低了用戶體驗,而且從丟包到重發這段時間,帶寬也會出現下降。
進行擁塞控制的最佳時間點,是緩沖隊列剛出現積壓的時刻,此時,網絡時延會增高,但帶寬維持不變,這兩個數值的變化可以給出明確的擁塞信號,如下圖所示:
這種以測量帶寬、時延來確定擁塞的方法,在丟包率較高的網絡中應用效果尤其好。2016 年 Google 推出的 BBR 算法(全稱 Bottleneck Bandwidth and Round-trip propagation time),就是測量驅動的擁塞控制算法