Socket选项SO_LINGER用于强制中断
到此才算明白,由于jedis客户端在连接时,设置了socket.setSoLinger(true, 0);,这样在关闭连接时就等同与TCP的Abort,也就是忽略所有正在发送和接收的数据,直接向对方发送一个RESET消息。这也是为什么jedis要在socket.close()前flush缓冲,以确保在途数据不会丢失。
我去掉了客户端对SO_LINGER的设置,终于又看到了正常的TCP挥手。
还想深入的同学,可以阅读linux源码net/ipv4/tcp.c。我大概看了下,代码逻辑很明确(linux内核版本有区别)如果设置了SO_LINGER,在close时,会直接调用tcp_disconnect发送RST数据包,而不再做常规的四次挥手流程。虽然我觉得这样做不太优雅,更优雅的做法可能是socket.setSoLinger(true, timeout)设置一个超时阀值。
在这个github jedis issue Improving socket performance中描述了加入以下四项设置用于提升性能。
socket.setReuseAddress(true);
socket.setKeepAlive(true);
socket.setTcpNoDelay(true);
socket.setSoLinger(true,0);
在issue下加了个comment询问了下,有消息了再更新吧。
总结
此次应用程序中Jedis连接池不能获取redis连接的问题,原因是redis服务器磁盘空间满,导致不能保存快照(rdb snapshot)。应用程序中在testOnBorrow为true的情况下,使用redisPING PONG命令测试redis连接是否有效时,收到了MISCONF Redis is configured to save RDB snapshots的响应,而非正常的PONG。这就导致jedis判断连接无效,强制断开了连接。
之后对TCP中RST flag做了浅尝辄止的分析。当设置了socket.setSoLinger(true, 0)后,关闭此socket将清空数据并向对方发送RST消息。
可以深入的地方还有不少,自己关于网络编程的知识也有待加强。准备补充下相关知识,再结合一些优秀的开源项目如redis、nginx深入了解下。 |