一、redis写入数据
public class JedisDemo {
static String host = "192.168.91.128";
static int port = 6379;
public static void main(String[] args) throws Exception {
try (JedisPool jedisPool = new JedisPool(host, port)) {
Jedis jedis = jedisPool.getResource();
StringBuilder sb = new StringBuilder();
for (int i = 1; i < 10000; i++) {
sb.append(i);
}
String trueString = sb.toString();
System.out.println(trueString.getBytes().length);//长度为38889
jedis.set("thisiskey", sb.toString());
}
}
}
一次性向redis中写38889字节的数据。
接下来通过抓包来看redis是tcp是如何传输这些数据的。
二、抓包redis网络交互数据
在redis服务器抓包
tcpdump -w redis_dump.cap -i eth0
打开dump
可以看到握手后本机向redis服务器分别传输了两次数据,大小分别为14654字节和24345字节。
初看这里是不对的,为什么呢?
因为以太网链路层的最大传输单元(MTU:Maximum Transmission Unit)是1500字节。
上面抓的包里面每次传输的数据是10倍的MTU,明显是不合理的。
问题出在哪里?
难道一次的传输数据会超过MTU的限制?
三、网卡TSO和GRO
先说结论:每次传输的数据量大小是不会超过MTU的限制的。
这就类似于路只有一米宽,那么最多也就允许一米的车行驶。无论如何都不可能有超过1米的车在上面行驶。
那么为什么会抓包抓到远远大于MTU的包呢?
这里需要引入两个概念:TSO和GSO(linux默认是打开的)
3.1 TSO
TSO(TCP Segmentation Offload)
把TCP分段的过程直接移到网卡中进行,当发送的机器网卡支持TSO时,TCP层不再对大于MSS的数据进行分片,而是直接把数据传送到网卡,由网卡渠道进行分片,从而避免在TCP分片而占用CPU。
- 数据拆分所需要的CPU开销挪到网卡
- 实际在链路层还是按照MTU进行传输
TSO示意图:
这个需要在请求发送的机器上进行抓包验证(本文没有实际操作)。
3.2 GRO(LRO)
Generic Receive Offload(Large Receive Offload)
在网卡驱动层面将受到的多个数据包聚合成一个大的数据包,一次性交给上层传输层,减少上层协议栈对应的CPU的开销。
LRO示意图:
这个需要在接受请求的机器进行抓包验证,本文的例子就是基于接受请求的机器抓包而演示的。
3.3 关闭GRO后再次抓包
关闭redis服务器的GRO。
ethtool -K eno1 gro off
再次在redis服务器抓包。
可以看到每次的传输都变成了1514字节,其中数据为1500字节,和MTU的大小一样。