common-pool2实现sftp连接池

背景

项目使用sftp作为存储介质,需要大量的上传下载,单例的sftp不能满足场景,使用ThreadLocal存储sftp连接,会频繁创建销毁连接,性能太差,于是想到将连接对象池化。

common-pool2介绍

Apache Common Pool2 是Apache提供的一个通用对象池技术实现,可以方便定制化自己需要的对象池,大名鼎鼎的 Redis 客户端 Jedis 内部连接池就是基于它来实现的。

  • 依赖
   <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.55</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.8.0</version>
    </dependency>
  • 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;

/**
 * @author zh
 */
@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";
  /**
   * ssh 根目录。
   * 用于判断是否成功返回连接到连接池的条件之一
   */
  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;

/**
 * @author zh
 */
@Component
@ConfigurationProperties("sftp")
@Getter
@Setter
public class SftpProperties {
  /**
   * 地址
   */
  private String host;
  /**
   * 端口号
   */
  private int port = 22;
  /**
   * 用户名
   */
  private String usernamel;
  /**
   * 密码
   */
  private String password;
  /**
   * Session 参数配置
   */
  private Map<String, String> session;
  /**
   * 连接池配置
   */
  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;
  }
}


  • 连接池(核心)
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;


/**
 * @author zh
 */
public class SftpPool implements ObjectPool<SftpClient> {
  private final GenericObjectPool<SftpClient> internalPool;

  public SftpPool(SftpProperties sftpProperties) {
    this.internalPool = new GenericObjectPool<SftpClient>(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<SftpClient> {

    private final SftpProperties sftpProperties;

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

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

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

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

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

  }

  private GenericObjectPoolConfig<SftpClient> getPoolConfig(SftpProperties.Pool properties) {
    if (properties == null) {
      properties = new SftpProperties.Pool();
    }
    GenericObjectPoolConfig<SftpClient> 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;

/**
 *
 * @author zh
 */
@Configuration
public class SftpPoolConfig {

    @Bean
    public SftpPool getSftpPool(SftpProperties sftpProperties){
        return new SftpPool(sftpProperties);
    }
}

  • demo
@Resource
    private SftpPool sftpPool;
    /**
     * demo
     *
     * @return void
     * @author zh
     */
    @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个连接对象,根据根据自己的需要调整最大连接对象数和空闲数,正常情况下保持几个连接就够用了。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用Spring Boot和commons-pool2实现SFTP连接池的示例代码: 1.添加依赖 ```xml <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.2</version> </dependency> ``` 2.配置连接池 ```java @Configuration public class SftpPoolConfig { @Value("${sftp.host}") private String host; @Value("${sftp.port}") private int port; @Value("${sftp.username}") private String username; @Value("${sftp.password}") private String password; @Value("${sftp.timeout}") private int timeout; @Value("${sftp.pool.maxTotal}") private int maxTotal; @Value("${sftp.pool.maxIdle}") private int maxIdle; @Value("${sftp.pool.minIdle}") private int minIdle; @Bean public GenericObjectPool<ChannelSftp> sftpPool() { GenericObjectPoolConfig<ChannelSftp> poolConfig = new GenericObjectPoolConfig<>(); poolConfig.setMaxTotal(maxTotal); poolConfig.setMaxIdle(maxIdle); poolConfig.setMinIdle(minIdle); poolConfig.setTestOnBorrow(true); poolConfig.setTestOnReturn(true); poolConfig.setTestWhileIdle(true); poolConfig.setTimeBetweenEvictionRunsMillis(60000); poolConfig.setMinEvictableIdleTimeMillis(1800000); return new GenericObjectPool<>(new SftpPoolFactory(host, port, username, password, timeout), poolConfig); } } ``` 3.实现连接池工厂 ```java public class SftpPoolFactory extends BasePooledObjectFactory<ChannelSftp> { private String host; private int port; private String username; private String password; private int timeout; public SftpPoolFactory(String host, int port, String username, String password, int timeout) { this.host = host; this.port = port; this.username = username; this.password = password; this.timeout = timeout; } @Override public ChannelSftp create() throws Exception { JSch jsch = new JSch(); Session session = jsch.getSession(username, host, port); session.setPassword(password); Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.setTimeout(timeout); session.connect(); Channel channel = session.openChannel("sftp"); channel.connect(); return (ChannelSftp) channel; } @Override public PooledObject<ChannelSftp> wrap(ChannelSftp sftp) { return new DefaultPooledObject<>(sftp); } @Override public void destroyObject(PooledObject<ChannelSftp> p) throws Exception { ChannelSftp sftp = p.getObject(); if (sftp.isConnected()) { sftp.disconnect(); } } @Override public boolean validateObject(PooledObject<ChannelSftp> p) { ChannelSftp sftp = p.getObject(); return sftp.isConnected(); } } ``` 4.使用连接池 ```java @Service public class SftpService { @Autowired private GenericObjectPool<ChannelSftp> sftpPool; public void upload(String remotePath, String fileName, InputStream inputStream) throws Exception { ChannelSftp sftp = sftpPool.borrowObject(); try { sftp.cd(remotePath); } catch (SftpException e) { sftp.mkdir(remotePath); sftp.cd(remotePath); } sftp.put(inputStream, fileName); sftpPool.returnObject(sftp); } public InputStream download(String remotePath, String fileName) throws Exception { ChannelSftp sftp = sftpPool.borrowObject(); sftp.cd(remotePath); InputStream inputStream = sftp.get(fileName); sftpPool.returnObject(sftp); return inputStream; } } ``` 相关问题:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值