项目中经过jsch中的sftp实现上传下载文件。在压测过程当中,因为调用到sftp,下载文件不存在时,系统不断抛出异常,内存飙升,逐渐把swap区也占满,经过top监控未发现占用内存的进程,经过查找sshd进程,发现服务器多了不少sftp的进程没有被关闭。html
刚开始觉得是sftp公共方法设计的有问题,每次建立链接都未释放,下面是部分代码片断java
@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()释放了第二次建立的,第一个链接一直没有获得释放。session
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(); }
下载业务代码以下:ssh
try { sftpClient.init(); /*************下载业务代码,此处抛出异常被上层捕获,该方法建立的链接被释放,但上层的链接 enenenenene *****************/ } finally { sftpClient.disconnect(); }
业务层的代码不规范,sftp链接在第一次初始化以后就不须要再在方法层里执行初始化了,这不但加重了资源的消耗,并且因为在业务层有嵌套try,最里面抛出异常未将第一个链接释放就再次执行初始化,致使未释放的sftp原来越多。this
将下载业务代码里的init()和disconnect()方法去掉,把sftp的链接和断开只交给上一层的业务层进行控制。spa
修改后的下载业务代码以下:debug
/*************下载业务代码,只专一业务 *****************/
附:问题排查过程当中部分命令以下:设计