`
simohayha
  • 浏览: 1401285 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

内核中拥塞窗口初始值对http性能的影响分析

阅读更多
这个是google的人提出的概念,那就是对tcp的拥塞窗口的初始值进行增大可以显著的提高http的性能,这个主要是针对tcp的slow start(我前面的blog有介绍)的.

下面是相关的paper和ppt:

paper: http://code.google.com/speed/articles/tcp_initcwnd_paper.pdf
ppt:http://www.ietf.org/proceedings/10mar/slides/iccrg-4.pdf

先来看什么叫做拥塞窗口:
拥塞窗口也就是tcp的发送端所估计的如果没有包的丢失(发生拥塞)时的窗口的大小,他主要是用于tcp的拥塞控制算法,以及发送滑动窗口。

当前内核默认实现是拥塞窗口默认为2个段(也就是两个mss的大小),也就是slow start开始的初始窗口大小只是2个段,这里google 的paper通过一些数据来正明最合适的大小为10(也就是15k),不过这里我认为这里的初始段的大小还是应该根据自己网站的平均页面大小来进行设置。

当前的的http,大部分都是短链接,因此很多情况下slow start还没有结束(进入拥塞避免状态),可是连接已经断开了,并且根据google的paper,网页的平均大小是300多k,而每次都是slow start,这个时候其实slow start会导致性能的下降,所以说这种情况下,我们其实可以加速slow start,而在内核中我们就可以通过设置initcwnd来进行控制。由于拥塞窗口的扩大,slow start将不会起作用,因为就算发送的数据包没有ack,可是由于我们的拥塞窗口本身就比较大,因此还是能够发送比较多的数据而不需要等待ack到来之后来增大拥塞窗口.

更具体的就要去看我上面给出的ppt和paper了。
接下来我们来看源码如何实现的。

首先要知道initcwnd必须通过iproute来设置,而且版本必须大于等于 2.6.34。

通过ip route的源码我们能够看到它是通过netlink 与内核进行通讯,从而达到修改这个值,而对应的数组位置就是RTAX_INITCWND。

这里ip route的源码就不详细分析了,主要我们来看内核的代码。
我们要知道dst_entry这个结构,这个结构我们不详细的分析,他有一个很重要的域我们这里会用到,就是metrics,这个域是一个数组,主要是保存了很多和对端通信时所需要的数值,比如initcwnd,比如max_mtu等等。这里我们只需要关注initcwnd.

下面就是它所保存的数值类型:

enum {
	RTAX_UNSPEC,
#define RTAX_UNSPEC RTAX_UNSPEC
	RTAX_LOCK,
#define RTAX_LOCK RTAX_LOCK
	RTAX_MTU,
#define RTAX_MTU RTAX_MTU
	RTAX_WINDOW,
#define RTAX_WINDOW RTAX_WINDOW
	RTAX_RTT,
#define RTAX_RTT RTAX_RTT
	RTAX_RTTVAR,
#define RTAX_RTTVAR RTAX_RTTVAR
	RTAX_SSTHRESH,
#define RTAX_SSTHRESH RTAX_SSTHRESH
	RTAX_CWND,
#define RTAX_CWND RTAX_CWND
	RTAX_ADVMSS,
#define RTAX_ADVMSS RTAX_ADVMSS
	RTAX_REORDERING,
#define RTAX_REORDERING RTAX_REORDERING
	RTAX_HOPLIMIT,
#define RTAX_HOPLIMIT RTAX_HOPLIMIT
	RTAX_INITCWND,
#define RTAX_INITCWND RTAX_INITCWND
	RTAX_FEATURES,
#define RTAX_FEATURES RTAX_FEATURES
	RTAX_RTO_MIN,
#define RTAX_RTO_MIN RTAX_RTO_MIN
	RTAX_INITRWND,
#define RTAX_INITRWND RTAX_INITRWND
	__RTAX_MAX
};


可以看到我们如果需要取得某个值的话,只需要从数组里面取得对应位置的数值就可以了。

下面就是读取方法:

dst_entry
static inline u32
dst_metric(const struct dst_entry *dst, int metric)
{
//根据传递进来的位置,返回对应的数值。
	return dst->metrics[metric-1];
}


拥塞窗口的初始化是在当tcp连接建立之后进行的,我们来看相关代码,这里是当状态处于TCP_SYN_RECV,然后接收到ack之后,进行初始化拥塞窗口。

相关代码在tcp_rcv_state_process中,来看代码片断:

	if (th->ack) {
		int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0;

		switch (sk->sk_state) {
		case TCP_SYN_RECV:
			if (acceptable) {
..............................................
//初始化对应序列号
				tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
				tp->snd_wnd = ntohs(th->window) <<
					      tp->rx_opt.snd_wscale;
...........................................

//这个函数里面将会初始化上面所介绍的那些类型metrics,包括initcwnd
				tcp_init_metrics(sk);

				tcp_init_congestion_control(sk);
............................................................


然后来看tcp_init_metrics的代码,这里我们只需要看最重要的一部分,也就是初始化snd_cwnd.

static void tcp_init_metrics(struct sock *sk)
{
.......................................
cwnd:
//初始化snd_cwnd
	tp->snd_cwnd = tcp_init_cwnd(tp, dst);
	tp->snd_cwnd_stamp = tcp_time_stamp;
	return;
..........................
}


然后就是最重要的一个函数,初始化snd_cwnd,这个函数主要就是调用dst_metric得到对应的INITCWND的值(如果dst存在),然后选择snd_cwnd_clamp与cwnd的最小值,这里snd_cwnd_clamp表示snd_cwnd的最大值,因此我们设置的值不能大于这个值。

__u32 tcp_init_cwnd(struct tcp_sock *tp, struct dst_entry *dst)
{
//取得对应类型的值
	__u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);

	if (!cwnd) {
		if (tp->mss_cache > 1460)
			cwnd = 2;
		else
			cwnd = (tp->mss_cache > 1095) ? 3 : 4;
	}
//返回两个之间的相对较小的值。
	return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
}


一开始我们就知道ip route initcwnd设置的就是 RTAX_INITCWND对应的值,因此这里就改变了拥塞窗口的值。

ok,然后我们来看拥塞窗口变大后,内核发送数据段时如何被影响。

内核4层发送数据是通过tcp_write_xmit函数进行的,我们来看这个的代码片断,这个函数的详细分析我前面的blog 有分析,有需要的可以看我前面的blog。

下面就是我们最关心的代码片断。
while ((skb = tcp_send_head(sk))) {
		unsigned int limit;

		tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
		BUG_ON(!tso_segs);

//这个函数将会返回还允许发送几个数据包,也就是说这个函数就是配合慢开始的。
		cwnd_quota = tcp_cwnd_test(tp, skb);
//如果没有则跳出循环,也就是不再发送数据。
		if (!cwnd_quota)
			break;



紧接着就是tcp_cwnd_test,这个函数里面用到了几个数据包的概念,比如in flight,这些我前面的blog都有分析,可以去看我前面的blog。

它通过拥塞窗口来计算还可惜发送几个段的数据包。

static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp,
					 struct sk_buff *skb)
{
	u32 in_flight, cwnd;

	/* Don't be strict about the congestion window for the final FIN.  */
	if ((TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) &&
	    tcp_skb_pcount(skb) == 1)
		return 1;
//计算发送还没有确认的数据包
	in_flight = tcp_packets_in_flight(tp);
//取得发送拥塞窗口
	cwnd = tp->snd_cwnd;
//如果发送还没确认的数据包小于拥塞窗口,则说明我们还能发送数据
	if (in_flight < cwnd)
//返回还能发送的数据段个数,默认的cwnd变大则将会返回更大的可以发送的数据段。
		return (cwnd - in_flight);
//否则返回0
	return 0;
}


通过上面的代码我们可以看到如果cwnd也就是拥塞默认窗口变大之后,则我们每次可以多发送一些数据段。

这里要注意的是就算initcwnd增大,slow start也是不可避免的,只不过现在拥塞窗口变大导致发送的时候能够多发送数据段。而也只有当发送的都ack之后拥塞窗口才会变大,而有可能等不到全部的ack,连接已经断开。

在2.6.33之后还多出来了一个RTAX_INITRWND,这个值主要是针对默认的接收窗口进行设置。
1
0
分享到:
评论
1 楼 youyuqin 2010-07-11  
讲的真有道理!

相关推荐

    Linux下Socket编程

    标签中提到的文件名涉及到HTTP性能与拥塞窗口初始值的关系。在TCP中,拥塞窗口(CWND)是控制数据传输速率的关键因素,用于防止网络拥塞。HTTP请求和响应通常基于TCP传输,因此,CWND的初始值会影响HTTP性能。当...

    淘宝 MySQL DBA 网络性能调优

    TCP初始化拥塞窗口(initcwnd)的调优也是降低短连接响应时间的有效方法。较大的initcwnd值可以在连接建立初期更快地填充网络管道,减少延迟。确保内核版本在2.6.30以上,并使用`ip route change`命令设置initcwnd值...

    tcp_vegas.rar_V2

    在TCP Vegas中,拥塞窗口(cwnd)的增长不是线性的,而是基于一个名为"加性增乘性减"(AIMD,Additive Increase Multiplicative Decrease)的机制。在无拥塞阶段,拥塞窗口以较小的步长线性增加;一旦检测到拥塞...

    LINUX tcp 优化资料,

    在IT领域,特别是网络优化和运维中,LINUX TCP优化...其他文档,如关于TCP初始窗口大小、窗口缩放选项、MTU值检测以及TCP参数的Linux内核详解,提供了丰富的理论知识和实际操作指南,对于提升网络运维技能非常有价值。

    tcp_cong.rar_control

    1. **慢启动**:当TCP连接建立时,发送方会设置一个初始的拥塞窗口(cwnd,congestion window)值,一般为1个报文段。然后在每个往返时间内,cwnd会成倍增长,这个过程被称为慢启动阶段,目的是快速探索网络的可用...

    linux 调优

    - 原因分析:ECN主要用于网络拥塞控制,但在实际应用中并未得到广泛支持。 **23. 数据包重新排序** - 参数名:`tcp_reordering` - 默认值:3 - 功能描述:设置数据包重新排序阈值。 - 推荐值:6 - 原因分析:...

    TCP Performance Simulations Using Ns2

    这一机制的核心在于当前窗口大小定义了发送端可以发送但未被确认的数据量的最大值。简而言之,TCP拥塞控制的目标是在避免网络拥塞的同时最大化链路利用率。 ##### 2.1.1 慢启动与拥塞避免 - **慢启动**:在初始阶段...

    百度招聘系统工程师(研发)笔试题

    - **拥塞窗口**:由当前网络拥塞程度决定。 - **接收窗口**:由接收方根据自己的缓冲区大小来决定。 - **发送窗口**:由发送方根据接收方允许接收的数据量以及网络拥塞情况来确定。 ### 编程题解析 #### 删除...

    系统运维工程师以及各厂面经及答案.docx

    - **示例:** 假设初始窗口大小为3,如果接收方只确认了2个数据包,则下一个窗口大小可能减少到2。 --- ##### 5. `fork()` 函数的理解 **功能与作用:** `fork()` 函数通过系统调用创建一个与原进程几乎完全相同...

    TCP_IP详解卷1

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part03

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part07

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part10

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part04

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part09

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part05

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCPIP详解卷[1].part06

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

    TCP/IP详解part_2

    描述了属于每一层的各个协议以及它们如何在不同操作系统中运行。作者用Lawrence Berkeley实验室的tcpdump程序来捕获不同操作系统和TCP/IP实现之间传输的不同分组。对tcpdump输出的研究可以帮助理解不同协议如何工作...

Global site tag (gtag.js) - Google Analytics