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

  • 相关阅读:
    10个令人惊奇的HTML5和JavaScript效果
    SQL Server 错误代码:17058 。解决方案
    [备忘]ASP.NET 惊爆新安全漏洞 攻击者可访问任意文件
    Senparc Google Sitemap(代号:SenMapic)网站地图生成器升级至v1.3
    Error 1 Project file must include the .NET Framework assembly 'PresentationCore, PresentationFramework' in the reference
    [备忘].NET Framework V2.0 Obsolete API List
    [备忘]在VirtualBox中收缩虚拟磁盘映像文件
    [备忘]修改SQL Server 2005/2008 用户数据库文件默认路径和默认备份路径
    [备忘]解决ManualResetEvent.WaitOne在Silverlight环境下将整个UI线程卡死,并无法继续调用异步子线程的问题
    [备忘]21个演示展示强大的jQuery特效
  • 原文地址:https://www.cnblogs.com/zjfjava/p/11007358.html
Copyright © 2011-2022 走看看