[转载] tcp数据重传时间细节探秘及数据中心优化

原文: http://weibo.com/p/1001603821691477346388

在数据中心网络内,机器之间数据传输的往返时间(rtt)一般在10ms以内,为此调内部服务的超时时间一般会设置成50ms、200ms、500ms等,如果在传输过程中出现丢包,这样的服务超时时间,tcp层有机会发现并重传一次数据么?如果设置成200ms以内,答案是没有机会,原因是linux系统下第一次重传时间等于传输的往返时间上至少加上200ms的预测偏差值,即如果rtt值是7ms,第一次重传超时时间至少是207ms,这样如果对某个接口的超时时间设置成200ms以内, 即便是rtt时间很小,仍然无法容忍一次丢包,因为在tcp发现丢包之前,该接口已经超时了。

本文针对linux系统tcp数据包第一次重传时间的计算进行探究,结果会让人大吃一惊。提出的优化方法,理论上能够降低内部服务调用时延和出错量。

tcp发送数据包后,会设置一个定时器,到期后如果还没有收到对方的回复(ack),就会重传数据包。从发出数据包到第一次重传之间的间隔时间称为retransmission timeout(RTO),rto由数据包的往返时间(rtt)加上rtt的预测偏差(波动值)计算出来。

即 rto = srtt + rttvar,其中srtt是rtt的平滑值,而rttvar是波动值,代表可能的预测偏差。

接下来我们做一个试验。

先ping一下www.weibo.com,看一下数据包的往返时间,如下:

[xiaohong@localhost ~]$ ping www.weibo.com

PING www.weibo.com (123.125.104.197) 56(84) bytes of data.

64 bytes from 123.125.104.197: icmp_seq=1 ttl=55 time=3.65 ms

64 bytes from 123.125.104.197: icmp_seq=2 ttl=55 time=3.38 ms

64 bytes from 123.125.104.197: icmp_seq=3 ttl=55 time=4.34 ms

64 bytes from 123.125.104.197: icmp_seq=4 ttl=55 time=7.82 ms

再看一下tcp对到www.weibo.com的rtt相关数据,下面的命令是针对centos7(如果是以下的版本,运行的命令是ip route list tab cache)如下:

[xiaohong@localhost ~]$ sudo ip tcp_metrics

123.125.104.197 age 22.255sec rtt 7375us rttvar 7250us cwnd 10

由上面看出,平滑后的rtt值约为7ms,rttvar约为7ms,那按理说rto值应该是14ms左右,也就是等14ms后,如果没有收到对方的响应,就会重传数据。实际的情况会是这样么?

在一个命令窗口里,运行下面的命令:

[xiaohong@localhost ~]$ nc www.weibo.com 80

GET / HTTP/1.1

Host: www.weibo.com

Connection:

同时再开一个命令行窗口里,运行下面的命令:

[xiaohong@localhost iproute2-3.19.0]$ ss -eipn ‘( dport = :www )‘

tcp   ESTAB      0      0              10.209.80.111:56486       123.125.104.197:80     users:(("nc",1713,3)) uid:1000 ino:14243 sk:ffff88002c992d00 <->

ts sack cubic wscale:0,7 rto:207 rtt:7.375/7.25 mss:1448 cwnd:10 send 15.7Mbps rcv_space:14600

从上面的结果可以看出,实际的rto值是207ms,相当于rtt值加上200ms,为什么呢?

下面从内核tcp源代码中分析原因。

设置超时时间的函数是tcp_set_rto,在net/ipv4/tcp_input.c中,如下:

static inline void tcp_set_rto(struct sock *sk)

{

const struct tcp_sock *tp = tcp_sk(sk);

inet_csk(sk)->icsk_rto = __tcp_set_rto(tp);

tcp_bound_rto(sk);

}

可以看出,重传的定时值isck_rto实际上是调用 __tcp_set_rto,接着看它的源码,这个在文件include/tcp/net/tcp.h中,如下:

static inline u32 __tcp_set_rto(const struct tcp_sock *tp)

{

return (tp->srtt >> 3) + tp->rttvar;

}

为了避免浮点数运算,rtt乘以8保存在socket数据结构中,从代码可以确认:

icsk_rto  = srtt + rttvar

而计算和影响srtt和rttvar的函数是tcp_rtt_estimator,在文件net/ipv4/tcp_input.c中,代码如下:

static void tcp_rtt_estimator(struct sock *sk, const __u32 mrtt)

{

struct tcp_sock *tp = tcp_sk(sk);

long m = mrtt; /* RTT */

/*      The following amusing code comes from Jacobson‘s

*      article in SIGCOMM ‘88.  Note that rtt and mdev

*      are scaled versions of rtt and mean deviation.

*      This is designed to be as fast as possible

*      m stands for "measurement".

*

*      On a 1990 paper the rto value is changed to:

*      RTO = rtt + 4 * mdev

*

* Funny. This algorithm seems to be very broken.

* These formulae increase RTO, when it should be decreased, increase

* too slowly, when it should be increased quickly, decrease too quickly

* etc. I guess in BSD RTO takes ONE value, so that it is absolutely

* does not matter how to _calculate_ it. Seems, it was trap

* that VJ failed to avoid. 8)

*/

if (m == 0)

m = 1;

if (tp->srtt != 0) {

m -= (tp->srtt >> 3);   /* m is now error in rtt est */

tp->srtt += m;          /* rtt = 7/8 rtt + 1/8 new */

if (m < 0) {

m = -m;         /* m is now abs(error) */

m -= (tp->mdev >> 2);   /* similar update on mdev */

/* This is similar to one of Eifel findings.

* Eifel blocks mdev updates when rtt decreases.

* This solution is a bit different: we use finer gain

* for mdev in this case (alpha*beta).

* Like Eifel it also prevents growth of rto,

* but also it limits too fast rto decreases,

* happening in pure Eifel.

*/

if (m > 0)

m >>= 3;

} else {

m -= (tp->mdev >> 2);   /* similar update on mdev */

}

tp->mdev += m;          /* mdev = 3/4 mdev + 1/4 new */

if (tp->mdev > tp->mdev_max) {

tp->mdev_max = tp->mdev;

if (tp->mdev_max > tp->rttvar)

tp->rttvar = tp->mdev_max;

}

if (after(tp->snd_una, tp->rtt_seq)) {

if (tp->mdev_max < tp->rttvar)

tp->rttvar -= (tp->rttvar - tp->mdev_max) >> 2;

tp->rtt_seq = tp->snd_nxt;

tp->mdev_max = tcp_rto_min(sk);

}

} else {

/* no previous measure. */

tp->srtt = m << 3;      /* take the measured time to be rtt */

tp->mdev = m << 1;      /* make sure rto = 3*rtt */

tp->mdev_max = tp->rttvar = max(tp->mdev, tcp_rto_min(sk));

tp->rtt_seq = tp->snd_nxt;

}

}

从上面的代码可以看出,srtt  = 7/8 old srtt + 1/8 new rtt,这个跟RFC一致,没有啥可以说的。

获得第一个往返时间数据时(一般是建立连接完成时,对于客户端就是发出sync请求,收到服务端的回应时,而对于服务器端就是发出syc+ack后,收到客户端的ack时)的计算分析如下:

} else {

/* no previous measure. */

/* 以前没有rtt的数据,这是收到第一个rtt的样本数据的代码逻辑 */

/* m是本次的rtt值,乘以8保存到 srtt中 */

tp->srtt = m << 3;      /* take the measured time to be rtt */

/* rtt的初始偏差值mdev是 2倍rtt值 */

tp->mdev = m << 1;      /* make sure rto = 3*rtt */

/* 设置rttvar和rtt偏差的最大值mdev_max这两者的初始值  */

/*   2倍的rtt值,tcp_rto_min之间,那个大,就选那个 */

tp->mdev_max = tp->rttvar = max(tp->mdev, tcp_rto_min(sk));

tp->rtt_seq = tp->snd_nxt;

}

再看tcp_rto_min的代码,在文件include/net/tcp.h中:

static inline u32 tcp_rto_min(struct sock *sk)

{

struct dst_entry *dst = __sk_dst_get(sk);

u32 rto_min = TCP_RTO_MIN; /* 200ms */

if (dst && dst_metric_locked(dst, RTAX_RTO_MIN))

rto_min = dst_metric_rtt(dst, RTAX_RTO_MIN);

return rto_min;

}

结合起来看,如果第一个数据包往返时间在100ms以内,rtt预测初始的偏差值就固定为200ms,当数据包往返时间超过100ms,rtt预测偏差的初始值是2倍的rtt值,也就是说rttvar最小值是200ms。

接着分析计算和影响srtt和rttvar的函数是tcp_rtt_estimator的代码:

if (tp->mdev > tp->mdev_max) {

/* 跟踪rtt的偏差,记录偏差最大值mdev_max */

tp->mdev_max = tp->mdev;

if (tp->mdev_max > tp->rttvar) /* 偏差最大值大于 rttvar时,rttvar跟着变大 */

tp->rttvar = tp->mdev_max;

}

if (after(tp->snd_una, tp->rtt_seq)) {

/* 偏差最大值小于 rttvar时,rttvar也会相应减少*/

if (tp->mdev_max < tp->rttvar)

tp->rttvar -= (tp->rttvar - tp->mdev_max) >> 2;

tp->rtt_seq = tp->snd_nxt;

/* 每个发送周期结束,重置mdev_max为tcp_rto_min */

tp->mdev_max = tcp_rto_min(sk);

}

也就是说,rtt预测偏差值rttvar会跟着实际的rtt预测偏差值变化,如果波动变大,则跟着变大,反之,如果波动变小,也会跟着变小。但因为每个发送周期内,偏差的最大值会重置为tcp_rto_min,所以,rtt预测偏差值rttvar不会小于200ms。

那这200ms的限制,有啥简单的方法调整么?继续看tcp_rto_min的代码,前面也贴过,如下:

static inline u32 tcp_rto_min(struct sock *sk)

{

struct dst_entry *dst = __sk_dst_get(sk);

u32 rto_min = TCP_RTO_MIN; /* 200ms */

if (dst && dst_metric_locked(dst, RTAX_RTO_MIN))

rto_min = dst_metric_rtt(dst, RTAX_RTO_MIN);

return rto_min;

}

从上面的代码可以看出,如果对应的目标的路由表项中设置了rto_min值,则以设置的值为准。这可以通过netlink机制来修改,具体可以通过ip route命令,增加rto_min选项来完成。

分析完源代码,接着试验一下。

运行下面的命令修改成20ms:

sudo ip route add 123.125.104.197/32 via 10.209.83.254 rto_min 20

看以下修改后的结果:

[xiaohong@localhost ~]$ ip route list

default via 10.209.83.254 dev enp0s3  proto static  metric 1024

10.209.80.0/22 dev enp0s3  proto kernel  scope link  src 10.209.80.111

123.125.104.197 via 10.209.83.254 dev enp0s3  rto_min lock 20ms

清除以下路由表的缓存,这样可以立即查看效果:

sudo ip tcp_metrics flush

再测试访问weibo.com:

[xiaohong@localhost ~]$ nc www.weibo.com 80

GET /

在另外的终端中确认一下结果:

[xiaohong@localhost iproute2-3.19.0]$ ss -eipn ‘( dport = :www )‘

tcp   ESTAB      0      0              10.209.80.111:56487       123.125.104.197:80     users:(("nc",1786,3)) uid:1000 ino:14606 sk:ffff88002c992d00 <->

ts sack cubic wscale:0,7 rto:22 rtt:2/1 mss:1448 cwnd:10 send 57.9Mbps rcv_space:14600

可以看出,本次的rtt值是2ms,rto为22ms,即已经生效。

欢迎一起讨论,拍砖也可以。呵呵。

时间: 2024-10-28 11:20:34

[转载] tcp数据重传时间细节探秘及数据中心优化的相关文章

通过packetdrill构造的包序列理解TCP快速重传机制

TCP的逻辑是极其复杂的,其学习曲线虽然很平缓但其每一步都是异常艰难,好在这些都是体力活,只要肯花时间也就不在话下了.想彻底理解一个TCP的机制,有个四部曲:1.读与其相关的RFC:2.看Linux协议栈的TCP实现:3.通过抓包以及其它工具来确认事实就是如此:4.解决一个与之相关的网络问题.经历了以上四步骤,相信任何人都可以在相关领域内稍微装逼一把了...        本文的内容是TCP快速重传机制,但是与其它文章不同的是,本文并不剖析源码实现,也不翻译RFC,更不是原理性介绍,而是通过一个

[转载] TCP/IP协议族

物理层(RS-232.V.35)和 数据链路层(HDLC.X.25)涉及到在通信信道上传输的原始比特流,它实现传输数据所需要的机械.电气.功能性及过程等手段,提供检错.纠错.同步等措施,使之对网络层显现一条无错线路:并且进行流量调控.Bits.Frames 网络层检查网络拓扑,以决定传输报文的最佳路由,执行数据转发.其关键问题是确定数据包从源端到目的端如何选择路由.网络层的主要协议有IP.ICMP(Internet Control Message Protocol,互联网控制报文协议).IGMP

在Wireshark的tcptrace图中看清TCP拥塞控制算法的细节(CUBIC/BBR算法为例)

这是一个令人愉快的周末,老婆上周从上海回来,这周末小小幼儿园组织去坪山秋游,比较远,因此大家都必须早早起来,而我更加有理由起床更早一些来完成这篇短文,因为要出去一整天,如果早上起不来,一天都没什么时间了.        另外,最近有人问我,为什么我总是喜欢在技术文章后面加一些与技术毫不相关的话,我说,咱们小时候学古文的时候,那些古代的作者不也是喜欢在文章最后写一段毫不相关的"呜呼...""嗟夫..."之类的吗?人写文章总是有感而发,所以,点题总是必要的.也有同事问,

【原创】TCP超时重传机制探索

TCP超时重传机制探索 作者:tll (360电商技术) 1)通信模型 TCP(Transmission Control Protocol)是一种可靠传输协议.在传输过程中当发送方(sender)向接收方(receiver)发送的数据丢失时,将引起发送方向接收方重传丢失的数据包. 其通信模型例如以下: wx_fmt=png" data-ratio="1.5138121546961325" data-w="362" _src="https://mm

[转载] tcp那些事1

原文: http://coolshell.cn/articles/11564.html TCP是一个巨复杂的协议,因为他要解决很多问题,而这些问题又带出了很多子问题和阴暗面.所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收获.关于TCP这个协议的细节,我还是推荐你去看W.Richard Stevens的<TCP/IP 详解 卷1:协议>(当然,你也可以去读一下RFC793以及后面N多的RFC).另外,本文我会使用英文术语,这样方便你通过这些英文关键词来查找相关的技术文档.

TCP快速重传与快速恢复原理分析

原文转自:http://blog.csdn.net/zhangskd/article/details/7174682 超时重传是TCP协议保证数据可靠性的一个重要机制,其原理是在发送一个数据以后就开启一个计时器,在一定时间内如果没有得到发送数据报的ACK报文,那么就重新发送数据,直到发送成功为止.这是数据包丢失的情况下给出的一种修补机制.一般来说,重传发生在超时之后,但是如果发送端接收到3个以上的重复ACK,就应该意识到,数据丢了,需要重新传递.这个机制不需要等到重传定时器溢出,所以叫做快速重传

[转载] tcp那些事2

原文: http://coolshell.cn/articles/11609.html 这篇文章是下篇,所以如果你对TCP不熟悉的话,还请你先看看上篇<TCP的那些事儿(上)> 上篇中,我们介绍了TCP的协议头.状态机.数据重传中的东西.但是TCP要解决一个很大的事,那就是要在一个网络根据不同的情况来动态调整自己的发包的速度,小则让自己的连接更稳定,大则让整个网络更稳定.在你阅读下篇之前,你需要做好准备,本篇文章有好些算法和策略,可能会引发你的各种思考,让你的大脑分配很多内存和计算资源,所以,

转载:TCP连接的状态详解以及故障排查

FROM:http://blog.csdn.net/hguisu/article/details/38700899 该博文的条理清晰,步骤明确,故复制到这个博文中收藏,若文章作者看到且觉得不能装载,麻烦请告知,谢谢. 我们通过了解TCP各个状态,可以排除和定位网络或系统故障时大有帮助.(总结网络上的内容) 1.TCP状态 linux查看tcp的状态命令: 1).netstat -nat  查看TCP各个状态的数量 2).lsof  -i:port  可以检测到打开套接字的状况 3).  sar

(转载)TCP/IP四层模型

本文章转载地址:http://www.cnblogs.com/BlueTzar/articles/811160.html TCP/IP参考模型 ISO制定的OSI参考模型的过于庞大.复杂招致了许多批评.与此对照,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用.如图2-1所示,是TCP/IP参考模型和OSI参考模型的对比示意图.            图2-1 TCP/IP参考模型 2.1 TCP/IP参考模型的层次结构 TCP/IP协议栈是美国国防部高级研究计划局计算机网(Advan