Netty之TcpNoDelay

背景

同事在使用spring-data-redis的时候,且连接的是公司基础服务搭建的Redis集群,多线程去get值的时候,结果会出现乱序的情况。

排查步骤

  1. 查看源码,spring-data-redis默认会使用lettuce这个redis客户端,而lettuce底层又使用了netty作为通讯组件。

  2. 查看收发包的情况,看是否在TCP这一层值是否就已经乱序了。

    netty-1

    从这里我们可以看到序号为322这个包发了一个GET命令到Redis服务器(其实Redis这种基于文本的协议很容看懂,大家可以参考http://doc.redisfans.com/topic/protocol.html#id3),然后Redis返回来的值没有问题,也即序号为323的包,这里我就不贴了。接着看下面netty-2

    从这里我们看到序号为324的这个包发了三个GET命令到Reids服务器,这个时候服务器恢复的325这个包就有问题了

  3. 和Redis团队进行沟通,他们在Redis集群之前做了一个代理,如果收到325这种包,也就是类似于Pipeline的这种方式,他们会异步的的去处理这个包里的所有命令,然后所有结果都返回后再返回给客户端。而由于我发的这几个Key在不同的分片上,所以返回的结果就乱了。当然这可能是他们Redis集群支持的问题,可能到这里的话很多人就会说他们不支持(因为之前我测试了单机Redis,以及用Jedis客户端都没有这个问题),但是有一个问题始终困扰着我,就是为什么在324这个包里会发送多个GET命令过去,难道是我们客户端用了pipeline,所以我又跟了一遍代码。

  4. 跟代码的过程当中没有发现任何地方使用pipeline,而且从上面的包也可以看出,收发包始终都是一个连接,在一个连接中,netty所有的写动作都是在一个for循环里完成的(为什么会这样,后面会写Netty源码分析的文章,放在后面去说明),所以也不存在多线程的问题。后来左思右想是不是TCP协议将小包合并了呢?然后就去查相关的资料,终于豁然开朗。

  5. TCP协议中有一个Nagle算法,简单点说,就是第一个包的ACK没有收到的话,后面的小包都会被整合到一个包中进行发送(具体Nagle算法的解释,请参看TCP/IP详解 卷1 15.4章节,里面有详细的介绍),这个默认都是开启的,那如何关闭呢?

  6. 在Netty中有一个TcpNoDelay参数,这个参数在lettuce中默认被设置为false,将它设置为true也就是禁用Nagle算法,这样的话小包就能发送了。禁用Nagle算法后,此问题没有再出现。

说说Nagle算法

Nagle算法的初衷是:避免发送大量的小包,防止小包泛滥与网络,而并没有阻止发送小包(完全阻止发送小包,有TCP_CORK来做控制)。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前发送数据的ack回包,他才会发数据,否则就是在攒数据。

总结

Netty有很多参数,在使用的时候要搞清楚每个参数是什么意思,配置与不配置会产生什么样的影响,这样的话你才能更好的掌控这个计数。

参考文章

https://coolshell.cn/articles/11609.html

https://blog.csdn.net/dog250/article/details/21303679