предисловие
HikariCP уже используется многими компаниями, и HikariCP также стал пулом соединений SpringBoot по умолчанию.Наряду со SpringBoot и микросервисами HikariCP, несомненно, станет широко популярным.
Позвольте Чену проанализировать, почему Spring Boot предпочитает HikariCP с точки зрения исходного кода.Каталог статей выглядит следующим образом:
Ноль, диаграммы классов и блок-схемы
Прежде чем начать, давайте разберемся в процессе взаимодействия между HikariCP для получения класса времени соединения, чтобы облегчить чтение подробного процесса ниже.
Межклассовые взаимодействия при получении соединения:
Во-первых, основной процесс 1: получить процесс подключения
Запись, когда HikariCP получает соединение,HikariDataSource
внутреннийgetConnection
метод, теперь давайте посмотрим на конкретный процесс этого метода:
Выше показана блок-схема, когда HikariCP получает соединение.Из рисунка 1 видно, что каждыйdatasource
объект будет содержатьHikariPool
Объект, обозначенный как пул, инициализированный пул объектов источника данных пуст, поэтому в первый разgetConnection
будет осуществляться, когда实例化pool
свойства (см.主流程1
), при инициализации необходимо преобразовать данные в текущем источнике данныхconfig属性
Пройдено в прошлом для инициализации пула, окончательная отметкаsealed
, а затем вызов в соответствии с объектом пулаgetConnection
метод (см.流程1.1
), объект подключения возвращается после успешного получения.
Во-вторых, основной процесс 2: Инициализация объекта пула
Этот процесс используется для初始化整个连接池
, этот процесс инициализирует все свойства в пуле соединений.Основные процессы были указаны на рисунке выше, кратко изложены:
- использовать
config
Инициализируйте различные свойства пула соединений и сгенерируйте生产物理连接
источник данныхDriverDataSource
- Инициализировать базовый класс, в котором хранится объект соединения.
connectionBag
- Инициализировать объект типа пула потоков с отложенной задачей
houseKeepingExecutorService
, для последующего выполнения некоторых задач задержки/времени (таких как задачи задержки проверки утечки соединения см.流程2.2
а также主流程4
,КромеmaxLifeTime
После активного перезапуска и закрытия соединения это также выполняется объектом.Об этом процессе см.主流程3
) - Подогрейте пул соединений, HikariCP
checkFailFast
Инициализируйте объект соединения в пуле и поместите его в пул.Конечно, запуск процесса должен быть гарантирован.initializationTimeout > 0
time (по умолчанию 1), это свойство конфигурации представляет время, оставшееся для операции прогрева (по умолчанию 1 не повторяет попытку при сбое прогрева). иDruid
пройти черезinitialSize
Разница в контроле количества предварительно подготовленных объектов соединения заключается в том, что HikariCP предварительно нагревает только один объект соединения в пуле. - Инициализировать объект пула потоков
addConnectionExecutor
, для последующего расширения объекта подключения - Инициализировать объект пула потоков
closeConnectionExecutor
, используется для закрытия некоторых объектов подключения, как запустить задачу закрытия? может относиться к流程1.1.2
3. Процесс 1.1: Получить объект подключения через HikariPool
Как видно из исходной структурной схемы, каждыйHikariPool
поддерживать одинConcurrentBag
Объект, используемый для хранения объектов подключения, как видно из рисунка выше, на самом делеHikariPool
изgetConnection
изConcurrentBag
установить соединение (позвонитьborrow
метод получения, соответствующийConnectionBag主流程
), проверьте этот блок на длинных соединениях, как было сказано ранееDruid
Отличие, длинная проверка соединения здесь не помечена как "已丢弃
", пока расстояние от последнего использования больше, чем500ms
Проверяется каждый раз, когда он вынимается (500 мс — значение по умолчанию, может быть настроеноcom.zaxxer.hikari.aliveBypassWindowMs
системные параметры для управления), эмммм, то естьHikariCP
Проверки живучести для длинных соединений выполняются часто, но их производительность параллелизма все же лучше, чемDruid
, что указывает на то, что частые длительные проверки соединения не являются ключом к производительности пула соединений.
Колонка Spring Boot автора и колонка Mybatis завершены, обратите внимание на общедоступную учетную запись [Code Ape Technology Column] ключевые слова ответа
Spring Boot 进阶
,Mybatis 进阶
Получать.
Это на самом деле из-за HikariCP无锁
Понимание, в случае высокого параллелизма нагрузка на ЦП не так высока, как разница в производительности параллелизма, вызванная другими пулами соединений.Конкретный метод HikariCP будет обсуждаться позже, даже если онDruid
,существует获取连接
,生成连接
,归还连接
все время锁控制
, поскольку на основе анализа предыдущей статьиDruid
Статья может знать, что,Druid
Ресурсы пула соединений совместно используются несколькими потоками, и неизбежно возникнет конкуренция блокировок.Конкуренция блокировок означает, что изменения состояния потока будут частыми, а частые изменения состояния потока будут означать частое переключение контекста ЦП.
назад流程1.1
, если полученное соединение пустое, то сразу будет сообщено об ошибке, если оно не пустое, то будет проведена соответствующая проверка, если проверка пройдена, оно будет запаковано какConnectionProxy
Объект возвращается деловой стороне, если не передан, то будет вызванcloseConnection
метод закрывает соединение (соответствует流程1.1.2
, процесс запуститсяConcurrentBag
изremove
метод отбрасывает соединение и передает фактическое соединение драйвераcloseConnectionExecutor
Пул потоков, асинхронно закрывает соединение с драйвером).
4. Процесс 1.1.1: Оценка соединения
предпринять вышеперечисленное流程1.1
В процессе суждения давайте посмотрим, как делается суждение, в первую очередь, метод проверки (обратите внимание, что этот метод принимает этоconnection
объект неpoolEntry
, ноpoolEntry
Объект подключения фактического драйвера удерживался), когда я представил Druid раньше, я знал, что Druid основан на том, существует ли он в драйвере.ping方法
Чтобы определить, включать ли пинг, чтобы определить, живо ли соединение, но когда речь идет о HikariCP, это более просто и грубо, только исходя из того, настроено ли оноconnectionTestQuery
Проверяем включен ли пинг:
this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
Так что, если общий драйвер не особенно низкой версии,Не рекомендуется настраивать это, иначе пойдетcreateStatement+excute
образом, по сравнению сping
Очевидно, что простая отправка данных пульса менее эффективна.
Кроме того, он будет установлен снова через объект подключения драйвера, когда он придет.networkTimeout
значение, чтобы оно сталоvalidationTimeout
, указывающий период тайм-аута проверки, зачем вам здесь сбрасывать это свойство? Потому что при использовании метода ping для проверки нет возможности передать аналогичныйstatement
это можетsetQueryTimeout
, поэтому его можно контролировать только по тайм-ауту сетевой связи, которым можно управлять с помощьюjdbc
параметры подключенияsocketTimeout
контролировать:
jdbc:mysql://127.0.0.1:3306/xxx?socketTimeout=250
Это значение в конечном итоге будет присвоено HikariCP.networkTimeout
поле, поэтому последний шаг использует это поле для восстановления атрибута тайм-аута подключения драйвера, здесь, зачем восстанавливать его снова в конце? Это легко понять, т. к. проверка завершена, а объект связи еще жив, егоnetworkTimeout
Значение по-прежнему равноvalidationTimeout
(Неожиданно), очевидно, перед тем, как взять его в пользование, необходимо восстановить значение стоимости, то есть значение в HikariCPnetworkTimeout
Атрибуты.
V. Процесс 1.1.2: Закрытие объекта подключения
Этот процесс просто流程1.1.1
Процесс, который активно закрывает мертвое соединение, не прошедшее проверку вConnectionBag
внутри移除
, а затем передать фактическое физическое подключение к пулу потоков для асинхронного выполнения.主流程2
Пул потоков инициализируется, когда пул инициализируется вcloseConnectionExecutor
, а затем запустите фактическую операцию закрытия соединения в асинхронной задаче. Поскольку активное закрытие соединения эквивалентно тому, что на одно соединение меньше, это также вызовет расширение пула соединений (см.主流程5
) работать.
6. Процесс 2.1: настройки мониторинга HikariCP
В отличие от Druid, у которого так много индикаторов мониторинга, HikariCP предоставит нам несколько индикаторов, которые нас очень беспокоят, например, количество простаивающих подключений в текущем пуле подключений, общее количество подключений, как долго соединение используется для возврат, сколько времени требуется для создания физического соединения и т. д., мониторинг пула соединений HikariCP. Давайте подробно разберем его в этом разделе, сначала найдите следующее HikariCPmetrics
Папка, здесь находятся некоторые стандартные интерфейсы мониторинга реализации и т.д., а также некоторые готовые реализации (например, HikariCP поставляется сprometheus
,micrometer
,dropwizard
поддержка, я не знаю много о последних двух,prometheus
в дальнейшем именуемый непосредственно普罗米修斯
):
Далее давайте сосредоточимся на определении интерфейса:
//这个接口的实现主要负责收集一些动作的耗时
public interface IMetricsTracker extends AutoCloseable
{
//这个方法触发点在创建实际的物理连接时(主流程3),用于记录一个实际的物理连接创建所耗费的时间
default void recordConnectionCreatedMillis(long connectionCreatedMillis) {}
//这个方法触发点在getConnection时(主流程1),用于记录获取一个连接时实际的耗时
default void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) {}
//这个方法触发点在回收连接时(主流程6),用于记录一个连接从被获取到被回收时所消耗的时间
default void recordConnectionUsageMillis(final long elapsedBorrowedMillis) {}
//这个方法触发点也在getConnection时(主流程1),用于记录获取连接超时的次数,每发生一次获取连接超时,就会触发一次该方法的调用
default void recordConnectionTimeout() {}
@Override
default void close() {}
}
После того, как триггерные точки будут четко поняты, давайте посмотримMetricsTrackerFactory
Определение интерфейса:
//用于创建IMetricsTracker实例,并且按需记录PoolStats对象里的属性(这个对象里的属性就是类似连接池当前闲置连接数之类的线程池状态类指标)
public interface MetricsTrackerFactory
{
//返回一个IMetricsTracker对象,并且把PoolStats传了过去
IMetricsTracker create(String poolName, PoolStats poolStats);
}
См. комментарии для вышеприведенного использования интерфейса, для новогоPoolStats
class, давайте посмотрим, что он делает:
public abstract class PoolStats {
private final AtomicLong reloadAt; //触发下次刷新的时间(时间戳)
private final long timeoutMs; //刷新下面的各项属性值的频率,默认1s,无法改变
// 总连接数
protected volatile int totalConnections;
// 闲置连接数
protected volatile int idleConnections;
// 活动连接数
protected volatile int activeConnections;
// 由于无法获取到可用连接而阻塞的业务线程数
protected volatile int pendingThreads;
// 最大连接数
protected volatile int maxConnections;
// 最小连接数
protected volatile int minConnections;
public PoolStats(final long timeoutMs) {
this.timeoutMs = timeoutMs;
this.reloadAt = new AtomicLong();
}
//这里以获取最大连接数为例,其他的跟这个差不多
public int getMaxConnections() {
if (shouldLoad()) { //是否应该刷新
update(); //刷新属性值,注意这个update的实现在HikariPool里,因为这些属性值的直接或间接来源都是HikariPool
}
return maxConnections;
}
protected abstract void update(); //实现在↑上面已经说了
private boolean shouldLoad() { //按照更新频率来决定是否刷新属性值
for (; ; ) {
final long now = currentTime();
final long reloadTime = reloadAt.get();
if (reloadTime > now) {
return false;
} else if (reloadAt.compareAndSet(reloadTime, plusMillis(now, timeoutMs))) {
return true;
}
}
}
}
На самом деле, именно здесь эти свойства получают и запускают обновление, так где же этот объект генерируется и перебрасывается вMetricsTrackerFactory
изcreate
метод? Вот суть этого раздела:主流程2
Вот процесс настройки монитора, чтобы увидеть, что там происходит:
//监控器设置方法(此方法在HikariPool中,metricsTracker属性就是HikariPool用来触发IMetricsTracker里方法调用的)
public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) {
if (metricsTrackerFactory != null) {
//MetricsTrackerDelegate是包装类,是HikariPool的一个静态内部类,是实际持有IMetricsTracker对象的类,也是实际触发IMetricsTracker里方法调用的类
//这里首先会触发MetricsTrackerFactory类的create方法拿到IMetricsTracker对象,然后利用getPoolStats初始化PoolStat对象,然后也一并传给MetricsTrackerFactory
this.metricsTracker = new MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(), getPoolStats()));
} else {
//不启用监控,直接等于一个没有实现方法的空类
this.metricsTracker = new NopMetricsTrackerDelegate();
}
}
private PoolStats getPoolStats() {
//初始化PoolStats对象,并且规定1s触发一次属性值刷新的update方法
return new PoolStats(SECONDS.toMillis(1)) {
@Override
protected void update() {
//实现了PoolStat的update方法,刷新各个属性的值
this.pendingThreads = HikariPool.this.getThreadsAwaitingConnection();
this.idleConnections = HikariPool.this.getIdleConnections();
this.totalConnections = HikariPool.this.getTotalConnections();
this.activeConnections = HikariPool.this.getActiveConnections();
this.maxConnections = config.getMaximumPoolSize();
this.minConnections = config.getMinimumIdle();
}
};
}
На этом этапе зарегистрирован монитор HikariCP, поэтому, если вы хотите добиться собственного монитора для получения вышеуказанных показателей, вам необходимо выполнить следующие шаги:
- Создайте новую реализацию класса
IMetricsTracker
интерфейс, мы записываем класс здесь какIMetricsTrackerImpl
- Создайте новую реализацию класса
MetricsTrackerFactory
интерфейс, мы записываем класс здесь какMetricsTrackerFactoryImpl
, и поместите вышеIMetricsTrackerImpl
В своемcreate方法
создание экземпляров - будет
MetricsTrackerFactoryImpl
Вызов HikariPool после создания экземпляраsetMetricsTrackerFactory
Метод зарегистрирован в пуле соединений Hikari.
не упомянутый вышеPoolStats
Как следить за недвижимостью вcreate方法
Он называется один раз, и он ушел.create方法
только что получилиPoolStats
Экземпляр объекта, если не обработан, то с окончанием вызова create экземпляр будет потерян для модуля мониторинга, так что если вы хотите получить его здесьPoolStats
свойств, вам необходимо открыть守护线程
, пусть держитPoolStats
экземпляр объекта, и периодически получать значения его внутренних свойств, а затемpush
для систем мониторинга, если прометей и т.д.pull方式
Система мониторинга для получения данных мониторинга может следовать внедрению собственного мониторинга HikariCP Prometheus и настраиватьCollector
объект для полученияPoolStats
Пример, чтобы Prometheus мог его регулярно дергать, например HikariCP, определяемый самой системой мониторинга PrometheusMetricsTrackerFactory
реализация (соответствует图2
внутреннийPrometheusMetricsTrackerFactory
своего рода):
@Override
public IMetricsTracker create(String poolName, PoolStats poolStats) {
getCollector().add(poolName, poolStats); //将接收到的PoolStats对象直接交给Collector,这样普罗米修斯服务端每触发一次采集接口的调用,PoolStats都会跟着执行一遍内部属性获取流程
return new PrometheusMetricsTracker(poolName, this.collectorRegistry); //返回IMetricsTracker接口的实现类
}
//自定义的Collector
private HikariCPCollector getCollector() {
if (collector == null) {
//注册到普罗米修斯收集中心
collector = new HikariCPCollector().register(this.collectorRegistry);
}
return collector;
Благодаря приведенному выше объяснению вы можете узнать, как настроить свой собственный монитор в HikariCP и в чем разница по сравнению с мониторингом Druid.
Много раз в нашей работе нам приходилось его настраивать, хотя наша компания также использует мониторинг Prometheus, потому что наименования индикаторов мониторинга в родном сборщике Prometheus HikariCP не согласованы.不符合我司的规范
, так просто自定义
Если у вас есть аналогичная проблема, вы можете попробовать.
🍁 В этом разделе нет рисунка, чистый код, потому что рисунок не очень хорошо объясняет эту часть, и эта часть имеет мало отношения к общему процессу пула соединений, в лучшем случае некоторые свойства самого пула соединений получен, и триггер в пуле соединений. Этот момент также проясняется в комментариях к приведенному выше фрагменту кода, и его можно лучше понять, просмотрев определение кода.
7. Процесс 2.2: Обнаружение утечки соединения и сигнализация
Этот раздел соответствует主流程2
внутренний子流程2.2
, при инициализации объекта пула инициализировать вызываемыйleakTaskFactory
свойство, этот раздел увидит, для чего оно используется.
7.1: Что он делает?
Соединение не использовалось дольше, чемleakDetectionThreshold
(Настраиваемый, по умолчанию 0). Если он не возвращен, он вызовет предупреждение об утечке соединения, чтобы уведомить бизнес-сторону о проблеме утечки соединения.
7.2: Детали процесса
СобственностьProxyLeakTaskFactory
тип объекта, и он также содержитhouseKeepingExecutorService
Этот объект пула потоков, используемый для производстваProxyLeakTask
объект, затем используйте вышеуказанноеhouseKeepingExecutorService
Задержка запускаrun
метод. Триггерная точка этого процесса находится в вышеуказанном流程1.1
наконец упаковано вProxyConnection
Для шага объекта давайте посмотрим на конкретную блок-схему:
каждый раз流程1.1
генерируется тамProxyConnection
объект, вышеописанный процесс будет запущен, как видно из блок-схемы,ProxyConnection
объект держитPoolEntry
иProxyLeakTask
объект, в котором инициализированProxyLeakTask
объект используетсяleakTaskFactory
объект, через которыйschedule
метод может бытьProxyLeakTask
, и передать его экземпляр вProxyConnection
Выполните назначение инициализации (ps: известное из рисункаProxyConnection
При запуске события повторного использования задача проверки утечки будет активно отменена, что такжеProxyConnection
нужно держатьProxyLeakTask
причина объекта).
Как видно из приведенной выше блок-схемы, только тогда, когдаleakDetectionThreshold
Когда он не равен 0, будет сгенерирована фактическая задача задержки.ProxyLeakTask
объект, в противном случае возвращает бессмысленный пустой объект. Итак, чтобы включить проверку утечки соединения, сначалаleakDetectionThreshold
В настройках конфигурации это свойство указывает на то, что по истечении этого времени одолженное соединение не было возвращено, и будет срабатывать сигнализация об утечке соединения.
ProxyConnection
зачем держатьProxyLeakTask
объект, потому что он может прослушивать, запускает ли соединение операцию возврата, и если да, то вызываетcancel
Метод отменяет отложенную задачу, чтобы предотвратить ложные тревоги.
Из этого процесса мы можем узнать, что, как и Druid, HikariCP также имеет проверку на утечку объекта соединения.По сравнению с активным восстановлением соединений в Druid, HikariCP проще в реализации.Он печатает журнал предупреждений только при срабатывании и не принимает конкретных обязательные восстановительные мероприятия.
Как и Druid, этот процесс также закрыт по умолчанию, потому что сторонние фреймворки, как правило, используются в реальной разработке, а сам фреймворк обеспечит своевременное закрытие соединений, чтобы предотвратить утечку объектов соединения. нужно это или нет.если его нужно открыть, как установитьleakDetectionThreshold
Размер тоже нужно учитывать. Колонка Spring Boot автора и колонка Mybatis завершены, обратите внимание на общедоступную учетную запись [Code Ape Technology Column] ключевые слова ответаSpring Boot 进阶
,Mybatis 进阶
Получать.
Восемь, основной процесс 3: создание объектов подключения
В этом разделе поговорим о主流程2
внутреннийcreateEntry
метод, этот метод использует PoolBaseDriverDataSource
объект генерирует фактический объект подключения (если вы забылиDriverDatasource
Где инициализируется, можно посмотреть主流程2
внутриPoolBase
изinitializeDataSource
метод), затем используйтеPoolEntry
Классы упакованы какPoolEntry
Object, теперь давайте посмотрим на основные свойства этого класса-обертки:
final class PoolEntry implements IConcurrentBagEntry {
private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
//通过cas来修改state属性
private static final AtomicIntegerFieldUpdater stateUpdater;
Connection connection; //实际的物理连接对象
long lastAccessed; //触发回收时刷新该时间,表示“最近一次使用时间”
long lastBorrowed; //getConnection里borrow成功后刷新该时间,表示“最近一次借出的时间”
@SuppressWarnings("FieldCanBeLocal")
private volatile int state = 0; //连接状态,枚举值:IN_USE(使用中)、NOT_IN_USE(闲置中)、REMOVED(已移除)、RESERVED(标记为保留中)
private volatile boolean evict; //是否被标记为废弃,很多地方用到(比如流程1.1靠这个判断连接是否已被废弃,再比如主流程4里时钟回拨时触发的直接废弃逻辑)
private volatile ScheduledFuture<?> endOfLife; //用于在超过连接生命周期(maxLifeTime)时废弃连接的延时任务,这里poolEntry要持有该对象,主要是因为在对象主动被关闭时(意味着不需要在超过maxLifeTime时主动失效了),需要cancel掉该任务
private final FastList openStatements; //当前该连接对象上生成的所有的statement对象,用于在回收连接时主动关闭这些对象,防止存在漏关的statement
private final HikariPool hikariPool; //持有pool对象
private final boolean isReadOnly; //是否为只读
private final boolean isAutoCommit; //是否存在事务
}
выше это всеPoolEntry
Все свойства в объекте, поговорим об этом здесьendOfLife
объект, который является эксплойтомhouseKeepingExecutorService
Задача задержки, выполняемая этим объектом пула потоков, эта задача задержки обычно выполняется после создания объекта соединения.maxLifeTime
Левый и правый триггер времени, см. нижеcreateEntry
Код:
private PoolEntry createPoolEntry() {
final PoolEntry poolEntry = newPoolEntry(); //生成实际的连接对象
final long maxLifetime = config.getMaxLifetime(); //拿到配置好的maxLifetime
if (maxLifetime > 0) { //<=0的时候不启用主动过期策略
// 计算需要减去的随机数
// 源注释:variance up to 2.5% of the maxlifetime
final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong(maxLifetime / 40) : 0;
final long lifetime = maxLifetime - variance; //生成实际的延时时间
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
() -> { //实际的延时任务,这里直接触发softEvictConnection,而softEvictConnection内则会标记该连接对象为废弃状态,然后尝试修改其状态为STATE_RESERVED,若成功,则触发closeConnection(对应流程1.1.2)
if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
addBagItem(connectionBag.getWaitingThreadCount()); //回收完毕后,连接池内少了一个连接,就会尝试新增一个连接对象
}
},
lifetime, MILLISECONDS)); //给endOfLife赋值,并且提交延时任务,lifetime后触发
}
return poolEntry;
}
//触发新增连接任务
public void addBagItem(final int waiting) {
//前排提示:addConnectionQueue和addConnectionExecutor的关系和初始化参考主流程2
//当添加连接的队列里已提交的任务超过那些因为获取不到连接而发生阻塞的线程个数时,就进行提交连接新增连接的任务
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
//提交任务给addConnectionExecutor这个线程池,PoolEntryCreator是一个实现了Callable接口的类,下面将通过流程图的方式介绍该类的call方法
addConnectionExecutor.submit(poolEntryCreator);
}
}
Благодаря описанному выше процессу мы можем узнать, что HikariCP обычно проходитcreateEntry
метод для добавления соединения в пул, каждое соединение упаковано как объект PoolEntry, когда объект создается, задача задержки будет отправлена в то же время, чтобы закрыть и отказаться от соединения, на этот раз мы настраиваемmaxLifeTime
, чтобы гарантировать, что он не выйдет из строя одновременно, HikariCP также будет использоватьmaxLifeTime
Вычтите случайное число в качестве окончательного времени задержки отложенной задачи, а затем, когда заброшенная задача будет запущена, она также запустится.addBagItem
, выполнить задачу добавления соединения (поскольку соединение потеряно, его нужно добавить в пул), и задача будет передана主流程2
определено вaddConnectionExecutor
Выполняется пул потоков, теперь давайте посмотрим на процесс задачи добавления объектов соединения асинхронно:
Этот процесс заключается в добавлении соединений в пул соединений сcreateEntry
Объединены потому, что эти два процесса тесно связаны между собой, кроме того,主流程5
(fillPool
, расширение пула соединений) также вызовет эту задачу.
Девять, основной процесс 4: сокращение пула соединений
HikariCP будет следоватьminIdle
Регулярно очищайте соединения, которые долгое время простаивали, эта временная задача находится в主流程2
Он включается при инициализации объекта пула соединений.Как и в предыдущем процессе, он также используетhouseKeepingExecutorService
Этот объект пула потоков является исполнителем запланированной задачи.
посмотри主流程2
Вот как включить задачу:
//housekeepingPeriodMs的默认值是30s,所以定时任务的间隔为30s
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
Таким образом, в этом разделе в основном говорится оHouseKeeper
Этот класс, этот класс реализуетRunnable
интерфейс, логика рециркуляции в основном в егоrun
способ, посмотримrun
Логическая схема метода:
Вышеупомянутый процессHouseKeeper
Конкретная вещь, сделанная в методе run , потому что обратный вызов системного времени вызовет ошибки, когда запланированная задача повторно использует некоторые соединения, поэтому есть следующие суждения:
//now就是当前系统时间,previous就是上次触发该任务时的时间,housekeepingPeriodMs就是隔多久触发该任务一次
//也就是说plusMillis(previous, housekeepingPeriodMs)表示当前时间
//如果系统时间没被回拨,那么plusMillis(now, 128)一定是大于当前时间的,如果被系统时间被回拨
//回拨的时间超过128ms,那么下面的判断就成立,否则永远不会成立
if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs))
Это мера, сделанная hikariCP, когда системные часы набираются обратно.Как вы можете видеть из блок-схемы, он напрямую извлекает все объекты подключения в пуле и помечает их как заброшенные один за другим и пытается изменить статус значение дляSTATE_RESERVED
(Эти состояния будут объяснены позже, поэтому я не буду вдаваться в них здесь). Если системные часы не изменились (в большинстве случаев сработает логика этого блока), как показано на рисунке, все текущие пулы будут переведены в состояние простоя (STATE_NOT_IN_USE
) соединение, затем вычисляем диапазон, который необходимо проверить, а затем циклически модифицируем состояние соединения:
//拿到所有处于闲置状态的连接
final List notInUse = connectionBag.values(STATE_NOT_IN_USE);
//计算出需要被检查闲置时间的数量,简单来说,池内需要保证最小minIdle个连接活着,所以需要计算出超出这个范围的闲置对象进行检查
int toRemove = notInUse.size() - config.getMinIdle();
for (PoolEntry entry : notInUse) {
//在检查范围内,且闲置时间超出idleTimeout,然后尝试将连接对象状态由STATE_NOT_IN_USE变为STATE_RESERVED成功
if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
closeConnection(entry, "(connection has passed idleTimeout)"); //满足上述条件,进行连接关闭
toRemove--;
}
}
fillPool(); //因为可能回收了一些连接,所以要再次触发连接池扩充流程检查下是否需要新增连接。
Приведенный выше код представляет собой соответствующую логику процесса на блок-схеме, когда системное время не возвращается обратно. Процесс находится вidleTimeout
больше 0 (по умолчанию равно 0) иminIdle
меньше, чемmaxPoolSize
Он будет включен только тогда, когда он включен.По умолчанию он не включен.Если вам нужно включить его, вы можете настроить его в соответствии с условиями.
Десять, основной процесс 5: расширить пул соединений
Этот процесс в основном зависит от HikariPool вfillPool
метод, этот метод уже появился во многих из вышеперечисленных процессов.Его функция состоит в том, чтобы инициировать операцию расширения количества соединений, когда триггерное соединение отбрасывается, а соединения пула соединений недостаточно.Это очень простой процесс. структура кода стала понятнее, а в исходный код внесены небольшие изменения):
// PoolEntryCreator关于call方法的实现流程在主流程3里已经看过了,但是这里却有俩PoolEntryCreator对象,
// 这是个较细节的地方,用于打日志用,不再说这部分,为了便于理解,只需要知道这俩对象执行的是同一块call方法即可
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null);
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
private synchronized void fillPool() {
// 这个判断就是根据当前池子里相关数据,推算出需要扩充的连接数,
// 判断方式就是利用最大连接数跟当前连接总数的差值,与最小连接数与当前池内闲置的连接数的差值,取其最小的那一个得到
int needAdd = Math.min(maxPoolSize - connectionBag.size(),
minIdle - connectionBag.getCount(STATE_NOT_IN_USE));
//减去当前排队的任务,就是最终需要新增的连接数
final int connectionsToAdd = needAdd - addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
//一般循环的最后一次会命中postFillPoolEntryCreator任务,其实就是在最后一次会打印一次日志而已(可以忽略该干扰逻辑)
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
}
}
Из этого процесса можно узнать, что окончательная задача этого нового соединения также передаетсяaddConnectionExecutor
Пул потоков обрабатывается, а также предмет задачиPoolEntryCreator
, этот процесс может относиться к主流程3.
потомneedAdd
Расчет:
Math.min(最大连接数 - 池内当前连接总数, 最小连接数 - 池内闲置的连接数)
Исходя из этого, можно гарантировать, что количество соединений в пуле никогда не превыситmaxPoolSize
, и никогда не будет нижеminIdle
. Когда соединение плотное, можно гарантировать, что каждый триггер будетminIdle
Расширение количества. Итак, если вmaxPoolSize
иminIdle
Если настроенное значение такое же, при жестком соединении в пуле расширения не произойдет.
11. Основной процесс 6: повторное использование соединения
Как было сказано в начале, окончательный объект реального физического соединения будет упакован какPoolEntry
объект, хранящийся вConcurrentBag
, а затем при получении объект PoolEntry будет снова обернут какProxyConnection
Если объект открыт для пользователя, то инициация восстановления соединения фактически вызывает метод закрытия в ProxyConnection:
public final void close() throws SQLException {
// 原注释:Closing statements can cause connection eviction, so this must run before the conditional below
closeStatements(); //此连接对象在业务方使用过程中产生的所有statement对象,进行统一close,防止漏close的情况
if (delegate != ClosedConnection.CLOSED_CONNECTION) {
leakTask.cancel(); //取消连接泄漏检查任务,参考流程2.2
try {
if (isCommitStateDirty && !isAutoCommit) { //在存在执行语句后并且还打开了事务,调用close时需要主动回滚事务
delegate.rollback(); //回滚
lastAccess = currentTime(); //刷新"最后一次使用时间"
}
} finally {
delegate = ClosedConnection.CLOSED_CONNECTION;
poolEntry.recycle(lastAccess); //触发回收
}
}
}
Это метод закрытия в ProxyConnection. Вы можете видеть, что он в конечном итоге вызовет метод recycle PoolEntry для повторного использования. Кроме того, в это время также обновляется время последнего использования объекта соединения. Это время является очень важным атрибутом. Вы можете использовать для определения времени простоя объекта подключения, посмотрите PoolEntryrecycle
метод:
void recycle(final long lastAccessed) {
if (connection != null) {
this.lastAccessed = lastAccessed; //刷新最后使用时间
hikariPool.recycle(this); //触发HikariPool的回收方法,把自己传过去
}
}
Как я уже говорил, каждый объект PoolEntry содержит объект HikariPool, что удобно для запуска некоторых операций пула соединений.Как видно из приведенного выше кода, в конечном итоге будет запущен метод recycle в HikariPool.Давайте посмотрим на метод переработки HikariPool:
void recycle(final PoolEntry poolEntry) {
metricsTracker.recordConnectionUsage(poolEntry); //监控指标相关,忽略
connectionBag.requite(poolEntry); //最终触发connectionBag的requite方法归还连接,该流程参考ConnectionBag主流程里的requite方法部分
}
Выше приведена логика части подключения и рециркуляции, по сравнению с другими процессами она относительно проста.
12. Основной процесс ConcurrentBag
Этот класс используется для хранения объекта подключения конечного типа PoolEntry и обеспечивает базовую функцию добавления, удаления и проверки.Действия и действия повторного использования фактически управляют классом ConcurrentBag и сортируют триггерные точки всех вышеперечисленных процессов:
- Основной процесс 2: Инициализация при инициализации HikariPool
ConcurrentBag(构造方法)
, через разминкуcreateEntry
Получите объект подключения и вызовитеConcurrentBag.add
Добавьте подключение к ConcurrentBag. - Процесс 1.1: когда соединение установлено через HikariPool, путем вызова
ConcurrentBag.borrow
Получить объект подключения. - Основной процесс 6: пройти
ConcurrentBag.requite
Вернуть соединение. - Процесс 1.1.2: при срабатывании закрытия соединения оно пройдет
ConcurrentBag.remove
Чтобы удалить объект соединения, предыдущий процесс показывает, что триггерной точкой для закрытия соединения является: соединение превышает максимальный жизненный цикл maxLifeTime и активно отбрасывается, проверка работоспособности не может активно отбрасывать, а пул соединений сокращается. - Основной процесс 3: при асинхронном добавлении соединения путем вызова
ConcurrentBag.add
Добавление соединения в ConcurrentBag. Предыдущий процесс показывает, что триггерной точкой для добавления соединения является: после того, как соединение превышает максимальный жизненный цикл maxLifeTime и активно отбрасывает соединение, пул соединений расширяется. - Основной процесс 4: задача сокращения пула соединений путем вызова
ConcurrentBag.values
Отфильтруйте объекты соединения, которыми необходимо управлять, а затем передайтеConcurrentBag.reserve
Завершите изменение состояния объекта подключения, а затем передайте流程1.1.2
Триггеры закрывают и удаляют соединения.
Перебирая триггерные точки, можно узнать, что основным методом в структуре является триггерная точка, помеченная как标签色
часть, а затем взглянем на базовое определение и основные методы класса:
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable {
private final CopyOnWriteArrayList<T> sharedList; //最终存放PoolEntry对象的地方,它是一个CopyOnWriteArrayList
private final boolean weakThreadLocals; //默认false,为true时可以让一个连接对象在下方threadList里的list内处于弱引用状态,防止内存泄漏(参见备注1)
private final ThreadLocal<List<Object>> threadList; //线程级的缓存,从sharedList拿到的连接对象,会被缓存进当前线程内,borrow时会先从缓存中拿,从而达到池内无锁实现
private final IBagStateListener listener; //内部接口,HikariPool实现了该接口,主要用于ConcurrentBag主动通知HikariPool触发添加连接对象的异步操作(也就是主流程3里的addConnectionExecutor所触发的流程)
private final AtomicInteger waiters; //当前因为获取不到连接而发生阻塞的业务线程数,这个在之前的流程里也出现过,比如主流程3里addBagItem就会根据该指标进行判断是否需要新增连接
private volatile boolean closed; //标记当前ConcurrentBag是否已被关闭
private final SynchronousQueue<T> handoffQueue; //这是个即产即销的队列,用于在连接不够用时,及时获取到add方法里新创建的连接对象,详情可以参考下面borrow和add的代码
//内部接口,PoolEntry类实现了该接口
public interface IConcurrentBagEntry {
//连接对象的状态,前面的流程很多地方都已经涉及到了,比如主流程4的缩容
int STATE_NOT_IN_USE = 0; //闲置
int STATE_IN_USE = 1; //使用中
int STATE_REMOVED = -1; //已废弃
int STATE_RESERVED = -2; //标记保留,介于闲置和废弃之间的中间状态,主要由缩容那里触发修改
boolean compareAndSet(int expectState, int newState); //尝试利用cas修改连接对象的状态值
void setState(int newState); //设置状态值
int getState(); //获取状态值
}
//参考上面listener属性的解释
public interface IBagStateListener {
void addBagItem(int waiting);
}
//获取连接方法
public T borrow(long timeout, final TimeUnit timeUnit) {
// 省略...
}
//回收连接方法
public void requite(final T bagEntry) {
//省略...
}
//添加连接方法
public void add(final T bagEntry) {
//省略...
}
//移除连接方法
public boolean remove(final T bagEntry) {
//省略...
}
//根据连接状态值获取当前池子内所有符合条件的连接集合
public List values(final int state) {
//省略...
}
//获取当前池子内所有的连接
public List values() {
//省略...
}
//利用cas把传入的连接对象的state从 STATE_NOT_IN_USE 变为 STATE_RESERVED
public boolean reserve(final T bagEntry) {
//省略...
}
//获取当前池子内符合传入状态值的连接数量
public int getCount(final int state) {
//省略...
}
}
Из этой базовой структуры видно, как HikariCP оптимизирует реализацию традиционных пулов соединений.По сравнению с Druid, HikariCP предпочитает реализацию без блокировок, чтобы максимально избежать конкуренции блокировок.
12.1: одолжить
Этот метод используется для получения доступного объекта подключения, точка срабатывания流程1.1
, HikariPool использует этот метод для получения соединения, давайте посмотрим, что делает этот метод:
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
// 源注释:Try the thread-local list first
final List<Object> list = threadList.get(); //首先从当前线程的缓存里拿到之前被缓存进来的连接对象集合
for (int i = list.size() - 1; i >= 0; i--) {
final Object entry = list.remove(i); //先移除,回收方法那里会再次add进来
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry; //默认不启用弱引用
// 获取到对象后,通过cas尝试把其状态从STATE_NOT_IN_USE 变为 STATE_IN_USE,注意,这里如果其他线程也在使用这个连接对象,
// 并且成功修改属性,那么当前线程的cas会失败,那么就会继续循环尝试获取下一个连接对象
if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry; //cas设置成功后,表示当前线程绕过其他线程干扰,成功获取到该连接对象,直接返回
}
}
// 源注释:Otherwise, scan the shared list ... then poll the handoff queue
final int waiting = waiters.incrementAndGet(); //如果缓存内找不到一个可用的连接对象,则认为需要“回源”,waiters+1
try {
for (T bagEntry : sharedList) {
//循环sharedList,尝试把连接状态值从STATE_NOT_IN_USE 变为 STATE_IN_USE
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// 源注释:If we may have stolen another waiter's connection, request another bag add.
if (waiting > 1) { //阻塞线程数大于1时,需要触发HikariPool的addBagItem方法来进行添加连接入池,这个方法的实现参考主流程3
listener.addBagItem(waiting - 1);
}
return bagEntry; //cas设置成功,跟上面的逻辑一样,表示当前线程绕过其他线程干扰,成功获取到该连接对象,直接返回
}
}
//走到这里说明不光线程缓存里的列表竞争不到连接对象,连sharedList里也找不到可用的连接,这时则认为需要通知HikariPool,该触发添加连接操作了
listener.addBagItem(waiting);
timeout = timeUnit.toNanos(timeout); //这时候开始利用timeout控制获取时间
do {
final long start = currentTime();
//尝试从handoffQueue队列里获取最新被加进来的连接对象(一般新入的连接对象除了加进sharedList之外,还会被offer进该队列)
final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
//如果超出指定时间后仍然没有获取到可用的连接对象,或者获取到对象后通过cas设置成功,这两种情况都不需要重试,直接返回对象
if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
//走到这里说明从队列内获取到了连接对象,但是cas设置失败,说明又该对象又被其他线程率先拿去用了,若时间还够,则再次尝试获取
timeout -= elapsedNanos(start); //timeout减去消耗的时间,表示下次循环可用时间
} while (timeout > 10_000); //剩余时间大于10s时才继续进行,一般情况下,这个循环只会走一次,因为timeout很少会配的比10s还大
return null; //超时,仍然返回null
} finally {
waiters.decrementAndGet(); //这一步出去后,HikariPool收到borrow的结果,算是走出阻塞,所以waiters-1
}
}
При внимательном рассмотрении заметок процесс можно условно разделить на три основных этапа:
- Получить соединение из кеша потоков
- больше не доступен из
sharedList
получить - Если вы не можете его получить, активируйте логику добавления соединения и попытайтесь получить вновь сгенерированный объект соединения из очереди.
12.2: добавить
Этот процесс добавляет объект подключения в корзину, обычно主流程3
внутреннийaddBagItem
метод переданaddConnectionExecutor
Асинхронная задача запускает операцию добавления.Основной процесс этого метода выглядит следующим образом:
public void add(final T bagEntry) {
sharedList.add(bagEntry); //直接加到sharedList里去
// 源注释:spin until a thread takes it or none are waiting
// 参考borrow流程,当存在线程等待获取可用连接,并且当前新入的这个连接状态仍然是闲置状态,且队列里无消费者等待获取时,发起一次线程调度
while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) { //注意这里会offer一个连接对象入队列
yield();
}
}
комбинироватьborrow
Для понимания, здесь объект подключения будет добавлен в очередь при наличии ожидающего потока, что может сделатьborrow
Легче опросить этот объект соединения, где происходит ожидание.
12.3: требуется
Этот процесс перезапустит соединение, точка срабатывания этого метода主流程6
, конкретный код выглядит следующим образом:
public void requite(final T bagEntry) {
bagEntry.setState(STATE_NOT_IN_USE); //回收意味着使用完毕,更改state为STATE_NOT_IN_USE状态
for (int i = 0; waiters.get() > 0; i++) { //如果存在等待线程的话,尝试传给队列,让borrow获取
if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
else if ((i & 0xff) == 0xff) {
parkNanos(MICROSECONDS.toNanos(10));
}
else {
yield();
}
}
final List<Object> threadLocalList = threadList.get();
if (threadLocalList.size() < 50) { //线程内连接集合的缓存最多50个,这里回收连接时会再次加进当前线程的缓存里,方便下次borrow获取
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry); //默认不启用弱引用,若启用的话,则缓存集合里的连接对象没有内存泄露的风险
}
}
12.4: удалить
Это отвечает за удаление объекта подключения из пула, точка срабатывания流程1.1.2
, код показан ниже:
public boolean remove(final T bagEntry) {
// 下面两个cas操作,都是从其他状态变为移除状态,任意一个成功,都不会走到下面的warn log
if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);
return false;
}
// 直接从sharedList移除掉
final boolean removed = sharedList.remove(bagEntry);
if (!removed && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);
}
return removed;
}
Здесь следует отметить, что при удалении удаляется толькоsharedList
Объект в кеше, соответствующий объект в наборе, кэшированном в каждом потоке, не был удален. Будет ли в это время снова извлекаться соединение из кеша? Да только не вернется, а прямоremove
Его нет, посмотри внимательноborrow
Когда код обнаружит, что состояние не простое, онremove
Если вы уроните его, вы не сможете его вынуть, и, естественно, это не вызовет метод утилизации.
12.5: значения
Для этого метода существует перегруженный метод, который используется для возврата коллекции объектов соединения в текущем пуле.主流程4
, код показан ниже:
public List values(final int state) {
//过滤出来符合状态值的对象集合逆序后返回出去
final List list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
Collections.reverse(list);
return list;
}
public List values() {
//返回全部连接对象(注意下方clone为浅拷贝)
return (List) sharedList.clone();
}
12.6: резерв
Этот метод просто преобразует значение состояния объекта соединения с помощьюSTATE_NOT_IN_USE
превратиться вSTATE_RESERVED
, точка срабатывания по-прежнему主流程4
, используемый при сжатии, код выглядит следующим образом:
public boolean reserve(final T bagEntry){
return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED);
}
12.7: получитьСчетчик
Этот метод используется для возврата общего количества подключений в пуле, соответствующих определенному значению статуса.主流程5
, используемый для получения общего количества простаивающих соединений при расширении пула соединений, код выглядит следующим образом:
public int getCount(final int state){
int count = 0;
for (IConcurrentBagEntry e : sharedList) {
if (e.getState() == state) {
count++;
}
}
return count;
}
ВышеупомянутоеConcurrentBag
Основной метод и основной поток обработки объектов соединения.
13. Резюме
На данный момент, в основном, связь от производства к приобретению, переработке и утилизации — это то, как в HikariCP управляется весь жизненный цикл.По сравнению с предыдущей реализацией Druid, это сильно отличается, в основном HikariCP.无锁
Получите соединение, которое не описано в этой статьеFastList
Описание, так как эта структура редко используется с точки зрения управления соединениями, используйтеFastList
Место в основном генерируется в объекте подключения к хранилищуstatement对象
И используется для хранения объектов подключения, кэшированных в потоке; столбец Spring Boot автора и столбец Mybatis были завершены, обратите внимание на общедоступный номер [Code Ape Technology Column] ключевые слова ответаSpring Boot 进阶
,Mybatis 进阶
Получать.
Кроме того, HikariCP также используетjavassist
Генерируется во время технической компиляцииProxyConnection
Инструкций по теме здесь нет, в интернете много статей по оптимизации HikariCP, большинство из которых упоминаются.字节码优化
,fastList
,concurrentBag
Реализация этой статьи в основном за счет глубокого анализаHikariPool
иConcurrentBag
Реализация HikariCP, чтобы проиллюстрировать, какие разные операции выполняет HikariCP по сравнению с Druid.