zoukankan      html  css  js  c++  java
  • jsch连接sftp后连接未释放掉问题排查

    项目中通过jsch中的sftp实现上传下载文件。在压测过程中,由于调用到sftp,下载文件不存在时,系统不断抛出异常,内存飙升,逐渐把swap区也占满,通过top监控未发现占用内存的进程,通过查找sshd进程,发现服务器多了很多sftp的进程没有被关闭。

    刚开始以为是sftp公共方法设计的有问题,每次创建连接都未释放,下面是部分代码片段

    @Repository("SftpClient")
    public class SftpClient {
    
        private Logger logger = LoggerFactory.getLogger(SftpClient.class);  
        private ThreadLocal<Session> sessionLocal = new ThreadLocal<Session>(); 
        private ThreadLocal<ChannelSftp> channelLocal = new ThreadLocal<ChannelSftp>();
        
        //初始化连接
        public SftpClient init() {
            try {
                String host = SFTP_HOST;
                int port = Integer.valueOf(SFTP_PORT);
                String userName = SFTP_USER_NAME;
                String password = SFTP_USER_PASSWORD;
                Integer timeout = Integer.valueOf(SFTP_TIMEOUT);
                Integer aliveMax = Integer.valueOf(SFTP_ALIVEMAX);
                // 创建JSch对象
                JSch jsch = new JSch();
                Session session = jsch.getSession(userName, host, port);
                // 根据用户名,主机ip,端口获取一个Session对象
                if (password != null) {
                    // 设置密码
                    session.setPassword(password);
                }
                // 为Session对象设置properties
                session.setConfig("StrictHostKeyChecking", "no");
                if (timeout != null) {
                    // 设置timeout时间
                    session.setTimeout(timeout);
                }
                if (aliveMax != null) {
                    session.setServerAliveCountMax(aliveMax);
                }
                // 通过Session建立链接
                session.connect();
                // 打开SFTP通道
                ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
                // 建立SFTP通道的连接
                channel.connect();
                channelLocal.set(channel);
                sessionLocal.set(session);
                logger.debug("SSH Channel connected.session={},channel={}", session, channel);
            } catch (JSchException e) {
                throw new SystemException(ImageExceptionCode.FTP_SEND_ERROR);
            }
            return this;
        }
    
        //断开连接
        public void disconnect() {
            ChannelSftp channel = channelLocal.get();
            Session session = sessionLocal.get();
            //断开sftp连接
            if (channel != null) {
                channel.disconnect();
                logger.debug("SSH Channel disconnected.channel={}", channel);
            }
            //断开sftp连接之后,再断开session连接
            if (session != null) {
                session.disconnect();
                logger.debug("SSH session disconnected.session={}", session);
            }
            channelLocal.remove();
            sessionLocal.remove();
        }
    }

     因为使用jsch的sftp有一个要注意的地方,当调用ChannelSftp.disconnect()断开sftp的连接之后,该连接的session还存在,这时,需要显式调用Session.disconnect()来断掉session。程序设计的时候也考虑到这一点了,公共方法设计的是没问题的,那么问题在哪呢?

    调整日志级别,通过查看日志定位出问题所在,业务层在调用时,执行两次init()创建了两个sftp连接,但当遇到异常时,仅仅执行了一次disconnect()释放了第二次创建的,第一个连接一直没有得到释放。

     try {
         sftpClient.init();
         for(XxxDto is:list){     
             /*********业务代码,略*************/
             try {
             /*********业务代码,略*************/
                try {
             /*********业务代码,略*************/
                  } catch (Exception e) {
                       throw new BusinessException(XxxExceptionCode.UNDER_USERID_FAIL);
               }
             /*********下载业务代码,问题所在,此处抛出异常*************/
              } catch (Exception e) {
                       throw new BusinessException( XxxExceptionCode.FTP_DOWNLOAD_LOCAL_FAIL);
             }
         }
      } finally {
          sftpClient.disconnect();
      }

    下载业务代码如下:

    try {
        sftpClient.init();
        /*************下载业务代码,此处抛出异常被上层捕获,该方法创建的连接被释放,但上层的连接 enenenenene *****************/
    } finally {
        sftpClient.disconnect();
    }

    业务层的代码不规范,sftp连接在第一次初始化之后就不需要再在方法层里执行初始化了,这不但加剧了资源的消耗,而且由于在业务层有嵌套try,最里面抛出异常未将第一个连接释放就再次执行初始化,导致未释放的sftp原来越多。

    将下载业务代码里的init()和disconnect()方法去掉,把sftp的连接和断开只交给上一层的业务层进行控制。

    修改后的下载业务代码如下:

    /*************下载业务代码,只专注业务 *****************/

    附:问题排查过程中部分命令如下:

    https://www.cnblogs.com/zjfjava/p/11007348.html

  • 相关阅读:
    “XXXXX” is damaged and can’t be opened. You should move it to the Trash 解决方案
    深入浅出 eBPF 安全项目 Tracee
    Unity3d开发的知名大型游戏案例
    Unity 3D 拥有强大的编辑界面
    Unity 3D物理引擎详解
    Unity 3D图形用户界面及常用控件
    Unity 3D的视图与相应的基础操作方法
    Unity Technologies 公司开发的三维游戏制作引擎——Unity 3D
    重学计算机
    windows cmd用户操作,添加,设备管理员组,允许修改密码
  • 原文地址:https://www.cnblogs.com/zjfjava/p/11007358.html
Copyright © 2011-2022 走看看