commons-pool实现FTP连接池
1. 待解决问题
问题1:有多个TXT文件,需要根据文件的类型发送给不同的FTP
问题2:MQ生成文件,需要根据文件的类型发送给不同的FTP
解决方法:使用连接池减少FTP连接次数
2. 调研commons-pool
2.1 简单介绍
本质上是"对象池",即通过一定的规则来维护对象集合的容器;commos-pool在很多场景中,用来实现"连接池"/"任务worker池"等,大家常用的dbcp数据库连接池,也是基于commons-pool实现
commons-pool实现思想非常简单,它主要的作用就是将"特定对象"池化,pool管理类严格按照"pool配置"对"特定对象"进行创建、还回、状态变更等操作。
- 将创建对象的方式,使用工厂模式;
- 通过"pool配置"来约束对象存取的时机
- 将对象列表保存在队列中
2.2 核心类
commons-pool提供了两种工厂模板:BasePooledObjectFactory、BaseKeyedPooledObjectFactory
BasePooledObjectFactory适用于池化一个对象,BaseKeyedPooledObjectFactory适用于池化一类对象需要使用key来区分
1 public interface PooledObjectFactory<T> { 2 //生成“特定对象” 3 PooledObject<T> makeObject() throws Exception; 4 5 //移除“特定对象” 6 void destroyObject(PooledObject<T> var1) throws Exception; 7 8 //校验“特定对象”状态 9 boolean validateObject(PooledObject<T> var1); 10 11 //激活“特定对象” 12 void activateObject(PooledObject<T> var1) throws Exception; 13 14 //钝化“特定对象” 15 void passivateObject(PooledObject<T> var1) throws Exception; 16 } 17 18 public interface KeyedPooledObjectFactory<K, V> { 19 //生成指定key的“特定对象” 20 PooledObject<V> makeObject(K var1) throws Exception; 21 22 //移除指定key的“特定对象” 23 void destroyObject(K var1, PooledObject<V> var2) throws Exception; 24 25 //校验指定key的“特定对象”状态 26 boolean validateObject(K var1, PooledObject<V> var2); 27 28 //激活指定key的“特定对象” 29 void activateObject(K var1, PooledObject<V> var2) throws Exception; 30 31 //钝化指定key的“特定对象” 32 void passivateObject(K var1, PooledObject<V> var2) throws Exception; 33 }
commons-pool对应两种工厂模板提供了两种pool模板:ObjectPool、KeyedObjectPool
1 public interface ObjectPool<T> extends Closeable { 2 // 创建对象并放进对象池 3 void addObject() throws Exception, IllegalStateException, UnsupportedOperationException; 4 5 // 批量创建对象并放进对象池 6 default void addObjects(int count) throws Exception { 7 for(int i = 0; i < count; ++i) { 8 this.addObject(); 9 } 10 11 } 12 13 // 从对象池借一个对象 14 T borrowObject() throws Exception, NoSuchElementException, IllegalStateException; 15 16 // 清空对象池 17 void clear() throws Exception, UnsupportedOperationException; 18 19 // 关闭对象池 20 void close(); 21 22 // 获取对象池中激活对象数量 23 int getNumActive(); 24 25 // 获取对象池中空闲对象数量 26 int getNumIdle(); 27 28 // 把对象置位失效 29 void invalidateObject(T var1) throws Exception; 30 31 // 归还对象 32 void returnObject(T var1) throws Exception; 33 }
KeyedObjectPool的模板和ObjectPool类似,只是入参多了一个key来筛选池化的对象,线程池管理类GenericObjectPool、GenericKeyedObjectPool是commons-pool提供的基础类,可以直接使用。
GenericObjectPool.Config 线程池的基本配置:
- maxActive: 链接池中最大连接数,默认为8.
- maxIdle: 链接池中最大空闲的连接数,默认为8.
- minIdle: 连接池中最少空闲的连接数,默认为0.
- maxWait: 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时.
- minEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除。
- softMinEvictableIdleTimeMillis: 连接空闲的最小时间,达到此值后空闲链接将会被移除,且保留“minIdle”个空闲连接数。默认为-1.
- numTestsPerEvictionRun: 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
- testOnBorrow: 向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值.
- testOnReturn: 向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值.
- testWhileIdle: 向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值.
- timeBetweenEvictionRunsMillis: “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
- whenExhaustedAction: 当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1:
-> 0 : 抛出异常,
-> 1 : 阻塞,直到有可用链接资源
-> 2 : 强制创建新的链接资源
3. 实现方案
1 @Component 2 public class FtpKeyedPoolableObjectFactory extends BaseKeyedPooledObjectFactory<String, FTPClient> { 3 4 @Override 5 public FTPClient create(String key) { 6 System.out.println(channel + "FTP连接开始创建===="); 7 FTPClient ftpClient; 8 switch (key) { 9 case "key1": 10 ftpClient = this.createFTPClient("",21,"",""); 11 break; 12 case "key2": 13 ftpClient = this.createFTPClient("",21,"",""); 14 break; 15 case "key3": 16 ftpClient = this.createFTPClient("",21,"",""); 17 break; 18 default: 19 ftpClient = new FTPClient(); 20 } 21 System.out.println(channel + "FTP连接创建完成===="); 22 return ftpClient; 23 } 24 25 @Override 26 public PooledObject<FTPClient> wrap(FTPClient ftpClient) { 27 return new DefaultPooledObject<>(ftpClient); 28 } 29 30 /** 31 * 销毁FtpClient对象 32 */ 33 @Override 34 public void destroyObject(String channel, PooledObject<FTPClient> ftpPooled) { 35 if (ftpPooled == null) { 36 return; 37 } 38 39 FTPClient ftpClient = ftpPooled.getObject(); 40 41 try { 42 if (ftpClient.isConnected()) { 43 ftpClient.logout(); 44 } 45 } catch (IOException ioe) { 46 Loggers.BIZ.error("关闭FTP登出异常", ioe); 47 } finally { 48 try { 49 ftpClient.disconnect(); 50 } catch (IOException ioe) { 51 Loggers.BIZ.error("关闭FTP连接失败", ioe); 52 } 53 } 54 } 55 56 /** 57 * 验证FtpClient对象 58 */ 59 @Override 60 public boolean validateObject(String channel, PooledObject<FTPClient> ftpPooled) { 61 try { 62 FTPClient ftpClient = ftpPooled.getObject(); 63 return ftpClient.sendNoOp(); 64 } catch (IOException ioe) { 65 Loggers.BIZ.error("查看FTP连接状态失败", ioe); 66 } 67 return false; 68 } 69 70 public FTPClient createFTPClient(String address, int port, String username, String password) { 71 FTPClient ftpClient = new FTPClient(); 72 try { 73 ftpClient.connect(address, port); 74 ftpClient.login(username, password); 75 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); 76 int reply = ftpClient.getReplyCode(); 77 if (!FTPReply.isPositiveCompletion(reply)) { 78 ftpClient.logout(); 79 Loggers.BIZ.warn("FTP服务器连接失败",null); 80 } 81 } catch (Exception e) { 82 Loggers.BIZ.error("FTP登录失败", e); 83 } 84 return ftpClient; 85 } 86 }
测试类:
1 @RunWith(SpringRunner.class) 2 @SpringBootTest 3 public class FtpKeyedObjectPoolTest { 4 5 @Test 6 public void test() throws Exception{ 7 CountDownLatch count = new CountDownLatch(2); 8 GenericKeyedObjectPool<String, FTPClient> ftpKeyedObjectPool = new GenericKeyedObjectPool<>(new FtpKeyedPoolableObjectFactory()); 9 //每次获取时判断状态是否失效 10 ftpKeyedObjectPool.setTestOnBorrow(true); 11 new Thread(new Runnable() { 12 @Override 13 public void run() { 14 for (int i = 0; i < 6;i++) { 15 String channel = "key"; 16 try { 17 FTPClient ftpClient = ftpKeyedObjectPool.borrowObject(channel); 18 System.out.println(channel + "-FTP状态:" + ftpClient.isConnected()); 19 ftpKeyedObjectPool.returnObject(channel, ftpClient); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 } 24 count.countDown(); 25 } 26 },"线程1").start(); 27 28 new Thread(new Runnable() { 29 @Override 30 public void run() { 31 for (int i = 0; i < 6;i++) { 32 String channel = "key"; 33 try { 34 FTPClient ftpClient = ftpKeyedObjectPool.borrowObject(channel); 35 System.out.println(channel + "-FTP状态:" + ftpClient.isConnected()); 36 ftpKeyedObjectPool.returnObject(channel, ftpClient); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 } 41 42 count.countDown(); 43 } 44 },"线程2").start(); 45 46 count.await(); 47 } 48 }