Jedis分析(三)TcpNoDelay - wtstengshen/blog-page GitHub Wiki

    接着上篇分析Jedis的connect()方法里的参数进行分析;
####1,socket.setTcpNoDelay(true)的作用

socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
                           // ensure timely delivery of data
在Jedis的connect()方法中,设置了tcpNoDelay为true,看一下源码中的注解,
/**
 * Disable Nagle's algorithm for this connection.  Written data
 * to the network is not buffered pending acknowledgement of
 * previously written data.
 *<P>
 * Valid for TCP only: SocketImpl.
 * <P>
 * @see Socket#setTcpNoDelay
 * @see Socket#getTcpNoDelay
 */
public final static int TCP_NODELAY = 0x0001;
[Nagle's algorith][1],Nagle算法是为了减少网络中小包较多的问题;减少必须发送包的个数来增加网络软件系统的效率; TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。如果网络中充斥这大量的,只有很小数据的tcp包,那么这么多包都需要进行ack,会给网络带来很大的压力,所以为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据,把很多小数据集合在一起发送出去,Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。   Nagle算法的伪代码,摘自维基百科:
if there is new data to send
  if the window size >= MSS and available data is >= MSS
    send complete MSS segment now
  else
    if there is unconfirmed data still in the pipe
      enqueue data in the buffer until an acknowledge is received
    else
      send data immediately
    end if
  end if
end if
可以看到他的算法起作用的前提是:有未被确认的数据,同时要发送的数据包的大小小于MSS; 同时,TCP_NODELAY和[TCP_DELAYED_ACKNOWLEDGMENT][2]碰在一起,有可能出现网络延迟40ms的情况;tcp的延迟确认会延迟ack包的发送,等待和数据包一次进行发送。

##2,代码重现40ms的延迟问题 client和server部署在两个不同的机器上,在client发送小包,查看往返请求的时间;

// client端的java代码
Socket socket = new Socket();
socket.setTcpNoDelay(false);// 这里设置是否禁用TcpNoDelay
socket.connect(new InetSocketAddress("182.168.1.23", 9091), 2000);

String head = "A";
String body = "B\r\n";
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
for (int i = 0; i < 10; i++) {
	long startTime = System.currentTimeMillis();
	// 分两次发送小包数据
	out.write(head.getBytes());
	out.write(body.getBytes());
	String resLine = reader.readLine();
	System.out.println("TTL:"
	+ (System.currentTimeMillis() - startTime) + ",resLine = "
	+ resLine);
}
in.close();
out.close();
socket.close();
1,禁用TcpNoDelay运行的结果:
TTL:2,resLine = AB
TTL:0,resLine = AB
TTL:0,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
TTL:0,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
2,启用TcpNoDelay运行结果:
TTL:2,resLine = AB
TTL:41,resLine = AB
TTL:42,resLine = AB
TTL:40,resLine = AB
TTL:42,resLine = AB
TTL:41,resLine = AB
TTL:41,resLine = AB
TTL:43,resLine = AB
TTL:42,resLine = AB
TTL:40,resLine = AB
可以看到本次的请求的响应时间基本上都多了40ms的时间; 3,把两次write改成一次,
// 分两次发送小包数据
out.write(head.getBytes());
out.write(body.getBytes());
//改为
out.write((head + body).getBytes());
程序运行结果:
TTL:3,resLine = AB
TTL:1,resLine = AB
TTL:1,resLine = AB
TTL:0,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
TTL:0,resLine = AB
TTL:1,resLine = AB
这也就验证了wiki上的伪代码算法;

##3,最后总结 Jedis设置关闭TcpNoDelay的目的和代码中注释解释的一样,因为40ms的延迟对于请求一次redis来说,实在是太长了,基本上局域网已经是8个来回了,生产上请求一次redis的时间基本上在5ms作用,Jedis这样做也是为了提供响应时间。

[1]:https://en.wikipedia.org/wiki/Nagle%27s_algorithm [2]:https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment

⚠️ **GitHub.com Fallback** ⚠️