转载

HikariPool源码(四)资源状态

了解池资源的状态,以及状态如何变迁,用于池化资源设计参考。

2.HikariPool资源核心类回顾

HikariPool资源相关的类如下:

HikariPool源码(四)资源状态

类说明:

职责
HikariPool 资源池,客户端资源操作的入口。
ConcurrentBag 通用的并发包工具。
CopyOnWriteArrayList 一个列表,用于存储资源,也就是PoolEntry。其特点是读不加锁,写操作加锁,提高并发性能。
PoolEntry 资源封装类,封装了Connection,资源的状态记录在这个类上。
Connection 真正要适用的资源,数据库连接。

3.资源状态

在PoolEntry上实际有两个状态或者说属性,分别是:

3.1 state

state用于声明资源是否可用,其状态变化如下:

HikariPool源码(四)资源状态
状态 何时变化
Not In Use 1.连接池初始化时
2.释放资源回池时
3.出借资源,资源不够时
In Use 出借可用资源时。
Reserved 1.资源超过最大生命周期时
2.资源池shutdown时
3.Detect retrograde time时
4.调用者主动调用时。
Removed 1.连接池初始化,动态伸缩时(降时)
2.获取连接时
3.发生特定异常时。

3.1.1 Not In Use

这个状态说明资源没有被使用,等待分配,资源刚初始化好或者释放回资源池中的资源将变成这个状态。

1.连接池初始化时,连接池中的资源初始化并达到最小资源数,这些初始化的资源就是这个状态。

2.资源使用完释放会池中时,资源状态会从In Use变成Not In Use。 3.出借资源,资源不足时,此时如果池中的资源数没有达到最大资源数,则会创建新的资源,新资源状态也是这个状态。

3.1.2 In Use

唯一的场景就是出借资源后,资源从Not In Use变成In Use。

3.1.3 Reserved

1.资源超过最大生命周期时是指:每一个资源实例可以设置最大生命周期,如果超过最大生命周期还没有使用(Not In Use)则会调整为Reserved状态,这个状态下资源不能被使用。

2.资源池shoudown时也会先将资源的状态从Not In Use修改为Reserved状态,避免再被出借出去。

3.Detect retrograde time时,是一个很特殊的场景,这是检查时钟同步时是否回拨了,这个场景我们一般不会考虑到,参考代码如下:

//HouseKeeper.java
            // Detect retrograde time, allowing +128ms as per NTP spec.
            if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) {
               logger.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
                           poolName, elapsedDisplayString(previous, now));
               previous = now;
               softEvictConnections();
               return;
            }
复制代码

资源什么时候超过最大生命周期,是通过一个延迟的线程任务来执行,如果线程执行了,资源还没有被使用,说明超过了最大生命周期。

4.允许调用者主动调用方法来设置这个状态,HikariPool中没有直接调用点。代码如下:

//HikariDataSource.java
   /**
    * Evict a connection from the pool.  If the connection has already been closed (returned to the pool)
    * this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
    * currently in use.  If the connection has not been closed, the eviction is immediate.
    *
    * @param connection the connection to evict from the pool
    */
   public void evictConnection(Connection connection)
   {
      HikariPool p;
      if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
         p.evictConnection(connection);
      }
   }
复制代码

3.1.4 Removed

这个状态意味着资源从连接池中清除,数据库连接被真正关闭,资源被释放。

1.连接池初始化,动态伸缩时(降时),这是指资源数量超过最小连接数,并且部分连接的闲置时间也已经超过允许的闲置时间,那么就会释放这些连接,使得池中的资源数量降到最小连接数。

2.获取连接时,通常我们不可能在此时去释放连接,这里是因为当连接池shutdown时,有的连接可能被设置为evicted,这样的连接是不可用的,对这样的连接要释放掉,导致这个场景发生的原因这两个动作没有加锁,而不加锁的目的是为了提升性能,毕竟这种场景并不多见,大多数时候不会出现,也就在大多数情况下提升了性能。

3.发生特定异常时,这类异常都是直接导致数据库连接不可用的异常,因此会将数据库连接释放掉。

3.2 evict

资源的另外一个状态或者属性是evict标志,如果标志为true,就意味着资源不可用。其状态变化比较简单,默认为false。

HikariPool源码(四)资源状态

这个标记最主要的作用是在获取资源时,如果资源的evict标志为true,则这个资源不可用,会接着获取下一个资源。

资源变为true的时机可参考state变为removed的时机,可以理解为就是资源不可用,将要清理并释放掉的时候。

从上面两个状态的变化来看,似乎用removed状态可以替代evict为true,但是在HikariPool中并没有这么做,一方面可能是因为从业务上讲两者的业务含义不同,另外一方面evict还用在异常处理中,对于异常的处理这里不再深入展开,有兴趣可以看下源码。

4. 总结

HikariPool使用了资源状态来控制资源是否可用,而不是通过一个可用资源池和一个已用资源池来控制资源可用,这么做的好处有:

  1. 控制粒度更小,更精确,不需要在池上加锁,只要在具体资源上加锁,符合并发编程优化的减小锁粒度原则
  2. 扩展更容易,如果通过不同的池来控制,那么增加新的状态,会导致要增加新的池来记录这些资源。

HikariPool使用了state和evict来控制资源的使用,实际设计时是否需要如此,要结合实际情况来看:

  1. 从解耦的角度看,如果业务上是两个业务语义,并且不同语义有不同用途,那么就分开。
  2. 如果实际业务只需要一个state就够了,就没有必要一开始就拆分为两个,可遵循适用原则,等有需要时再扩展。

end.

<--感谢三连击,左边点赞和关注。

HikariPool源码(四)资源状态
原文  https://juejin.im/post/5e8dd39b5188257397286a37
正文到此结束
Loading...