1、概述linux
咱們以前介紹過rtt、ssthresh等變量,這些變量通常在TCP鏈接創建的時候有個初始值,而後隨着TCP的數據交互逐漸調整到適應對應的網絡狀態的值。可是若是每次TCP創建鏈接都依靠默認初始值逐漸調整,那麼可能須要一段時間才能調整到合適值,這顯然會下降TCP性能,對於這種場景一種優化方案就是destination metrics。git
RFC2140中描述,若是新創建的鏈接從已經關閉的鏈接緩存的狀態信息中獲取初始化信息,稱呼爲temporal sharing,若是新創建的鏈接從其餘已創建的TCP鏈接獲取初始化信息稱爲ensemble sharing。linux中實現的是temporal sharing。RFC3124中提出一個Congestion Manager,congestion manager是一個操做系統服務,TCP鏈接能夠從這個服務中獲取鏈接相關信息。一樣linux也是使用destination metrics來實現的Congestion Manager。
web
2、Linux實現簡介緩存
destination metrics是指TCP根據用戶預設的一些值或者以前TCP鏈接緩存的一些值來初始化相關的狀態變量。也就是說destination metric其實能夠分爲兩部分,一部分是用戶預設的值,另一部分則是以前TCP鏈接緩存緩存的值,後面這一部分也稱爲TCP metrics。顯然一個TCP鏈接的網絡狀態(如RTT時延、擁塞窗口cwnd)只與目的IP地址強相關,而與傳輸層的端口並沒有太大關係。TCP metric就是以IP地址來緩存的,每一個IP地址對應一個緩存條目。通常來講,當TCP鏈接創建的時候,若是要初始化一個對應的狀態變量,首先會查詢TCP metrics緩存中是否存在目標地址的metric,若是存在則根據metric信息來初始化鏈接的參數,若是不存在則會在TCP metrics緩存中建立對應這個IP地址的TCP metric,建立的時候還會根據destination metrics的設置來初始化tcp metrics。TCP鏈接在關閉的時候也會嘗試把最新的鏈接狀態信息寫入到TCP metrics緩存中。網絡
上面說了這麼多廢話,那麼linux中到底有那些置destination metrics、tcp metrics呢?首先咱們說一下destination metrics包含那些狀態信息,與TCP關係比較大的幾個有mtu、 window、 rtt、 rttvar、 rto_min、 ssthresh、 cwnd、 initcwnd、 initrwnd、 quickack、 reordering、congctl、 advmss,其中標識爲紅色的5個即爲TCP metrics。咱們在以前的文章中已經演示過mtu、 ssthresh、 initcwnd、 quickack、 congctl、window等destination metrics的設置和影響。這些參數的詳細解釋請查閱man ip-route和man ip-tcp_metrics。其中須要注意的cwnd這個metric,這個值表示TCP鏈接擁塞窗口cwnd的上限,而不是擁塞窗口的初始值,metric中的cwnd更名爲cwnd_clamp,顯然更合適一些,另外就是這個metric的設置須要加lock關鍵字才生效。另外內核目前並不會根據rttvar來初始化狀態變量了。tcp
下面咱們說一下設置destination metrics時候,加不加lock的關係,其中rtt、rttvar、ssthresh、cwnd、reordering這5個TCP metrics能夠在設置的時候添加lock關鍵字,TCP鏈接在初始創建時候若是沒有對應目標IP地址的TCP metric,則會根據設置值來初始化對應這條IP 地址的TCP metric,若是添加了lock關鍵字,那麼隨後這個TCP鏈接關閉的時候就不會更新對應的metric,若是沒有添加lock字段,而且tcp_no_metrics_save參數爲0,那麼就會根據當前狀態來更新TCP metric緩存。性能
最後說一下,使用ip route設置的destination metrics並非當即生效的,上面咱們說了TCP鏈接創建的時候會先從TCP metrics緩存中初始化鏈接相關的狀態信息,若是沒有TCP metric緩存纔會從ip route設置中讀取參數配置來創建TCP metrics。也就是說destination metrics爲TCP metrics提供了初始值,一旦緩存中的TCP metric有效,就不會ip route設置的destination metric來初始化TCP鏈接了。那麼TCP metric何時會失效呢?若是讀取TCP metric緩存的時候發現距離上次更新這條IP地址的metric時間超過一小時,那麼這個TCP metric就無效了,就須要從destination metric來從新初始化TCP metric。測試
3、示例優化
下面咱們經過示例來看一下destination metric和tcp metric的常見操做及相關特性
ui
#經過ip route add命令能夠添加路由,而後針對這條路由的目標地址設置destination metrics****@Inspiron:****/04_cc/tcp17# ip route add local 127.0.0.2 dev lo congctl reno initcwnd 5 ssthresh lock 4 #設置後查看一下相關信息****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2#下面一行對應 ip route show table all | grep 127.0.0.2 該命令能夠查看destination metrics設置local 127.0.0.2 dev lo table local scope host ssthresh lock 50 cwnd lock 9 initcwnd 5 congctl reno#下面一行對應命令 ss -i sport = 9877 查看源端口爲9877 的tcp鏈接信息Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port #下面一行對應 ip tcp_metrics show 127.0.0.2 該命令能夠查看tcp metrics設置,此時咱們雖然設置了destination metric,可是由於#尚未創建到127.0.0.2的tcp鏈接,於是尚未tcp metric信息RTNETLINK answers: No such process#啓動server server端在與client創建鏈接後會休眠30ms 而後連續發送15個數據包 每一個數據包的大小爲50bytes,發送間隔爲3ms#數據包發送完後休眠30s,而後關閉與client的鏈接****@Inspiron:****/04_cc/tcp17# ./server.out &[1] 24091#client創建與server端的tcp鏈接,client對於每一個收到的數據包都會回覆一個ACK確認包****@Inspiron:****/04_cc/tcp17# ./client.out > rst_client &[2] 24093****@Inspiron:****/04_cc/tcp17# conn setup sleep 30s#上面一行是server端打印,提示已經與client創建鏈接,開始休眠30s,此時咱們再次查看信息****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2#能夠看到路由表中的destination metric的設置值是靜態不變的local 127.0.0.2 dev lo table local scope host ssthresh lock 50 cwnd lock 9 initcwnd 5 congctl reno#如今能夠查看到server端與client端的鏈接了 注意路由表中設置了ssthresh lock 50,可是下面的鏈接信息告訴咱們server端ssthresh=9,緣由就是路由表中設置了cwnd lock 9,cwnd這個metric生效的時候,TCP鏈接會設置ssthresh=min(ssthresh,cwnd)=9Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.258/25.129 mss:50 cwnd:5 ssthresh:9 segs_in:2 send 39.8Kbps lastsnd:6064456 lastack:6064456 pacing_rate 47.8Kbps rcv_space:43690#能夠看到在新創建TCP鏈接後,就會初始化一個tcp metric,初始值來自路由表中靜態的destination metric 127.0.0.2 age 4.760sec ssthresh 50 cwnd 9 source 127.0.0.1****@Inspiron:****/04_cc/tcp17# ****@Inspiron:****/04_cc/tcp17# server send startserver send end sleep 30s#server端發送數據後再次查看相關信息****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh lock 50 cwnd lock 9 initcwnd 5 congctl reno#因爲路由表中設置cwnd lock 9,限制了擁塞窗口最大值只能到9,注意路由表中的cwnd限制的是擁塞窗口的最大值,從下面鏈接信息能夠看到cwnd=9Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.271/0.392 mss:50 cwnd:9 ssthresh:9 bytes_acked:750 segs_out:15 segs_in:17 send 71.6Kbps lastsnd:6094556 lastack:6094604 pacing_rate 85.9Kbps rcv_space:43690#從下面的age能夠看到server端並無更新tcp metrics127.0.0.2 age 39.528sec ssthresh 50 cwnd 9 source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server sockfd close[2]+ 已完成 ./client.out > rst_client#server端鏈接關閉,再次查看相關信息****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh lock 50 cwnd lock 9 initcwnd 5 congctl reno#已經查不到源端口爲9877的tcp鏈接Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port #能夠從age信息裏面看到tcp 鏈接關閉的時候更新了tcp metric,可是能夠看到ssthresh和cwnd的值並無更新這個就是lock的做用,#被lock的tcp metric是不會被tcp鏈接更新的 另外能夠看到rtt和rttvar這兩個metric發生了更新,可是reordering並無更新,也就是說TCP#鏈接關閉的時候還要檢測當前狀態的有效性來決定是否更新相關的metric。127.0.0.2 age 6.116sec ssthresh 50 cwnd 9 rtt 50281us rttvar 50281us source 127.0.0.1#接下來咱們更新路由表destination metric的cwnd和ssthresh兩個metrics****@Inspiron:****/04_cc/tcp17# ip route change local 127.0.0.2 dev lo initcwnd 5 cwnd lock 8 ssthresh lock 40 congctl reno****@Inspiron:****/04_cc/tcp17# ./client.out > rst_client &[2] 24190****@Inspiron:****/04_cc/tcp17# conn setup sleep 30s#從新進行測試 鏈接創建****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh lock 40 cwnd lock 8 initcwnd 5 congctl reno#從下面的鏈接信息中的ssthresh值能夠看到,路由表中的設置並無生效,鏈接創建的時候是從tcp metrics中讀取的鏈接緩存信息Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:248 rtt:50.225/25.112 mss:50 cwnd:5 ssthresh:9 segs_in:2 send 39.8Kbps lastsnd:6393360 lastack:6393360 pacing_rate 47.8Kbps rcv_space:43690#能夠看到此時的tcp metric中cwnd和ssthresh並無從destination中更新,緣由就是上面說的#只有TCP metrics過時後或者初始創建時候纔會從destination metric更新 127.0.0.2 age 273.460sec ssthresh 50 cwnd 9 rtt 50281us rttvar 50281us source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server send startserver send end sleep 30s****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh lock 40 cwnd lock 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.306/0.377 mss:50 cwnd:9 ssthresh:9 bytes_acked:750 segs_out:15 segs_in:17 send 71.6Kbps lastsnd:6423460 lastack:6423508 pacing_rate 85.9Kbps rcv_space:43690127.0.0.2 age 303.164sec ssthresh 50 cwnd 9 rtt 50281us rttvar 50281us source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server sockfd close[2]+ 已完成 ./client.out > rst_client****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh lock 40 cwnd lock 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port #鏈接關閉後 再次更新了tcp metrics 這一點能夠從age看到 也能夠從rtt、rttvar中看到 127.0.0.2 age 4.800sec ssthresh 50 cwnd 9 rtt 50312us rttvar 37785us source 127.0.0.1#接下來咱們看一下不填加lock關鍵字的效果****@Inspiron:****/04_cc/tcp17# ip route change local 127.0.0.2 dev lo initcwnd 5 cwnd 8 ssthresh 40 congctl reno****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port 127.0.0.2 age 91.740sec ssthresh 50 cwnd 9 rtt 50312us rttvar 37785us source 127.0.0.1#爲了讓路由表中的destination metric生效,須要使用下面的命令從tcp metrics中刪除127.0.0.2對應的緩存信息****@Inspiron:****/04_cc/tcp17# ip tcp_metrics flush 127.0.0.2****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port #能夠看到TCP metrics中已經沒有對應127.0.0.2的緩存了RTNETLINK answers: No such process****@Inspiron:****/04_cc/tcp17# ./client.out > rst_client &[2] 24265****@Inspiron:****/04_cc/tcp17# conn setup sleep 30s#client再次創建鏈接 進行新的測試****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl reno#從下面的鏈接信息中能夠看到,cwnd雖然設置了 可是並無限制到server端的擁塞窗口,由於若是cwnd這個metric設置生效的話,那麼ssthresh應爲8Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.243/25.121 mss:50 cwnd:5 ssthresh:40 segs_in:2 send 39.8Kbps lastsnd:6574128 lastack:6574128 pacing_rate 79.6Kbps rcv_space:43690#鏈接創建後建立新的tcp metric,能夠看到其中的初始值來自與路由表中destination metrics設置127.0.0.2 age 2.980sec ssthresh 40 cwnd 8 source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server send startserver send end sleep 30s****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl reno#server端發送數據後,能夠看到cwnd已經增加到了20,超越了路由表中cwnd的設置。說明沒有添加lock關鍵字的cwnd metric並無生效Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.273/0.372 mss:50 cwnd:20 ssthresh:40 bytes_acked:750 segs_out:15 segs_in:17 send 159.1Kbps lastsnd:6604192 lastack:6604244 pacing_rate 191.0Kbps rcv_space:43690127.0.0.2 age 34.248sec ssthresh 40 cwnd 8 source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server sockfd close[2]+ 已完成 ./client.out > rst_client****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port #由於路由表中的cwnd metric沒有lock,所以鏈接關閉的時候會更新cwnd,實際上更新了也沒用,由於新的tcp鏈接並不會使用127.0.0.2 age 3.064sec ssthresh 40 cwnd 24 rtt 50275us rttvar 50275us source 127.0.0.1#接着進行新的測試,下面命令指示client丟掉第8個數據包 形成server端快速重傳,從而更新ssthresh****@Inspiron:****/04_cc/tcp17# ./client.out 8 >rst_client &[2] 24606****@Inspiron:****/04_cc/tcp17# conn setup sleep 30s****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl reno#新創建鏈接的擁塞窗口cwnd始終從路由表中的initcwnd初始化 而不是從tcp metric或者destination metric中的cwnd metric更新,#再說一次 cwnd這個metric限制的是擁塞窗口的最大值,並且只有在加lock關鍵字設置後纔會生效Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:50.287/25.143 mss:50 cwnd:5 ssthresh:40 segs_in:2 send 39.8Kbps lastsnd:7230928 lastack:7230928 pacing_rate 79.5Kbps rcv_space:43690127.0.0.2 age 601.280sec ssthresh 40 cwnd 24 rtt 50275us rttvar 50275us source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server send startserver send end sleep 30s****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl reno#快速重傳及快速恢復後,cwnd=7,ssthresh=6Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 127.0.0.1:9877 127.0.0.2:webmin reno wscale:0,7 rto:252 rtt:49.999/0.534 mss:50 cwnd:7 ssthresh:6 bytes_acked:750 segs_out:16 segs_in:17 send 56.0Kbps lastsnd:7261040 lastack:7261088 pacing_rate 67.2Kbps retrans:0/1 rcv_space:43690127.0.0.2 age 630.636sec ssthresh 40 cwnd 24 rtt 50275us rttvar 50275us source 127.0.0.1****@Inspiron:****/04_cc/tcp17# server sockfd close[2]+ 已完成 ./client.out 8 > rst_client****@Inspiron:****/04_cc/tcp17# ip route show table all | grep 127.0.0.2; ss -i sport = 9877;ip tcp_metrics show 127.0.0.2local 127.0.0.2 dev lo table local scope host ssthresh 40 cwnd 8 initcwnd 5 congctl renoNetid State Recv-Q Send-Q Local Address:Port Peer Address:Port #鏈接關閉後 能夠看到cwnd和ssthresh都已經更新了 127.0.0.2 age 4.996sec ssthresh 6 cwnd 15 rtt 50244us rttvar 37823us source 127.0.0.1****@Inspiron:****/04_cc/tcp17#
補充說明:
一、實際在TCP metrics中有7個參數,可是有兩個是冗餘的,主要是爲了接口兼容保留下來的,參考kernel修改https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=740b0f1841f6e39085b711d41db9ffb07198682b&dt=1,Iproute2配套修改 https://patchwork.ozlabs.org/patch/386544/,Iproute2即爲ss命令的程序包,Iproute2還包含其餘的一些命令,用來取代net-tools程序包,net-tools程序包就是netstat/ip等程序所在的軟件包。
二、TCP metrics枚舉tcp_metric_index、TCP_METRICS_ATTR_UNSPEC,destination metric枚舉RTAX_UNSPEC
三、鏈接創建時候更新metric信息初始化鏈接狀態變量tcp_init_metrics,鏈接關閉時候更新tcp metrics代碼點tcp_update_metrics
四、顯然TCPIP詳解中對於destination metric中,cwnd的解釋也是錯誤的了。