背景
高并发下,SFTP上传偶现com.jcraft.jsch.JSchException: verify: false,网上有说升级版本什么的修复了这个bug,然而升级版本后事实证明这个bug还是会出现,大概上传几百次会出现一次。但是项目不可能允许文件丢失,需要百分百的正确率。
SFTP工具类
package util;import com.jcraft.jsch.*;
import org.springframework.util.StringUtils;import java.io.InputStream;
import java.util.*;public class SFTPUtil {private ChannelSftp sftp;private Session session;/*** SFTP 登录用户名*/private String username;/*** SFTP 登录密码*/private String password;/*** SFTP 服务器地址IP地址*/private String host;/*** SFTP 端口*/private int port;/*** 构造基于密码认证的sftp对象*/public SFTPUtil(String username, String password, String host, int port) {this.username = username;this.password = password;this.host = host;this.port = port;}public SFTPUtil() {}/*** 连接sftp服务器*/public void login() {try {JSch jsch = new JSch();session = jsch.getSession(username, host, port);if (password != null) {session.setPassword(password);}Properties config = new Properties();config.put("StrictHostKeyChecking", "no");session.setConfig(config);session.connect();Channel channel = session.openChannel("sftp");channel.connect();sftp = (ChannelSftp) channel;} catch (Exception e) {e.printStackTrace();}}/*** 关闭连接 server*/public void logout() {if (sftp != null) {if (sftp.isConnected()) {sftp.disconnect();}}if (session != null) {if (session.isConnected()) {session.disconnect();}}}/*** 将输入流的数据上传到sftp作为文件。文件完整路径=basePath+directory** @param directory 上传到该目录* @param sftpFileName sftp端文件名* @param is 文件输入流*/public boolean upload(String directory, String sftpFileName, InputStream is){try {if (!StringUtils.isEmpty(directory)) {sftp.cd(directory);}sftp.put(is, sftpFileName); //上传文件return true;} catch (Exception e) {e.printStackTrace();return false;}}}
测试代码
import util.SFTPUtil;import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** @description:* @author:wanzh* @date:2022/3/29*/
public class TestSFTPUtils {private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(128, 128, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500));public static void main(String[] args) throws InterruptedException {testSFTP(500);}private static void testSFTP(int num) throws InterruptedException {long startTime = System.currentTimeMillis();AtomicInteger successNum = new AtomicInteger();CountDownLatch countDownLatch = new CountDownLatch(num);for(int i = 0; i < num; i++){threadPoolExecutor.execute(()->{boolean flag = false;try {File file = new File("D:\\work.jpg");InputStream is = new FileInputStream(file);String directName = "/upload/nvrm/20220107/";SFTPUtil sftpUtil = new SFTPUtil("xxx", "xxx", "xxx", 22);sftpUtil.login();flag = sftpUtil.upload(directName, UUID.randomUUID()+ ".jpg", is);sftpUtil.logout();}catch (Exception e){e.printStackTrace();}if(flag){successNum.incrementAndGet();}countDownLatch.countDown();});}countDownLatch.await();threadPoolExecutor.shutdown();long endTime = System.currentTimeMillis();System.out.println(String.format("总文件数量:%s,成功传输文件数量:%s,花费时间: %s秒",num,successNum.get(),(endTime - startTime)/1000));}
}
测试效果



解决方案
大概思路,首先是上传失败后重试,其次为了提升传输速度,每个线程只登录一次即可,不需要每传输一个文件登录退出。
public class TestSFTPUtils2 {private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(128, 128, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500));private static ThreadLocal<SFTPUtil> sftpUtilThreadLocal = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {testSFTP(500);}private static void testSFTP(int num) throws InterruptedException {long startTime = System.currentTimeMillis();AtomicInteger successNum = new AtomicInteger();CountDownLatch countDownLatch = new CountDownLatch(num);for(int i = 0; i < num; i++){threadPoolExecutor.execute(()->{boolean flag = false;try {File file = new File("D:\\work.jpg");InputStream is = new FileInputStream(file);String directName = "/upload/nvrm/20220107/";flag = upload( UUID.randomUUID()+ ".jpg", directName,is,1);}catch (Exception e){e.printStackTrace();}if(flag){successNum.incrementAndGet();}countDownLatch.countDown();});}countDownLatch.await();threadPoolExecutor.shutdown();long endTime = System.currentTimeMillis();System.out.println(String.format("总文件数量:%s,成功传输文件数量:%s,花费时间: %s秒",num,successNum.get(),(endTime - startTime)/1000));}/*** 传输文件* @param fileName 文件名* @param directName 目录* @param inputStream 文件流* @param num 传输次数* @return*/public static boolean upload(String fileName, String directName,InputStream inputStream, int num) {if(num > 1){System.out.println(String.format("重新上传,文件名:%s,次数:%s",fileName,num));}SFTPUtil sftpUtil = sftpUtilThreadLocal.get();if(sftpUtil == null){sftpUtil = new SFTPUtil("xxx", "xxx", "xxxx", 22);sftpUtil.login();sftpUtilThreadLocal.set(sftpUtil);}if(num > 3){return false;}boolean upload = sftpUtil.upload(directName, fileName, inputStream);if(upload){return true;}sftpUtil.logout();sftpUtilThreadLocal.remove();sftpUtil = new SFTPUtil("xxx", "xxx", "xxxx", 22);sftpUtil.login();sftpUtilThreadLocal.set(sftpUtil);return upload(fileName,directName,inputStream,num+1);}}
测试效果


总结
若文件个数越多,提升速度越明显,项目中解决方案大致如上,这里只是简单模拟测试一下,希望对您有帮助。












