背景
高并发下,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);
}
}
测试效果
总结
若文件个数越多,提升速度越明显,项目中解决方案大致如上,这里只是简单模拟测试一下,希望对您有帮助。