commom-pool对象池
一、简介
comoms-pool2是Apache下一个开源的公共资源池。我们可以根据它来快速的建立一个自己的对象池。组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现。
大家常用的dbcp数据库连接池,也是基于comoms-pool实现。
对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的”容器”,被称为对象池(Object Pool)
二、使用对象池的利与弊
1、弊
- 增加复杂度:正常对象的生命周期由使用者和JVM维护:使用者创建(JVM分配内存)→ 销毁→ GC。对象池对象的生命周期更为复杂,需要引入多个状态:IDLE(空闲)、ALLOCATED(分配)、EVICTION(驱逐)、VALIDATION(验证)、INVALID(无效)、RETURING(返回中)。
- 多线程资源竞争:commons-pool内部大量使用了synchronized及其他juc原子类,可能会造成锁竞争,影响程序性能
- 参数配置失误可能会带来严重问题
2、利
- 复用池中对象,减少 创建对象、回收对象时所产生的开销
3、适用场景
- 创建成本高昂的对象:并非所有对象都适合拿来池化,因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了
- 数量受限的资源:比如数据库连接
- 资源受限的环境:cpu/内存等物理资源有限,垃圾收集,内存抖动会造成较大影响,需要提供内存管理效率,响应性比吞吐量更为重要
三、commons-pool结构分析
1、基本框架
它主要由3个大的部分组成:
ObjectPool:对象池,专门实现对象存取和状态管理的池实现。
PooledObject:池化对象,是对需要放到池里对象的一个包装类。添加了一些附加的信息,比如说状态信息,创建时间,激活时间,关闭时间等。这些添加的信息方便pool来管理和实现一些特定的操作。
PooledObjectFactory:对象工厂类,负责具体对象的创建、初始化、对象状态的销毁和验证,commons-pools框架本身提供了默认的抽象实现BasePooledObjectFactory,业务方在使用的时候只需要继承该类,然后实现wrap和create方法即可。
以上三者构成了一个object pool的基本框架。他们的关系可以用如下的一个图来描述:
用一句话来概括他们整体的关系就是factory创建需要放入pool的对象,经过PooledObject包装一下就可以上架了
2、对象池Pool的创建
以GenericObjectPool为例
public GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) : 此方法创建一个GenericObjectPool实例
GenericObjectPool类已经实现了和对象池有关的所有核心操作,开发者可以通过继承或者封装的方式来使用它,通过此构造函数,我们能够清晰的看到,一个Pool中需要指定PoolableObjectFactory实例,以及此对象池的Config信息。
此构造函数实例化了一个LinkedList作为”对象池”容器,用来存取“对象”。此外还会根据timeBetweenEvictionRunsMillis的值来决定是否启动一个后台线程,此线程用来周期性扫描pool中的对象列表,检测“对象池中的对象”空闲(idle)的时间是否达到了阈值,如果是则移除此对象
3、对象工厂PoolableObjectFactory接口
commons-pool通过使用ObjectFactory(工厂模式)的方式将"对象池中的对象"的创建、检测、销毁等特性解耦出来,这是一个非常良好的设计思想.此接口有一个抽象类BasePoolableObjectFactory,可供开发者继承和实现.
- Object makeObject() : 创建一个新对象;当对象池中的对象个数不足时,将会使用此方法来“输出”一个新的“对象”,并交付给对象池管理
- void destroyObject(Object obj) :销毁对象;如果对象池中检测到某个”对象”idle的时间超时,或者操作者向对象池“归还对象”时检测到“对象”已经无效,那么此时将会销毁对象。调用此方法时,“对象”的生命周期必须结束,如果object是线程,那么此时线程必须退出;如果object是socket操作,那么此时socket必须关闭;如果object是文件流操作,那么此时“数据flush”且正常关闭。
- boolean validateObject(Object obj) :检测对象是否“有效”;Pool中不能保存无效的对象,因此后台检测线程会周期性的检测Pool中的对象的有效性,如果对象无效,则会将对象从Pool中移除并destroy。
- void activateObject(Object obj) :“激活”对象,当Pool决定输出一个对象给调用者时,会调用此方法激活对象。
- void void passivateObject(Object obj) : “钝化”对象,当调用者“归还对象”时,Pool将会钝化对象
4、ObjectPool接口
ObjectPool是一个顶层接口,里面定义了对象池的一些基本操作,GenericObjectPool是一个具体的对象池的实现。
public interface ObjectPool<T> extends Closeable { /** 向对象池中添加对象 */ void addObject() throws Exception, IllegalStateException, UnsupportedOperationException; /** 从对象池中借出对象 */ T borrowObject() throws Exception, NoSuchElementException, IllegalStateException; /** 清空对象池,并销毁对象池中所有对象 */ void clear() throws Exception, UnsupportedOperationException; /** 关闭对象池并释放资源 */ @Override void close(); /** 得到当前对象池已经借出的对象数量 */ int getNumActive(); /** 得到当前对象池中空闲对象数量 */ int getNumIdle(); /** 使对象无效*/ void invalidateObject(T obj) throws Exception; /** 使对象归还到对象池中 */ void returnObject(T obj) throws Exception; }
5、对象生命周期
四、config详解
- maxActive :连接池中最大连接数,默认为8
- maxIdle :连接池中最大空闲的连接数,默认为8
- minIdle :连接池中最少空闲连接数,默认为0
- maxWait:当连接池资源耗尽时,调用者最大阻塞的时间,超出将抛出异常。单位:毫秒数。默认为-1,表示永不超时
- minEvictableIdleTimeMillis :连接空闲的最小时间,达到此值后空闲连接将会被移除,负值(-1)表示不移除
- softMinEvictableIdleTimeMillis :连接空闲的最小时间,达到此值后空闲连接将会被移除,负值(-1)表示不移除
- numTestsPerEvictionRun : 对于“空闲连接”检测线程而言,每次检测的连接资源的个数。默认为3.
- testOnBorrow:向调用者输出”连接”对象时,是否进行有效性检测,如果无效则从连接池中移除,并尝试继续获取连接。默认为false
- testOnReturn :向连接池归还连接时。是否检测连接对象的有效性。默认false
- testWhileIdle :向调用者输出”连接”对象时,是否检测它的空闲超时,如果连接空闲超时,将会被移除,默认为false
- timeBetweenEvictionRunsMillis : “空闲连接”检测线程检测的周期,单位:毫秒数。如果为负值,表示不运行“检测线程”。默认为-1。
- whenExhaustedAction :当“连接池”中active数量达到阈值时,即“连接”资源耗尽时,连接池需要采取的手段: 0→抛出异常,1→阻塞,直到有可用连接资源(和maxWait参数有关,如果maxWait是正数,那么会等待maxWait的毫秒的时间,超时抛出异常,如果maxWait为负值,会永久等待),2→强制创建新的连接资源。默认为1
- lifo :pool可以被配置成LIFO队列(last -in-first-out)或FIFO(first-in-first-out)队列,来指定空闲对象被使用的次序,默认为true
五、示例
示例:sftp连接池
1、Maven依赖
<dependencies> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.14</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> <version>2.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.5.RELEASE</version> </dependency> </dependencies>
2、SftpClientObject(对象池内对象)
package com.chenly.demo.pool; import com.chenly.demo.config.SftpProperties; import com.jcraft.jsch.*; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Properties; /** * @author: chenly * @date: 2022-11-01 11:02 * @description: * @version: 1.0 */ @Component @Data @Slf4j public class SftpClientObject { private ChannelSftp sftp; public SftpClientObject(SftpProperties sftpProperties) throws Exception{ connect(sftpProperties); } /** * 连接登陆远程服务器 */ public ChannelSftp connect(SftpProperties sftpProperties) throws Exception { JSch jSch = new JSch(); 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; } /** * 服务配置 * @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.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.pool; import com.chenly.demo.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.pool; import com.chenly.demo.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.controller; import com.chenly.demo.config.SftpProperties; import com.chenly.demo.pool.SftpClientObject; import com.chenly.demo.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=8 sftp.pool.maxActive=16 sftp.pool.maxWait=-1 sftp.pool.testOnBorrow=true sftp.pool.testOnReturn=true sftp.pool.timeBetweenEvictionRuns=300000
参考地址:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!