JSchException verify: false

1. 产生此异常的原因

 高并发下,SFTP上传偶现com.jcraft.jsch.JSchException: verify: false的异常(大概上传几百次就会抛出一次这个异常)。JSch版本日志中描述该问题在jsch-0.1.50已修复,但是升级后这个bug还是会出现。
 

 

 2. 如何解决

2.1 出现异常进行重试

一般情况下只要重试一次就能连接成功
/**
 * 连接登陆远程服务器,异常重试三次
 * @return
 */
public ChannelSftp connect() throws Exception{
   return connect(3);
}


/**
 * 连接登陆远程服务器
 *
 * @return
 */
private ChannelSftp connect(int retrycount) throws Exception {
    JSch jSch = new JSch();
    Session session = null;
    ChannelSftp sftp = null;
    try {
        session = jSch.getSession(loginName, server, port);
        session.setPassword(loginPassword);
        session.setConfig(this.getSshConfig());
        session.connect();

        sftp = (ChannelSftp) session.openChannel("sftp");
        sftp.connect();
    }catch (JSchException e){
        //重试3次
        //不是verify: false异常只重试一次
        if(!"verify: false".equals(e.getMessage())){
            retrycount = retrycount-2;
        }
        retrycount--;
        if(retrycount <0){
            log.error("SSH方式连接FTP服务器时有JSchException异常",e);
            throw e;
        }
        log.warn("SSH方式连接FTP服务器时有JSchException异常:{}!,进行重试,第{}次重试",e.getMessage(),3-retrycount);
        return connect(retrycount);
    } catch (Exception e) {
        log.error("SSH方式连接FTP服务器时有Exception异常!",e);
        return null;
    }
    return sftp;
}

/**
 * 获取服务配置
 * @return
 */
private Properties getSshConfig() {
    Properties sshConfig =  new Properties();
    sshConfig.put("StrictHostKeyChecking", "no");
    return sshConfig;
}
 
 

2.2 避免每次上传文件都新建连接

例如:使用连接对象池
sftp连接池示例

1. maven依赖

<!-- 对象池依赖 -->
<dependency>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-pool2</artifactId>
     <version>2.8.0</version>
</dependency>

 

2. SftpClientObject

package com.chenly.demo.basepool.pool;

import com.chenly.demo.basepool.config.SftpProperties;
import com.chenly.demo.keypool.config.SftpConfig;
import com.jcraft.jsch.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Properties;

/**
 * @author: chenly
 * @date: 2022-11-01 11:02
 * @description:
 * @version: 1.0
 */
@Data
@Slf4j
public class SftpClientObject {

    private ChannelSftp sftp;

    public SftpClientObject(SftpProperties sftpProperties) throws Exception{
        connect(sftpProperties,3);
    }
        /**
     * 连接登陆远程服务器
     */
    public ChannelSftp connect(SftpProperties sftpProperties,int retrycount) throws Exception  {
        JSch jSch = new JSch();
        try{
            Session session = null;
            session = jSch.getSession(sftpProperties.getUsername(), sftpProperties.getHost(), sftpProperties.getPort());
            session.setPassword(sftpProperties.getPassword());
            session.setConfig(this.getSshConfig());
            session.connect();
            sftp = (ChannelSftp)session.openChannel("sftp");
            sftp.connect();
            return sftp;
        }catch (JSchException e){
            //重试3次
            //不是verify: false异常只重试一次
            if(!"verify: false".equals(e.getMessage())){
                retrycount = retrycount-2;
            }
            retrycount--;
            if(retrycount <0){
                log.error("SSH方式连接FTP服务器时有JSchException异常",e);
                throw e;
            }
            log.warn("SSH方式连接FTP服务器时有JSchException异常:{}!,进行重试,第{}次重试",e.getMessage(),3-retrycount);
            return connect(sftpProperties,retrycount);
        } catch (Exception e) {
            log.error("SSH方式连接FTP服务器时有Exception异常!",e);
            throw e;
        }
    }
       /**
     * 服务配置
     * @return
     */
    private Properties getSshConfig() {
        Properties sshConfig =  new Properties();
        sshConfig.put("StrictHostKeyChecking", "no");
        return sshConfig;
    }

    /**
     * 关闭连接
     */
    public void disconnect() {
        try {
            if(sftp!=null){
                if(sftp.getSession().isConnected()){
                    sftp.getSession().disconnect();
                }
                sftp.disconnect();
            }

        } catch (Exception e) {
            log.error("关闭与sftp服务器会话连接异常",e);
        }
    }

    //验证连接
    public boolean validateConnect() {
        try {
            if(sftp == null){
                return false;
            }
            return sftp.getSession().isConnected() && sftp.isConnected();
        } catch (Exception e) {
            return false;
        }
    }
}

 

 

3. SftpProperties (sftp连接配置、连接池配置)

package com.chenly.demo.basepool.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * @author: chenly
 * @date: 2022-11-01 11:14
 * @description:
 * @version: 1.0
 */
@Component
@ConfigurationProperties("sftp")
@Data
public class SftpProperties {
    /** host */
    private String host;
    /**  端口号 */
    private int port = 22;
    /**  用户名*/
    private String username;
    /** 密码 */
    private String password;
    /** 字符集*/
    private String charset;
    /** sftp目录*/
    private String remotePath;
    /**  连接池配置 */
    private Pool pool;

    /**  连接池配置类 */
    @Data
    public static class Pool {
        /** 池中最小的连接数,只有当 timeBetweenEvictionRuns 为正时才有效*/
        private int minIdle = 0;
        /** 池中最大的空闲连接数,为负值时表示无限*/
        private int maxIdle = 8;
        /** 池可以产生的最大对象数,为负值时表示无限 */
        private int maxActive = 16;
        /** 当池耗尽时,阻塞的最长时间,为负值时无限等待 */
        private long maxWait = -1;
        /** 从池中取出对象是是否检测可用 */
        private boolean testOnBorrow = true;
        /**  将对象返还给池时检测是否可用*/
        private boolean testOnReturn = false;
        /** 检查连接池对象是否可用*/
        private boolean testWhileIdle = true;
        /** 距离上次空闲线程检测完成多久后再次执行*/
        private long timeBetweenEvictionRuns = 300000L;
    }
}

4.SftpPooledObjectFactory(对象池内对象工厂类)

package com.chenly.demo.basepool.pool;

import com.chenly.demo.basepool.config.SftpProperties;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
/**
 * @author: chenly
 * @date: 2022-11-01 11:41
 * @description:
 * @version: 1.0
 */
public class SftpPooledObjectFactory extends BasePooledObjectFactory<SftpClientObject> {

    private final SftpProperties sftpProperties;

    public SftpPooledObjectFactory(SftpProperties sftpProperties) {
        this.sftpProperties = sftpProperties;
    }

    @Override
    public SftpClientObject create() throws Exception {
        return new SftpClientObject(sftpProperties);
    }

    @Override
    public PooledObject<SftpClientObject> wrap(SftpClientObject sftpClient) {
        return new DefaultPooledObject<>(sftpClient);
    }

    @Override
    public boolean validateObject(PooledObject<SftpClientObject> p) {
        return p.getObject().validateConnect();
    }

    @Override
    public void destroyObject(PooledObject<SftpClientObject> p) {
        p.getObject().disconnect();
    }

}
 

5. SftpObjectPool (对象池)

package com.chenly.demo.basepool.pool;
import com.chenly.demo.basepool.config.SftpProperties;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 * @author: chenly
 * @date: 2022-11-01 11:17
 * @description:
 * @version: 1.0
 */
public class SftpObjectPool implements ObjectPool<SftpClientObject> {
    private  GenericObjectPool<SftpClientObject> internalPool;

    public SftpObjectPool(SftpProperties sftpProperties) {
        this.internalPool = new GenericObjectPool<SftpClientObject>(new SftpPooledObjectFactory(sftpProperties), getPoolConfig(sftpProperties.getPool())){
            @Override
            public void returnObject(SftpClientObject sftpClient) {
                try {
                    sftpClient.getSftp().cd(sftpProperties.getRemotePath());
                } catch (Exception e) {
                }
                super.returnObject(sftpClient);
            }
        };
    }

    @Override
    public void addObject() throws Exception {
        internalPool.addObject();
    }

    @Override
    public SftpClientObject borrowObject() throws Exception {
        return internalPool.borrowObject();
    }

    @Override
    public void clear() {
        internalPool.clear();
    }

    @Override
    public void close() {
        internalPool.close();
    }

    @Override
    public int getNumActive() {
        return internalPool.getNumActive();
    }

    @Override
    public int getNumIdle() {
        return internalPool.getNumIdle();
    }

    @Override
    public void invalidateObject(SftpClientObject obj) throws Exception {
        internalPool.invalidateObject(obj);
    }

    @Override
    public void returnObject(SftpClientObject obj) {
        internalPool.returnObject(obj);
    }


    private GenericObjectPoolConfig<SftpClientObject> getPoolConfig(SftpProperties.Pool properties) {
        if (properties == null) {
            properties = new SftpProperties.Pool();
        }
        GenericObjectPoolConfig<SftpClientObject> config = new GenericObjectPoolConfig<>();
        config.setMinIdle(properties.getMinIdle());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxWaitMillis(properties.getMaxWait());
        config.setTestOnBorrow(properties.isTestOnBorrow());
        config.setTestOnReturn(properties.isTestOnReturn());
        config.setTestWhileIdle(properties.isTestWhileIdle());
        config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns());
        return config;
    }
}

 

6.SftpController (测试类)

package com.chenly.demo.basepool.controller;

import com.chenly.demo.basepool.config.SftpProperties;
import com.chenly.demo.basepool.pool.SftpClientObject;
import com.chenly.demo.basepool.pool.SftpObjectPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * @author: chenly
 * @date: 2022-11-01 11:18
 * @description:
 * @version: 1.0
 */
@Slf4j
@RestController
public class SftpController {

    @Resource
    private SftpObjectPool sftpPool;
    @Resource
    private SftpProperties sftpProperties;

    @GetMapping("/file/download")
    public Integer fileDownload() {
        SftpClientObject sftpClient = null;
        try {
            log.info("sftp连接池大小{}",sftpPool.getNumIdle());
            sftpClient = sftpPool.borrowObject();
            InputStream inputStream = sftpClient.getSftp().get("/test1/DMC_PROD_XXPT_TABLE_20221019.dat");
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            IOUtils.copy(inputStream,byteArrayOutputStream);
            return byteArrayOutputStream.size();
        } catch (Exception e1) {
            log.info("下载异常!",e1);
            try {
                sftpPool.invalidateObject(sftpClient);
            } catch (Exception e2) {
                log.info("销毁sftp连接异常!",e2);
            }
            sftpClient = null;
        } finally {
            if (null != sftpClient) {
                sftpPool.returnObject(sftpClient);
            }
        }
        return null;
    }
}

 

7. 配置文件application.properties

server.port=9090
#sftp配置
sftp.host=192.168.2.32
sftp.port=22
sftp.username=sftp1
sftp.password=123qwe
sftp.charset=GBK
sftp.remote-path=/test1
sftp.pool.min-idle=0
sftp.pool.maxIdle=16
sftp.pool.maxActive=16
sftp.pool.maxWait=-1
sftp.pool.testOnBorrow=true
sftp.pool.testOnReturn=false
sftp.pool.timeBetweenEvictionRuns=300000

 


 
posted @ 2022-12-15 14:00  harara  阅读(1676)  评论(0编辑  收藏  举报