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
作者:小念
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。