欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从Jedis看TCP常用的参数设置

程序员文章站 2024-03-21 09:43:16
...

个人博客请访问 http://www.x0100.top                  

背景

由于微服务的兴起,以及公司内部各种服务间的各种调用,最近也遇过几次线上大量TIME_WAIT的问题,虽然知道怎么解决,但也点燃了我对tcp协议重新温习学习的兴趣。

从何学习呢?重新看下几百多页的《TCP/IP详解》,那太花时间了,以前页看过一次。不如从一些优秀的开源项目里,看下他们是怎么使用tcp建立连接的?那就从jedis开始吧。????

源码分析步骤

下载源代码

git clone https://github.com/xetorthio/jedis.git
git checkout tags/jedis-2.9.0

分析

从最简单的jedis.set追到底层的tcp网络编程吧

时序图

从Jedis看TCP常用的参数设置

image.png

核心tcp参数设置

socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method
socket.setSoTimeout(soTimeout);

1 setReuseAddress

可以看出setReuseAddress本质上就是对SO_REUSEADDR的设置

public void setReuseAddress(boolean on) throws SocketException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    getImpl().setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(on));
}

SO_REUSEADDR主要的作用(其它作用)就是对TIME_WAIT连接的重用,对于jedis,作者应该是希望客户端重启后,重新创建到redis服务器的tcp连接能够重用之前的接口。因为客户端重启时,活跃的jedis连接由于时客户端主动关闭,会持续2*MSL的TIME_WAIT状态,在此期间会一直占用着客户端端口。MSL的大小可通过以下命令查看

sysctl -a | grep tcp_fin_timeout
tcp_fin_timeout = 60

2 setKeepAlive

SO_KEEPALIVE是tcp利用心跳机制保持连接的存活,假如连接已经断开,则会响应错误码Broken Pipe给上层应用,由于keepalive发起心跳包的开始实际比较迟,对于调整后的linux操作系统仍然需要5分钟,数值过小又会导致过多无用的包发出,5分钟的时间长度发出后,通常也会由于各种各样的原因连接早已被销毁,例如客户端服务器端的连接经过了lvs,haproxy各种各样的代理,代理服务器本身也会有个keepalive的最长时间。一般业务对于SO_KEEPALIVE的依赖比较小,假如需要保持连接的话,会自己进行一些存活性的维持和判断,例如

  1. 定期小周期发送心跳包keep alive

  2. 在使用连接前进行判断,例如连接池通常采用的testOnBorrow, testOnReturn之类的机制。

sudo sysctl -a | grep tcp_keepalive
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 300 //通常默认值为7200

3 setTcpNoDelay

TCP_NODELAY的作用就是禁用Nagle,减少由于小包带来的推迟发送的延迟

If set, disable the Nagle algorithm. This means that segments are always sent as soon as possible, even if there is only a small amount of data. When not set, data is buffered until there is a suffi‐ cient amount to send out, thereby avoiding the frequent sending of small packets, which results in poor utilization of the network. This option is overridden by TCP_CORK; however, setting this option forces an explicit flush of pending output, even if TCP_CORK is currently set.

4 setSoLinger(true, 0)

这个选项的设置就是jedis关闭的时候直接发送RST,而不是按照正常的4次挥手的关闭流程,避免了客户端TIME_WAIT的情况,但不是一个很好的pratice,因为服务端会只会收到RST,这里留下一个问题,redis是如何处理rst的,是否是直接忽略,不然就会堆积很多错误日志?

5 socket.setSoTimeout(soTimeout)

SO_TIMEOUT的作用就是设置以下三个socket操作的超时时间,很明显,ServerSocket.accept()是针对服务端而言,DatagramSocket.receive()是针对UDP,在这里jedis生效的是SocketInputStream.read(),作用就是jedis发送命令后,开始读redis请求的响应时间。

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

总结

tcp是一个非常复杂的网络协议,但对于“客户端”的tcp编程其实也不是特别难,从jedis在tcp的使用上看,直接用了blocking io,同时设置了5个参数,就满足了大部分场合的使用,对于一般互联网的服务,也只是使用多一个池化JedisPool而已。