HikariPool原始碼(三)資源池動態伸縮

Java極客發表於2020-04-05

HikariPool原始碼(三)資源池動態伸縮

Java極客  |  作者  /  鏗然一葉
這是Java極客的第 54 篇原創文章

1.資源池的動態伸縮

1.為了提升資源池的效能,需要設定最小閒置資源數量,在資源池初始化時完成初始化;而當使用的資源超過最小閒置資源數,消費者釋放回池中超過一定時間後要收縮到最小閒置資源數。

2.為了避免無限申請資源導致超出負載,需要設定最大資源數,池中資源不能超出最大資源數。

2.動態伸縮相關類結構

HikariPool原始碼(三)資源池動態伸縮

職責說明:

職責
HouseKeeper 實現了Runnable介面,負責池資源的動態伸縮。
ScheduledExecutorService 負責排程HouseKeeper。
ScheduledFuture 由ScheduledExecutorService排程返回,可以終止HouseKeeper的執行。
HikariPool 負責在構造器中初始化和呼叫以上類。

3.原始碼

3.1.HikariPool

      // 初始化ScheduledExecutorService
      this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
      
      // 排程HouseKeeper, 延遲並週期性排程
      this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
複製程式碼

3.2.HouseKeeper

   private final class HouseKeeper implements Runnable
   {
      private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs);

      @Override
      public void run()
      {
         try {
            // refresh values in case they changed via MBean
            connectionTimeout = config.getConnectionTimeout();
            validationTimeout = config.getValidationTimeout();
            leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
            catalog = (config.getCatalog() != null && !config.getCatalog().equals(catalog)) ? config.getCatalog() : catalog;

            final long idleTimeout = config.getIdleTimeout();
            final long now = currentTime();

            // 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;
            }
            else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) {
               // No point evicting for forward clock motion, this merely accelerates connection retirement anyway
               logger.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));
            }

            previous = now;

            String afterPrefix = "Pool ";
            if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {
               logPoolState("Before cleanup ");
               afterPrefix = "After cleanup  ";
               
               // 獲取未使用的資源
               final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
               // 未使用資源大於配置的最小閒置資源則關閉多餘資源
               int toRemove = notInUse.size() - config.getMinimumIdle();
               for (PoolEntry entry : notInUse) {
                  if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
                     closeConnection(entry, "(connection has passed idleTimeout)");
                     toRemove--;
                  }
               }
            }

            logPoolState(afterPrefix);
            
            // 除了動態減少資源,這裡動態擴容資源
            fillPool(); // Try to maintain minimum connections
         }
         catch (Exception e) {
            logger.error("Unexpected exception in housekeeping task", e);
         }
      }
   }
複製程式碼

動態擴容資源:

//HikariPool.java
   private synchronized void fillPool()
   {
      final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
                                   - addConnectionQueue.size();
      for (int i = 0; i < connectionsToAdd; i++) {
         addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
      }
   }
複製程式碼

3.3.ConcurrentBag

在ConcurrentBag中釋放資源時之會修改資源的狀態,不會去改變資源池可用資源數量。

   public void requite(final T bagEntry)
   {
      // 這裡只是修改資源狀態,並不減少資源池中可用資源數量。
      bagEntry.setState(STATE_NOT_IN_USE);

      for (int i = 0; waiters.get() > 0; i++) {
         if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
            return;
         }
         else if ((i & 0xff) == 0xff) { // 0xff 是255, 每隔256進去一次
            parkNanos(MICROSECONDS.toNanos(10));
         }
         else {
            yield();
         }
      }

      final List<Object> threadLocalList = threadList.get();
      if (threadLocalList.size() < 50) {
         threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
      }
   }
複製程式碼

4.總結

  1. 資源池在初始化時初始化最小資源數。
  2. 資源的動態伸縮可以通過JUC工具ScheduledExecutorService排程執行緒完成,而不需要額外使用第三方定時器。
  3. 消費者釋放資源時並不會立即減少資源池可用資源數量,因為很可能其他的消費者又會申請資源,為了避免減少無謂的建立資源操作,釋放的資源應該在超過一定時間後才真正關閉。

end.


<--感謝三連擊,左邊點贊和關注。

HikariPool原始碼(三)資源池動態伸縮


相關閱讀:
HikariPool原始碼(一)初識
HikariPool原始碼(二)設計思想借鑑


Java極客站點: javageektour.com/

相關文章