该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2011-06-30
最后修改:2011-06-30
Nagle算法的立意是良好的,避免网络中充塞小封包,提高网络的利用率。但是当Nagle算法遇到delayed ACK悲剧就发生了。Delayed ACK的本意也是为了提高TCP性能,跟应答数据捎带上ACK,同时避免糊涂窗口综合症,也可以一个ack确认多个段来节省开销。 write(head); write(body); read(response);
接收端的处理代码类似这样: read(request); process(request); write(response);
这里假设head和body都比较小,当默认启用nagle算法,并且是第一次发送的时候,根据nagle算法,第一个段head可以立即发送,因为没有
等待确认的段;接收端收到head,但是包不完整,继续等待body达到并延迟ACK;发送端继续写入body,这时候nagle算法起作用了,因为
head还没有被ACK,所以body要延迟发送。这就造成了发送端和接收端都在等待对方发送数据的现象,发送端等待接收端ACK
head以便继续发送body,而接收端在等待发送方发送body并延迟ACK,悲剧的无以言语。这种时候只有等待一端超时并发送数据才能继续往下走。 package net.fnil.nagle; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(8000)); System.out.println("Server startup at 8000"); for (;;) { Socket socket = serverSocket.accept(); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); while (true) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line = reader.readLine(); out.write((line + "\r\n").getBytes()); } catch (Exception e) { break; } } } } }
package net.fnil.nagle; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; public class Client { public static void main(String[] args) throws Exception { // 是否分开写head和body boolean writeSplit = false; String host = "localhost"; if (args.length >= 1) { host = args[0]; } if (args.length >= 2) { writeSplit = Boolean.valueOf(args[1]); } System.out.println("WriteSplit:" + writeSplit); Socket socket = new Socket(); socket.connect(new InetSocketAddress(host, 8000)); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String head = "hello "; String body = "world\r\n"; for (int i = 0; i < 10; i++) { long label = System.currentTimeMillis(); if (writeSplit) { out.write(head.getBytes()); out.write(body.getBytes()); } else { out.write((head + body).getBytes()); } String line = reader.readLine(); System.out.println("RTT:" + (System.currentTimeMillis() - label) + " ,receive:" + line); } in.close(); out.close(); socket.close(); } }
WriteSplit:true RTT:8 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world RTT:39 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world RTT:40 ,receive:hello world 可以看到,每次请求到应答的时间间隔都在40ms,除了第一次。linux的delayed ack是40ms,而不是原来以为的200ms。第一次立即ACK,似乎跟linux的quickack mode有关,这里我不是特别清楚,有比较清楚的同学请指教。 Socket socket = new Socket(); socket.setTcpNoDelay(true); socket.connect(new InetSocketAddress(host, 8000));
WriteSplit:true RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:1 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world
WriteSplit:false RTT:7 ,receive:hello world RTT:1 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world RTT:0 ,receive:hello world 结果跟禁用nagle算法的效果类似。既然这样,我们还有什么理由一定要禁用nagle算法呢?通过我在xmemcached的
压测中的测试,启用nagle算法在小数据的存取上甚至有一定的效率优势,memcached协议本身就是个连续的请求应答的模型。上面的测试如果在
windows上跑,会发现RTT最大会在200ms以上,可见winsock的delayed ack超时是200ms。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-06-30
没啥人关注啊,我写了一下午的
|
|
返回顶楼 | |
发表时间:2011-06-30
dennis_zane 写道 没啥人关注啊,我写了一下午的
哈哈,除了那几批“老人”外,没几人对TCP/Socket熟悉的。 你叫人怎么关注呀~~~~ |
|
返回顶楼 | |
发表时间:2011-06-30
不错,投了精华!
|
|
返回顶楼 | |
发表时间:2011-07-01
多学习了一点内容,以前还不知道有delay ack这回事。
有个问题:在memcached协议里,对应的cmd都是一次请求request出去。不知道在xmemcached会出现head,body的情况? 还有一个,就是比较关心LZ是怎么发现这个问题原因?貌似是个性能改进点,并不会造成功能不可用。要发现这个问题,可得下不少功夫,蹲点tcpdump? |
|
返回顶楼 | |
发表时间:2011-07-01
good job,我的程序里面也是write header write body,然后wait response的模式,现在我把客户端程序用bufferedOutPutStream缓存header body一起发送。
|
|
返回顶楼 | |
发表时间:2011-07-01
agapple 写道 多学习了一点内容,以前还不知道有delay ack这回事。
有个问题:在memcached协议里,对应的cmd都是一次请求request出去。不知道在xmemcached会出现head,body的情况? 还有一个,就是比较关心LZ是怎么发现这个问题原因?貌似是个性能改进点,并不会造成功能不可用。要发现这个问题,可得下不少功夫,蹲点tcpdump? 首先是xmemcached不会有这种问题,它都是搞成一个缓冲区来发送的,也没有用writev。 其次,这个问题过去一直都有存在,都是简单地设置nagle算法来解决。最近回去看了些tcp/ip的资料(tcp/ip协议卷一),发现这样解决不是最好和唯一的办法。 |
|
返回顶楼 | |
发表时间:2011-07-01
最后修改:2011-07-01
dennis_zane 写道 agapple 写道 多学习了一点内容,以前还不知道有delay ack这回事。
有个问题:在memcached协议里,对应的cmd都是一次请求request出去。不知道在xmemcached会出现head,body的情况? 还有一个,就是比较关心LZ是怎么发现这个问题原因?貌似是个性能改进点,并不会造成功能不可用。要发现这个问题,可得下不少功夫,蹲点tcpdump? 首先是xmemcached不会有这种问题,它都是搞成一个缓冲区来发送的,也没有用writev。 其次,这个问题过去一直都有存在,都是简单地设置nagle算法来解决。最近回去看了些tcp/ip的资料(tcp/ip协议卷一),发现这样解决不是最好和唯一的办法。 可能在memcached client这样的场景下也许不会出现这样的情况。 nio每次的write都操作一个sendBuffer,如果一次flush不能完全输出,还是会出现的write-write情况,写类似的server,比如大文本数据下载,大数据量的rpc请求服务,就得注意LZ提的场景。 不过看了下目前公司的几个socket操作的包,基本都是Nagle=false的为多。 |
|
返回顶楼 | |
发表时间:2011-07-01
最后修改:2011-07-01
文章太长,只看了一半。
不过有个疑问,TCP是有捎带ACK。但是绝大多数实现采用的时延为200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。 你在本机测试时RTT很小觉得有阻塞,但是200ms对于网络传输来说并不是很大吧。 |
|
返回顶楼 | |
发表时间:2011-07-01
还有个疑问,在一个web应用里面,既存在小数据量得http请求,也存在大数据量的文件上传,第二种情况write不能合并,服务器也不会马上响应,那这种情况怎么解决?当前的nginx,tomcat等服务器是怎么做的呢?
|
|
返回顶楼 | |