连接池介绍项目使用sftp作为存储介质,需要大量的上传下载,单例的sftp不能满足场景,使用ThreadLocal存储sftp连接,会频繁创建销毁连接,性能太差,于是想到将连接对象池化。
对象池顾名思义就是存放对象的池,与我们常听到的线程池、数据库连接池、http连接池等一样,都是典型的池化设计思想。
对象池的优点就是可以集中管理池中对象,减少频繁创建和销毁长期使用的对象,从而提升复用性,以节约资源的消耗,可以有效避免频繁为对象分配内存和释放堆中内存,进而减轻jvm垃圾收集器的负担,避免内存抖动。
Apache Common Pool2 是Apache提供的一个通用对象池技术实现,可以方便定制化自己需要的对象池,大名鼎鼎的 Redis 客户端 Jedis 内部连接池就是基于它来实现的。
- 依赖
com.jcraft jsch 0.1.55 org.apache.commons commons-pool2 2.8.0
- sftpClient,用于创建连接,验证连接,销毁连接使用
package com.zhanghao.base.util;
import com.jcraft.jsch.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
@Data
public class SftpClient {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final AtomicLong CLIENT_NUMBER = new AtomicLong(1L);
private ChannelSftp channelSftp;
private Session session;
private String clientInfo = "sftpclient";
private String originalDir;
//创建连接
public SftpClient(SftpProperties sftpProperties) throws SftpException, JSchException {
try {
JSch jsch = new JSch();
session = jsch.getSession(sftpProperties.getUsername(), sftpProperties.getHost(), sftpProperties.getPort());
session.setPassword(sftpProperties.getPassword());
Properties config = new Properties();
if (sftpProperties.getSession() != null) {
sftpProperties.getSession().forEach(config::put);
}
session.setConfig(config);
session.connect();
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
clientInfo += CLIENT_NUMBER.getAndIncrement() + ",createTime:" + DATE_TIME_FORMATTER.format(LocalDateTime.now());
originalDir = channelSftp.pwd();
} catch (Exception e) {
disconnect();
throw e;
}
}
//断开连接
public void disconnect() {
if (channelSftp != null) {
try {
channelSftp.disconnect();
} catch (Exception ignored) {
}
}
if (session != null) {
try {
session.disconnect();
} catch (Exception ignored) {
}
}
}
//验证连接
public boolean validateConnect() {
try {
return session.isConnected() && channelSftp.isConnected() && originalDir.equals(channelSftp.pwd());
} catch (Exception e) {
return false;
}
}
}
- 配置
package com.zhanghao.base.util;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@ConfigurationProperties("sftp")
@Getter
@Setter
public class SftpProperties {
private String host;
private int port = 22;
private String usernamel;
private String password;
private Map session;
private Pool pool;
@Data
public static class Pool {
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;
}
}
- 连接池(核心)
package com.zhanghao.base.util; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; public class SftpPool implements ObjectPool{ private final GenericObjectPool internalPool; public SftpPool(SftpProperties sftpProperties) { this.internalPool = new GenericObjectPool (new SftpFactory(sftpProperties), getPoolConfig(sftpProperties.getPool())){ @Override public void returnObject(SftpClient sftpClient) { try { sftpClient.getChannelSftp().cd(sftpClient.getOriginalDir()); } catch (Exception ignored) { } super.returnObject(sftpClient); } }; } @Override public void addObject() throws Exception { internalPool.addObject(); } @Override public SftpClient 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(SftpClient obj) throws Exception { internalPool.invalidateObject(obj); } @Override public void returnObject(SftpClient obj) { internalPool.returnObject(obj); } private static class SftpFactory extends BasePooledObjectFactory { private final SftpProperties sftpProperties; public SftpFactory(SftpProperties sftpProperties) { this.sftpProperties = sftpProperties; } @Override public SftpClient create() throws Exception { return new SftpClient(sftpProperties); } @Override public PooledObject wrap(SftpClient sftpClient) { return new DefaultPooledObject<>(sftpClient); } @Override public boolean validateObject(PooledObject p) { return p.getObject().validateConnect(); } @Override public void destroyObject(PooledObject p) { p.getObject().disconnect(); } } private GenericObjectPoolConfig getPoolConfig(SftpProperties.Pool properties) { if (properties == null) { properties = new SftpProperties.Pool(); } GenericObjectPoolConfig 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; } }
- 实例化对象池
package com.zhanghao.base.util;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SftpPoolConfig {
@Bean
public SftpPool getSftpPool(SftpProperties sftpProperties){
return new SftpPool(sftpProperties);
}
}
- demo
@Resource
private SftpPool sftpPool;
@GetMapping("/file/download")
public Integer fileDownload() {
SftpClient sftpClient = null;
try {
log.info("sftp连接池大小{}",sftpPool.getNumIdle());
sftpClient = sftpPool.borrowObject();
InputStream inputStream = sftpClient.getChannelSftp().get("/upload/233.jpg");
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;
}
- 压测数据解析
2022-07-26 18:26:06.467 INFO 6424 --- [io-8090-exec-77] c.zhanghao.base.biz.BizCommonController : sftp连接池大小8 2022-07-26 18:26:06.471 INFO 6424 --- [o-8090-exec-113] c.zhanghao.base.biz.BizCommonController : sftp连接池大小7
总结:
根据上文的配置,200线程下,最大创建了8个连接对象,根据根据自己的需要调整最大连接对象数和空闲数,正常情况下保持几个连接就够用了。



