溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring Boot集成Druid出現(xiàn)異常報(bào)錯(cuò)的原因是什么

發(fā)布時(shí)間:2021-05-27 14:06:50 來(lái)源:億速云 閱讀:948 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹Spring Boot集成Druid出現(xiàn)異常報(bào)錯(cuò)的原因是什么,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

Spring Boot集成Druid異常

在Spring Boot集成Druid項(xiàng)目中,發(fā)現(xiàn)錯(cuò)誤日志中頻繁的出現(xiàn)如下錯(cuò)誤信息:

discard long time none received connection. , jdbcUrl : jdbc:mysql://******?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8, version : 1.2.3, lastPacketReceivedIdleMillis : 172675

經(jīng)過(guò)排查發(fā)現(xiàn)是Druid版本導(dǎo)致的異常,在1.2.2及以前版本并未出現(xiàn)如此異常。而在其以上版本均存在此問(wèn)題,下面就來(lái)分析一下異常原因及解決方案。

異常分析

首先上面的異常并不影響程序的正常運(yùn)行,但作為程序員看到程序中不停的出現(xiàn)異常還是難以忍受的。所以還是要刨根問(wèn)底的解決一下的。

跟蹤堆棧信息會(huì)發(fā)現(xiàn)對(duì)應(yīng)的異常是從com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal方法中拋出的,對(duì)應(yīng)的代碼如下:

if (valid && isMySql) { // unexcepted branch
    long lastPacketReceivedTimeMs = MySqlUtils.getLastPacketReceivedTimeMs(conn);
    if (lastPacketReceivedTimeMs > 0) {
        long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
        if (lastPacketReceivedTimeMs > 0 //
                && mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
            discardConnection(holder);
            String errorMsg = "discard long time none received connection. "
                    + ", jdbcUrl : " + jdbcUrl
                    + ", jdbcUrl : " + jdbcUrl
                    + ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
            LOG.error(errorMsg);
            return false;
        }
    }
}

上述代碼中,MySqlUtils.getLastPacketReceivedTimeMs(conn) 是獲取上一次使用的時(shí)間,mysqlIdleMillis 就是計(jì)算出來(lái)空閑的時(shí)間,timeBetweenEvictionRunsMillis 是常量60秒。如果連接空閑了60秒以上,那就discardConnection(holder) 丟棄這個(gè)舊連接并順帶打印了一個(gè)日志LOG.warn(errorMsg)。

原理追蹤

在上述代碼中,我們看到進(jìn)入該業(yè)務(wù)邏輯是有前提條件的,也就是valid和isMySql變量同時(shí)為true。isMySql為true是必須的,我們使用的本身就是Mysql數(shù)據(jù)庫(kù)。那么是否可以讓valid為false呢?這樣不就不會(huì)進(jìn)入該業(yè)務(wù)處理了嗎?

來(lái)看看valid的來(lái)源,還是在該方法的上面:

boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);

我們找到validConnectionChecker的Mysql實(shí)現(xiàn)子類(lèi)MySqlValidConnectionChecker,該類(lèi)中對(duì)isValidConnection的實(shí)現(xiàn)如下:

public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
    if (conn.isClosed()) {
        return false;
    }

    if (usePingMethod) {
        if (conn instanceof DruidPooledConnection) {
            conn = ((DruidPooledConnection) conn).getConnection();
        }

        if (conn instanceof ConnectionProxy) {
            conn = ((ConnectionProxy) conn).getRawObject();
        }

        if (clazz.isAssignableFrom(conn.getClass())) {
            if (validationQueryTimeout <= 0) {
                validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
            }

            try {
                ping.invoke(conn, true, validationQueryTimeout * 1000);
            } catch (InvocationTargetException e) {
                Throwable cause = e.getCause();
                if (cause instanceof SQLException) {
                    throw (SQLException) cause;
                }
                throw e;
            }
            return true;
        }
    }

    String query = validateQuery;
    if (validateQuery == null || validateQuery.isEmpty()) {
        query = DEFAULT_VALIDATION_QUERY;
    }

    Statement stmt = null;
    ResultSet rs = null;
    try {
        stmt = conn.createStatement();
        if (validationQueryTimeout > 0) {
            stmt.setQueryTimeout(validationQueryTimeout);
        }
        rs = stmt.executeQuery(query);
        return true;
    } finally {
        JdbcUtils.close(rs);
        JdbcUtils.close(stmt);
    }

}

我們可以看到上述方法中有三個(gè)返回的地方:第一個(gè)連接已關(guān)閉;第二個(gè)使用ping的形式進(jìn)行檢查;第三,使用select 1的方式進(jìn)行檢查。而使用ping的形式檢查時(shí),無(wú)論是否拋異常都會(huì)返回true。這里我們禁用該模式即可。

進(jìn)入ping的業(yè)務(wù)邏輯主要靠變量usePingMethod來(lái)判斷,追蹤代碼會(huì)發(fā)現(xiàn)在這里進(jìn)行的設(shè)置:

public void configFromProperties(Properties properties) {
    String property = properties.getProperty("druid.mysql.usePingMethod");
    if ("true".equals(property)) {
        setUsePingMethod(true);
    } else if ("false".equals(property)) {
        setUsePingMethod(false);
    }
}

那么,也就是說(shuō),當(dāng)我們把系統(tǒng)屬性druid.mysql.usePingMethod設(shè)置為false即可禁用該功能。

禁用Ping Method

找到了問(wèn)題的根源,那么剩下的就是如何禁用了,通常有三種形式。

第一,在啟動(dòng)程序時(shí)在運(yùn)行參數(shù)中增加:-Ddruid.mysql.usePingMethod=false。

第二,在Spring Boot項(xiàng)目中,可在啟動(dòng)類(lèi)中添加如下靜態(tài)代碼快:

static {
    System.setProperty("druid.mysql.usePingMethod","false");
}

第三,類(lèi)文件配置。在項(xiàng)目的DruidConfig類(lèi)中新增加:

/*
* 解決druid 日志報(bào)錯(cuò):discard long time none received connection:xxx
* */
@PostConstruct
public void setProperties(){
    System.setProperty("druid.mysql.usePingMethod","false");
}

至此,已可以成功關(guān)閉該功能,異常信息再也不會(huì)出現(xiàn)了。

為什么要清空空閑60秒以上的連接

猜測(cè),阿里給數(shù)據(jù)庫(kù)設(shè)置的數(shù)據(jù)庫(kù)空閑等待時(shí)間是60秒,mysql數(shù)據(jù)庫(kù)到了空閑等待時(shí)間將關(guān)閉空閑的連接,以提升數(shù)據(jù)庫(kù)服務(wù)器的處理能力。

MySQL的默認(rèn)空閑等待時(shí)間是8小時(shí),就是「wait_timeout」的配置值。如果數(shù)據(jù)庫(kù)主動(dòng)關(guān)閉了空閑的連接,而連接池并不知道,還在使用這個(gè)連接,就會(huì)產(chǎn)生異常。

springboot是什么

springboot一種全新的編程規(guī)范,其設(shè)計(jì)目的是用來(lái)簡(jiǎn)化新Spring應(yīng)用的初始搭建以及開(kāi)發(fā)過(guò)程,SpringBoot也是一個(gè)服務(wù)于框架的框架,服務(wù)范圍是簡(jiǎn)化配置文件。

以上是“Spring Boot集成Druid出現(xiàn)異常報(bào)錯(cuò)的原因是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI