一个更高效的RACK机制

tcp在kernel-4.3内核中加入了RACK机制,用从时间维度上来判断丢包,用最新被(S)ACK确认的数据包为基准,其发送时间减去一个乱序时间窗口之前的数据包如果没有收到反馈,就可以判断为丢失了。主要为了解决尾部丢包和二次重传的问题。
旧版RACK一个比较明显的缺点就是容易误判,尤其是在重传数据包被(S)ACK确认,是否更新最近被确认的数据包问题上,只是简单用判断重传到被确认的时间是否大于min_rtt。如果min_rtt很小,连接又有乱序情况,很容易误判一大片丢包,造成传输效率低下的问题。
当然,旧版的RACK还存在其他问题,比如时间窗口值为(1ms,min_rtt/4)的最大值,几乎可以认为是一个固定的值,当发现误判时,收到客户端反馈回来的D-SACK信息,并不能动态的调节这个乱序时间窗口。其次,RACK的另一个好处是解决尾部丢包问题,但是这是ack驱动的,每次丢包判断只能在更新了最近被(S)ACK之后进行。如果某一次判定,最新更新数据包的发送时间与该发送时间减去乱序时间窗口的时间区间,还剩下两三个数据包未能判断丢失,只能等下次满足判断条件的ack到来。
为了解决上述所说的三个问题,linux-4.14的内核中改进了RACK实现机制,使其变成一个更高效的丢包判断机制,以至于可以取消FACK这种为了较为激进的机制。首先加入了REO定时器,该定时器是复用RTO定时器,通过事件event来区分。定时器的处理函数tcp_write_timer_handler()。
void tcp_write_timer_handler(struct sock *sk)
{
    .......
 	event = icsk->icsk_pending;
 
 	switch (event) {
	case ICSK_TIME_REO_TIMEOUT:
		tcp_rack_reo_timeout(sk);
		break;
 	case ICSK_TIME_EARLY_RETRANS:
 		tcp_resume_early_retransmit(sk);
 		break;
        ......
        }
}
当某次判断的乱序时间窗口内只要有一个包未能判断丢包,则会启动这个REO定时器,该定时器的超时时间就是乱序时间窗口值,定时器处理函数中调用tcp_rack_reo_timeout()判断数据包丢失,就解决了ack驱动,有效的解决了尾部丢包问题。
void tcp_rack_reo_timeout(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct skb_mstamp now;
	u32 timeout, prior_inflight;

	skb_mstamp_get(&now);
	prior_inflight = tcp_packets_in_flight(tp);
	tcp_rack_detect_loss(sk, &now, &timeout);/*判断丢包*/
	if (prior_inflight != tcp_packets_in_flight(tp)) {/*满足条件,说明rack有新判断出丢包*/
		if (inet_csk(sk)->icsk_ca_state != TCP_CA_Recovery) {/*之前未丢包,切换到recovery状态*/
			tcp_enter_recovery(sk, false);
			if (!inet_csk(sk)->icsk_ca_ops->cong_control)
				tcp_cwnd_reduction(sk, 1, 0);
		}/*有判断出新丢包,则进行重传*/
		tcp_xmit_retransmit_queue(sk);
	}
	if (inet_csk(sk)->icsk_pending != ICSK_TIME_RETRANS)/*重置为RTO定时器*/
		tcp_rearm_rto(sk);
}
判断丢包函数处理过程
static void tcp_rack_detect_loss(struct sock *sk, u32 *reo_timeout)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *skb, *n;
	u32 reo_wnd;

	*reo_timeout = 0;
	reo_wnd = tcp_rack_reo_wnd(sk);/*获取乱序时间窗口值*/
	list_for_each_entry_safe(skb, n, &tp->tsorted_sent_queue,
				 tcp_tsorted_anchor) {/*遍历传输队列*/
		struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
		s32 remaining;

		/* Skip ones marked lost but not yet retransmitted */
		if ((scb->sacked & TCPCB_LOST) &&
		    !(scb->sacked & TCPCB_SACKED_RETRANS))
			continue;/*忽略已经标记丢失但未重传的skb*/
                 /*已经判断完最近被(s)ack确认skb的之前所有的包*/
		if (!tcp_rack_sent_after(tp->rack.mstamp, skb->skb_mstamp,
					 tp->rack.end_seq, scb->end_seq))
			break;

		/* A packet is lost if it has not been s/acked beyond
		 * the recent RTT plus the reordering window.
		 */
		remaining = tcp_rack_skb_timeout(tp, skb, reo_wnd);/*小于等于零,可以判断为丢包,大于零,为需要在额外等待的时间*/
		if (remaining <= 0) {
			tcp_mark_skb_lost(sk, skb);
			list_del_init(&skb->tcp_tsorted_anchor);
		} else {
			/* Record maximum wait time *//*记录需要等待最长的额外时间,用该值重置REO定时器*/
			*reo_timeout = max_t(u32, *reo_timeout, remaining);
		}
	}
}
时间窗口值是第二比较大的改变,时间窗口值不在是max(1ms, min_rtt/4)这样比较固定的值,是可以根据客户端反馈回来的D-SACK信息进行动态调整的,并且是以轮数为单位进行调整。当某一轮收到一个D-SACK数据包,就将时间窗口增加一个min_rtt/4,当然时间窗口值不能超过当前srtt的值,最大可以增加到64min_rtt的时间窗口值。当连续十六轮都没有再收到过D-SACK数据包,就将时间窗口值重置为min_rtt/4。这部分代码主要在tcp_recovery.c的函数tcp_rack_update_reo_wnd()
void tcp_rack_update_reo_wnd(struct sock *sk, struct rate_sample *rs)
{
	struct tcp_sock *tp = tcp_sk(sk);
        /*TCP_RACK_STATIC_REO_WND为0x02,如果设置了该值,就回到旧版的静态时间窗口功能,默认是开启0x01*/
	if (sock_net(sk)->ipv4.sysctl_tcp_recovery & TCP_RACK_STATIC_REO_WND ||
	    !rs->prior_delivered)
		return;

	/* Disregard DSACK if a rtt has not passed since we adjusted reo_wnd */
	if (before(rs->prior_delivered, tp->rack.last_delivered)) /*刚调整完乱序时间窗口,还未经过一轮,则忽略处理改d-sack*/
		tp->rack.dsack_seen = 0;

	/* Adjust the reo_wnd if update is pending */
	if (tp->rack.dsack_seen) {
		tp->rack.reo_wnd_steps = min_t(u32, 0xFF,
					       tp->rack.reo_wnd_steps + 1);/*将时间窗口增加一个min_rtt/4*/
		tp->rack.dsack_seen = 0;
		tp->rack.last_delivered = tp->delivered;
		tp->rack.reo_wnd_persist = TCP_RACK_RECOVERY_THRESH;/*重置为16轮*/
	} else if (!tp->rack.reo_wnd_persist) {/*连续16轮没再收到D-SACK信息,则认为网络乱序已经变好,将时间窗口值变小*/
		tp->rack.reo_wnd_steps = 1;
	}
}
新版rack机制相比之下,确实有很大的改善,但是动态调整机制需要客户端支持D-SACK机制,作者patch中提到说大部分的机器都支持D-SACK机制,就我司服务端抓取的日志来看,未支持的D-SACK机制机器还是有一定的占比的。其次,服务端只要是收到了D-SACK就认为是因为我们乱序时间窗口值不够大造成的误判,所以调大了这个值,但可能我们使用了其他的判断丢包机制造成的误判。最后,连续十六轮未发现D-SACK机制,就马上将reo_wnd_steps重置为1,也就是时间窗口值设置为min_rtt/4。之前乱序reordering更新也没有说连续十几轮没发现乱序就减小当前乱序值,直接将降回初始值,是不是太乐观了,可以考虑改稳妥些,连续十六轮未发现新的D-SACK,将reo_wnd_steps减半。



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页