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 }

 

posted @ 2020-06-28 20:48  鸡犬相闻  阅读(883)  评论(0编辑  收藏  举报