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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM