jsch鏈接sftp後鏈接未釋放掉問題排查

項目中經過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

/*************下載業務代碼,只專一業務 *****************/

 

附:問題排查過程當中部分命令以下:設計

http://www.javashuo.com/article/p-sslhkrbs-hk.html日誌

相關文章
相關標籤/搜索