項目中經過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
/*************下載業務代碼,只專一業務 *****************/
附:問題排查過程當中部分命令以下:設計