[Ngbatis源码学习]Ngbatis源码阅读之连接池的创建

发布时间 2023-12-06 23:31:50作者: knqiufan

Ngbatis源码阅读之连接池的创建

1. NebulaPool的创建

NgbatisBeanFactoryPostProcessor 这个类实现了 BeanFactoryPostProcessor 后置处理器,Ngbatis 连接池的创建就在 postProcessBeanFactory 中实现。关于 BeanFactoryPostProcessor 的有关内容可以参考上一篇文章。

首先来看 org.nebula.contrib.ngbatis.NgbatisBeanFactoryPostProcessor#postProcessBeanFactory 方法,代码如下:

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    NebulaPool nebulaPool = nebulaPool();
    // ... 省略部分代码 ...
  }

进入 org.nebula.contrib.ngbatis.NgbatisBeanFactoryPostProcessor#nebulaPool 方法:

  public NebulaPool nebulaPool() {
    NebulaPool pool = new NebulaPool();
    try {
        // 传入地址集合与线程池配置参数
      pool.init(
          nebulaJdbcProperties.getHostAddresses(),
          nebulaJdbcProperties.getPoolConfig()
      );
    } catch (UnknownHostException e) {
      throw new RuntimeException("Can not connect to Nebula Graph");
    }
    return pool;
  }

可以看到主要调用了 init 方法。init 方法传入了两个参数,一个是地址集合参数,一个是线程池配置参数。这两个参数都是通过 yml 配置文件来获取得到。进入 com.vesoft.nebula.client.graph.net.NebulaPool#init 方法:

    public boolean init(List<HostAddress> addresses, NebulaPoolConfig config) throws UnknownHostException, InvalidConfigException {
        // 检查是否已经初始化
        checkInit();
        // 设置是否初始化参数为true
        hasInit.set(true);
        // 检查配置参数是否正确
        checkConfig(config);
        // 等待时间
        this.waitTime = config.getWaitTime();
        // 支持配置多个NebulaGraph连接地址
        List<HostAddress> newAddrs = hostToIp(addresses);
        // 根据是否ssl获取负载均衡器(负载均衡策略:轮询)
        this.loadBalancer = config.isEnableSsl()
                ? new RoundRobinLoadBalancer(newAddrs, config.getTimeout(), config.getSslParam(),
                config.getMinClusterHealthRate())
                : new RoundRobinLoadBalancer(newAddrs, config.getTimeout(),
                config.getMinClusterHealthRate());
        // 根据负载均衡器、配置、 GenericObjectPool 对象池技术创建连接池
        ConnObjectPool objectPool = new ConnObjectPool(this.loadBalancer, config);
        this.objectPool = new GenericObjectPool<>(objectPool);
        // 配置相关配置
        GenericObjectPoolConfig objConfig = new GenericObjectPoolConfig();
        objConfig.setMinIdle(config.getMinConnSize());
        objConfig.setMaxIdle(config.getMaxConnSize());
        objConfig.setMaxTotal(config.getMaxConnSize());
        objConfig.setTestOnBorrow(true);
        objConfig.setTestOnReturn(true);
        objConfig.setTestOnCreate(true);
        objConfig.setTestWhileIdle(true);
        objConfig.setTimeBetweenEvictionRunsMillis(config.getIntervalIdle() <= 0
            ? BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS
            : config.getIntervalIdle());
        objConfig.setSoftMinEvictableIdleTimeMillis(config.getIdleTime() <= 0
            ? BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS
            : config.getIdleTime());
        this.objectPool.setConfig(objConfig);

        AbandonedConfig abandonedConfig = new AbandonedConfig();
        abandonedConfig.setRemoveAbandonedOnBorrow(true);
        this.objectPool.setAbandonedConfig(abandonedConfig);
        // 连接池初始化
        return objectPool.init();
    }

可以看到 NebulaGraph 的连接池其实就是基于 apache commons-pool2 对象池框架对连接进行了池化管理。

以下补充相关核心类 GenericObjectPool 和 GenericObjectPoolConfig 的介绍。

2. GenericObjectPool

Apache Commons Pool 是一个对象池的框架,它提供了一整套用于实现对象池化的API。它提供了三种对象池:GenericKeyedObjectPoolSoftReferenceObjectPoolGenericObjectPool,其中 GenericObjectPool 是最常用的对象池。

对象池的技术其实很常见,比如线程池、数据库连接池,都是针对对象创建代价高,比较消耗资源和性能这些问题得出的解决方案,即使用完对象后不立即释放资源,而是放到一个池子里存起来,下次就能直接从池子里拿出现成可用的对象。

对象池需要具备以下能力:

  • 首先得有个容器数据结构,能存放多个对象,也有数量上限
  • 维持一定数量的常驻对象,当数量与 QPS * RT 匹配的话,业务处理就能直接获取可用对象,不需要消耗对象创建的时间
  • 能应对突发的流量
  • 一定时间内没有获取成功就抛出异常,不会卡死业务现成
  • 具有活性检测机制,从容器中拿出来的对象是可用的

对象获取的核心流程是:

3. GenericObjectPoolConfig

GenericObjectPoolConfig 是在 GenericObjectPool 构造的时候用的,用来设置 pool 的一些属性。

代码如下:

public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {
  public static final int DEFAULT_MAX_TOTAL = 8;
  public static final int DEFAULT_MAX_IDLE = 8;
  public static final int DEFAULT_MIN_IDLE = 0;
  private int maxTotal = 8;
  private int maxIdle = 8;
  private int minIdle = 0;

  public GenericObjectPoolConfig() {
  }

  public GenericObjectPoolConfig<T> clone() {
    try {
      return (GenericObjectPoolConfig)super.clone();
    } catch (CloneNotSupportedException var2) {
      throw new AssertionError();
    }
  }
  // ... 省略部分代码 ...
}

可以看到在 GenericObjectPoolConfig 中主要配置了 maxTotal、maxIdle、minIdle:

  • maxTotal:对象池中允许的最大对象个数。

    在从对象池获取对象时,如果对象池中有Idle对象,则直接返回一个对象。否则,判断活跃对象是否已经达到maxTotal,是就会发生阻塞等待,直到有对象归还对象池;没有达到maxTotal,则就会创建一个新对象供使用。

  • maxIdle:对象池中最大空闲的对象个数。

    在归还对象时,对象会处于Idle状态,这时,对象池会判断Idle对象个数,如果已经达到maxIdle,则对象会被直接销毁,否则才会放回对象池

  • minIdle:对象池中最小空闲的对象个数, 默认为0。

    new一个对象池时,会初始创建minIdle个对象。当开启了回收机制后,如果对象空闲时间达到了设定值,则会被销毁、移出对象池。但对象池中至少会保留minIdle个对象。

其实还有其他很多属性,都在父类 BaseObjectPoolConfig 中,浅看一眼:

public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Cloneable {
  // ...
  private Duration maxWaitDuration;
  private Duration minEvictableIdleDuration;
  private Duration evictorShutdownTimeoutDuration;
  private Duration softMinEvictableIdleDuration;
  private int numTestsPerEvictionRun;
  private EvictionPolicy<T> evictionPolicy;
  private String evictionPolicyClassName;
  private boolean testOnCreate;
  private boolean testOnBorrow;
  private boolean testOnReturn;
  private boolean testWhileIdle;
  private Duration durationBetweenEvictionRuns;
  private boolean blockWhenExhausted;
  private boolean jmxEnabled;
  private String jmxNamePrefix;
  private String jmxNameBase;
  // ...
}