關於HikariCP為什么不推薦配置connectionTestQuery


《在SpringBoot 1.5.3上使用gradle引入hikariCP》一文里寫了怎么在SpringBoot1.5上用Hikari連接池,轉眼來到SpringBoot2.1,不用麻煩了,因為這貨已經干掉了tomcat-jdbcPool成為被springboot認可的默認的連接池了!

但之前有個沒理解的事情,前文中properties文件有個注釋掉的配置:

#配置了這個query則每次拿連接的時候用這個query測試,否則就使用java.sql.Connection的isValid測試  推薦jdbc4不配置。
#spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL   

HikariCP推薦jdbc4不配置connectionTestQuery

官網上是這么講的:
If your driver supports JDBC4 we strongly recommend not setting this property. This is for "legacy" drivers that do not support the JDBC4 Connection.isValid() API. This is the query that will be executed just before a connection is given to you from the pool to validate that the connection to the database is still alive. Again, try running the pool without this property, HikariCP will log an error if your driver is not JDBC4 compliant to let you know. Default: none
“如果用JDBC4驅動的話強烈建議不要配connectionTestQuery,這玩意是給舊版的不支持Connection.isValid() API的驅動准備的。在每次從池里拿連接的時候用這個connectionTestQuery配置的SQL語句執行一下,來檢驗連接的有效性。再一次聲明,試着不用這個配置項來運行hikari,如果你的驅動不支持JDBC4我們會log一個error來提示你的。”

hikari jdbc4連接檢測方式分析

之前用的tomcat jdbc pool或者更早之前過的dbcp,在連接有效性保障上都是有兩種策略,testOnBorrow以及testWhileIdle,test的手段就是用sql比如select 1 from dual來執行一下。當時開發人員的共識是為了保證性能,不配置testOnBorrow,而采用testWhileIdle這種方式,由連接池內部的一個異步線程去定時的調用上面的test sql來排除失效的連接。(技術是相通的,apache HttpClient的pool也是類似思路來排查失效連接)
所以Hikari既然官方號稱不要使用這個test sql,而且在testOnBorrow時檢測連接有效性,那么我們可以推斷兩個事情:

  1. 連接池也就是客戶端側應用層不來做檢測連接這個事情了,這個事交給底下一層的網絡通信層也就是驅動程序來做。
  2. 假如能夠保證檢測這個動作足夠高效而輕量,那么使用testOnBorrow也未嘗不可,且相比異步定時檢測的方式時效性上更有保證。
    下面我們帶着上面的兩個猜測來走讀一下相關的Hikari源代碼。

源碼分析

com.zaxxer.hikari.pool.PoolBase

   boolean isConnectionAlive(final Connection connection)
   {
    
            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds); //如果支持JDBC4就用connection.isValid接口

   }

接下來,來到了驅動程序:
com.mysql.cj.jdbc.ConnectionImpl

@Override
    public boolean isValid(int timeout) throws SQLException {
           synchronized (getConnectionMutex()) {
            if (isClosed()) {
                return false;
            }

            try {
                try {
                    pingInternal(false, timeout * 1000); //ping一下檢測連接有效性
                } catch (Throwable t) {
                    try {
                        abortInternal();
                    } catch (Throwable ignoreThrown) {
                        // we're dead now anyway
                    }

                    return false;
                }

            } catch (Throwable t) {
                return false;
            }
            return true;
        }
    }
  
    @Override
    public void pingInternal(boolean checkForClosedConnection, int timeoutMillis) throws SQLException {
        this.session.ping(checkForClosedConnection, timeoutMillis);
    }

看起來是pingInternal(false, timeout * 1000)這一步,進行了網絡通信去檢測了連接。進去看看:
com.mysql.cj.NativeSession

    public void ping(boolean checkForClosedConnection, int timeoutMillis) {
        if (checkForClosedConnection) {
            checkClosed();
        }

        long pingMillisLifetime = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingSecondsLifetime).getValue();
        int pingMaxOperations = getPropertySet().getIntegerProperty(PropertyKey.selfDestructOnPingMaxOperations).getValue();

        if ((pingMillisLifetime > 0 && (System.currentTimeMillis() - this.connectionCreationTimeMillis) > pingMillisLifetime)
                || (pingMaxOperations > 0 && pingMaxOperations <= getCommandCount())) {

            invokeNormalCloseListeners();

            throw ExceptionFactory.createException(Messages.getString("Connection.exceededConnectionLifetime"),
                    MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE, 0, false, null, this.exceptionInterceptor);
        }
        //前邊一堆判斷大概是否超過了最大ping的次數pingMaxOperations,以及連接是否超過了LifeTime
        //如果都ok,那么就執行下面的command, sendCommand(this.commandBuilder.buildComPing(null)是構造了一個message
        sendCommand(this.commandBuilder.buildComPing(null), false, timeoutMillis); // it isn't safe to use a shared packet here 
    }

    public final NativePacketPayload sendCommand(NativePacketPayload queryPacket, boolean skipCheck, int timeoutMillis) {
        return (NativePacketPayload) this.protocol.sendCommand(queryPacket, skipCheck, timeoutMillis);
    }

看起來是向數據庫發了一個message來測試連接。

public NativePacketPayload buildComPing(NativePacketPayload sharedPacket) {
        NativePacketPayload packet = sharedPacket != null ? sharedPacket : new NativePacketPayload(1);
        packet.writeInteger(IntegerDataType.INT1, NativeConstants.COM_PING);
        return packet;
    }
static final int COM_PING = 14;

哦,原來是向數據庫發了一個網絡包,這個包里邊是一個整數14
我們來看下是怎么發的,從sendCommand方法開始,經過一層一層的send方法,最后找到如下方法:

package com.mysql.cj.protocol.a;

import java.io.BufferedOutputStream;
import java.io.IOException;

import com.mysql.cj.protocol.MessageSender;

public class SimplePacketSender implements MessageSender<NativePacketPayload> {
    public void send(byte[] packet, int packetLen, byte packetSequence) throws IOException {
        PacketSplitter packetSplitter = new PacketSplitter(packetLen);
        while (packetSplitter.nextPacket()) {
            this.outputStream.write(NativeUtils.encodeMysqlThreeByteInteger(packetSplitter.getPacketLen()));
            this.outputStream.write(packetSequence++);
            this.outputStream.write(packet, packetSplitter.getOffset(), packetSplitter.getPacketLen());
        }
        this.outputStream.flush();
    }
}

搞清楚了,原來是用java bio的輸出流向數據庫服務器寫的message。

至此,我們搞清楚了Hikari的連接檢測機制:
如果驅動程序支持JDBC4的標准,那么就放棄使用執行SQL語句這種重量級的需要數據庫引擎參與的檢測方式,用這種方式無論是testOnBorrow策略還是異步定時檢測策略,都是要用到數據庫引擎的算力的。
將連接檢測的工作交給JDBC4驅動程序的connectin.isAlive接口,這個接口十分輕量級,采用的是向數據庫發送一個心跳包的方式來試探連接是否存活,發送的時候使用的java bio包。(這也說明了我們的JDBC是同步阻塞的模型)
最后,由於檢測方式十分輕量,HikariCP索性采用了時效性更好的連接檢測策略testOnBorrow

參考:

https://github.com/brettwooldridge/HikariCP/ HikariCP官網
https://www.cnblogs.com/littleatp/p/14088588.html 《MySQL 連接為什么掛死了》 作者真大神,贊一個,關於mysql連接串的配置最好要加上socket超時和connect超時的提醒很及時!jdbc:mysql://10.0.71.13:33052/appdb?socketTimeout=60000&connectTimeout=30000&serverTimezone=UTC
https://blog.csdn.net/weixin_30899789/article/details/113214524 也是說的這個事,數據庫地址配置上要加上socketTimeout和connectTimeout


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM