Board logo

标题: 从Redis连接池获取连接失败的原因说起(3) [打印本页]

作者: look_w    时间: 2019-2-20 19:03     标题: 从Redis连接池获取连接失败的原因说起(3)

对RST包的理解

但是我还有一个问题,那就是为什么会有一个RST包呢?如果没有那个RST包,其实问题还不好发现,虽然按照错误日志的时间,挨个查找Redis数据包的信息,能够查询出来,但是RST无疑从一开始就吸引了我的注意,让我能够更加快速的定位问题。
初识RST

那现在问题来了,为什么会有RST包呢?
首先了解一下RST。(可参考TCP/IP详解 卷1 , 18.7 复位报文段)
归纳起来,当以下任一情况发生时,会产生RST包:

    到不存在的端口的连接请求
    异常终止一个连接
    检测半打开连接

jedis与redis的关闭机制

观察RST之前的几个包
931112-ebd5fc72398ace00.png
ws_3.png

使用wireshark的专家信息查看多个RST包,发现RST之前都会有QUIT,OK的交互。那看来应该是框架层面的问题。
再翻看上面GenericObjectPool的相关代码,在borrowObject时如果发生异常,会调用destroyObject()方法,这个destroyObject是延迟到子类实现的,也就是上面说到的JedisPool。

    public void destroyObject(final Object obj) throws Exception {
        if (obj instanceof Jedis) {
            final Jedis jedis = (Jedis) obj;
            if (jedis.isConnected()) {
                try {
                    try {
                        jedis.quit();
                    } catch (Exception e) {
                    }
                    jedis.disconnect();
                } catch (Exception e) {
     
                }
            }
        }
    }

最终调用redis.clients.jedis.Connection的disconnect,关闭输入输出流。

    public void disconnect() {
        if (isConnected()) {
            try {
                inputStream.close();
                outputStream.close();
                if (!socket.isClosed()) {
                    socket.close();
                }
            } catch (IOException ex) {
                throw new JedisConnectionException(ex);
            }
        }
    }
     

这也就解释了为什么会出现RST包:
客户端请求QUIT,服务端返回OK。(此时客户端在接收完quit返回后,调用了disconnect方法,导致连接断开)紧接着服务端发起TCP挥手,发送FIN包到之前交互的客户端51311端口,但调用完disconnect的客户端已经断开了和服务端的连接。客户端只能通过发送RST,通知服务端“你发送了一个到不存在的端口的关闭请求”。

翻看新版的jedis代码,除了将之前JedisPool中实现的代码挪到了JedisFactory中实现,大致逻辑依然没有改变()

    // 2.10 JedisFactory
    @Override
      public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {
        final BinaryJedis jedis = pooledJedis.getObject();
        if (jedis.isConnected()) {
          try {
            try {
              jedis.quit();
            } catch (Exception e) {
            }
            jedis.disconnect();
          } catch (Exception e) {
     
          }
        }
      }
     
    @Override
    public boolean validateObject(PooledObject<Jedis> pooledJedis) {
      final BinaryJedis jedis = pooledJedis.getObject();
      try {
        HostAndPort hostAndPort = this.hostAndPort.get();
     
        String connectionHost = jedis.getClient().getHost();
        int connectionPort = jedis.getClient().getPort();
     
        return hostAndPort.getHost().equals(connectionHost)
            && hostAndPort.getPort() == connectionPort && jedis.isConnected()
            && jedis.ping().equals("PONG");
      } catch (final Exception e) {
        return false;
      }
    }
     

而disconnect最终调用的Connection有变化。

    public void disconnect() {
      if (isConnected()) {
        try {
          outputStream.flush();
          socket.close();
        } catch (IOException ex) {
          broken = true;
          throw new JedisConnectionException(ex);
        } finally {
          IOUtils.closeQuietly(socket);
        }
      }
    }
     

由之前的inpusStream.close()和outputStream.close()改成了outputStream.flush()。原因是jedis自定义了带缓冲的RedisOutputStream,在socket.close前要确保缓冲内容写到流中。
客户端使用disconnect确实能够快速释放资源,在调用disconnect时关闭了客户端端口,回收了文件句柄资源。
试想如果在quit后,服务端就已经释放了文件句柄,关闭了socket连接,而客户端不调用disconnect释放资源,就会一直占用资源,在进程结束才会释放。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0