Commons-pool2 应用与实战


把创建耗时的对象提前创建好,放在池中管理,需要时直接取,用完归还,可以做对象复用,避免用时创建慢且提高系统的效率。 比如数据库连接、网络连接、大对象创建等都可以池化处理。

image-20210811195620000

commons-pool的优点

对象复用,减少创建开销。对于数据库的连接池可以减少创建连接带来的开销,连接用完归还可以给其他线程复用。

三个核心对象

GenericObjectPool

连接池对象,继承自BaseGenericObjectPool 用双端阻塞队列LinkedBlockingDeque保存空闲链接,用ConcurrentHashMap保存所有的链接。

构造方法

1
2
3
public GenericKeyedObjectPool(KeyedPooledObjectFactory<K, T> factory, GenericKeyedObjectPoolConfig<T> config) {
  //...
}

GenericObjectPoolConfig和PooledObjectFactory是创建对象池所必须的两个对象,稍后详细说明。

重要方法

  • borrowObject() 从连接池中取对象。

  • returnObject() 归还对象。

PooledObjectFactory

生产对象的工厂,有如下一些方法:

  • makeObject() 生产一个对象
    • 连接池初始化,驱逐过期连接后小于最小连接数,所有连接被占用且总连接数小于最大连接时调用。
  • destroyObject() 销毁一个对象
  • validateObject() 校验一个对象
  • activateObject() 重新激活一个对象
  • passivateObject() 钝化一个对象

没有特别要求可用PooledObjectFactory 的子类 BasePooledObjectFactory作为生产对象的工厂更简洁。

  • create() 生产对象的逻辑

  • wrap() 将对象包装成 PooledObject,必须支持多线程。

GenericObjectPoolConfig

连接池配置对象,继承自BaseObjectPoolConfig 。

常用参数:

maxTotal:对象池中管理的最多对象个数。默认值是8。 maxIdle:对象池中最大的空闲对象个数。默认值是8。 minIdle:对象池中最小的空闲对象个数。默认值是0。

实例代码

创建一个简单的连接对象,模拟数据库连接对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
   /**
     * 连接对象
     */
    @Getter
    @Setter
    @ToString
    static class Connection {
        private Integer id;

        public Connection(Integer id) {
            this.id = id;
        }
    }

生产连接对象的工厂,需要继承 BasePooledObjectFactory 类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 /**
     * 对象生产工厂
     */
    static class ConnectionObjectFactory extends BasePooledObjectFactory<Connection> {

        private AtomicInteger atomicInteger = new AtomicInteger(1);

        @Override
        public Connection create() throws Exception {
            // 模拟连接对象的创建过程
            return new Connection(atomicInteger.getAndIncrement());
        }

        @Override
        public PooledObject<Connection> wrap(Connection connection) {
            // 将连接包装成PooledObject对象,要线程安全
            return new DefaultPooledObject<>(connection);
        }
    }

自定义配置对象,继承GenericObjectPoolConfig,如不需要特殊配置可直接用GenericObjectPoolConfig。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    /**
     * 连接池配置信息
     */
    static class ConnectionPoolConfig extends GenericObjectPoolConfig {
        public ConnectionPoolConfig() {
            setTestWhileIdle(true);
            setMinEvictableIdleTimeMillis(60000);
            setTimeBetweenEvictionRunsMillis(30000);
            setNumTestsPerEvictionRun(-1);
        }
    }

使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    public static void main(String[] args) throws Exception {

        // 实例化对象生产工厂
        ConnectionObjectFactory factory = new ConnectionObjectFactory();
        // 实例化连接池配置信息
        ConnectionPoolConfig config = new ConnectionPoolConfig();
        config.setMaxTotal(5);
        config.setMaxWaitMillis(1000);
        // 实例化连接池
        GenericObjectPool<Connection> pool = new GenericObjectPool<>(factory, config);

        for (int i = 0; i < 10; i++) {
            // 获取连接
            Connection connection = pool.borrowObject();
            System.out.println(connection);
            // 归还连接
            //  pool.returnObject(connection);
        }

    }

先注释归还对象的方法 pool.returnObject(connection),执行结果如下,只能取到5个连接对象,因为设置的最大连接数是5。

image-20210729144629504

在打开 pool.returnObject(connection) 代码,每次获取的第一个连接,说明归还了对象可以接着用,实现连接对象复用。

image-20210729144822189

Redis客户端jedis的的连接池典型应用

JedisPoolConfig

配置类源码, 只修改了默认的几个参数,其余继承GenericObjectPoolConfig

1
2
3
4
5
6
7
8
9
public class JedisPoolConfig extends GenericObjectPoolConfig {
  public JedisPoolConfig() {
    // defaults to make your life with connection pool easier :)
    setTestWhileIdle(true);
    setMinEvictableIdleTimeMillis(60000);
    setTimeBetweenEvictionRunsMillis(30000);
    setNumTestsPerEvictionRun(-1);
  }
}

JedisFactory

生成jedis对象的工厂,继承commons-pool2的 PooledObjectFactory,重点实现了makeObject()生产jedis对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class JedisFactory implements PooledObjectFactory<Jedis> {
    @Override
    public PooledObject<Jedis> makeObject() throws Exception {
      final HostAndPort hostAndPort = this.hostAndPort.get();
      final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout,
          soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);

      try {
        jedis.connect();
        if (null != this.password) {
          jedis.auth(this.password);
        }
        if (database != 0) {
          jedis.select(database);
        }
        if (clientName != null) {
          jedis.clientSetname(clientName);
        }
      } catch (JedisException je) {
        jedis.close();
        throw je;
      }

      return new DefaultPooledObject<Jedis>(jedis);

    }
}

JedisPool

实例化jedis连接池,初始化连接对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class JedisPool extends Pool<Jedis> {
   protected GenericObjectPool<T> internalPool;
  // 实例化连接池 参数省略...
  public JedisPool(hostname,port,...){
    this.internalPool = new GenericObjectPool<Jedis>(new JedisFactory(host,
          Protocol.DEFAULT_PORT, Protocol.DEFAULT_TIMEOUT, Protocol.DEFAULT_TIMEOUT, null,
          Protocol.DEFAULT_DATABASE, null, false, null, null, null), new GenericObjectPoolConfig());
    }
  
  @Override
  public Jedis getResource() {
    //获取jedis对象
  }
  @Override
  public void returnResource(final Jedis resource) {
    //归还jedis对象
  }
}

最终调用GenericObjectPool作为对象池管理jedis对象,传入JedisFactory工厂和默认的配置对象GenericObjectPoolConfig,配置类可以使用redis自定义的JedisPoolConfig对象。

Jedis客户端通过依赖commons-pool2实现连接池,使得Jedis本身只关注自身和Redis服务的交互上,不需要关系连接怎么创建、销毁等。

0%