Spring Boot集成Druid出現(xiàn)異常報錯的原因及解決
Spring Boot集成Druid異常
在Spring Boot集成Druid項(xiàng)目中,發(fā)現(xiàn)錯誤日志中頻繁的出現(xiàn)如下錯誤信息:
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)過排查發(fā)現(xiàn)是Druid版本導(dǎo)致的異常,在1.2.2及以前版本并未出現(xiàn)如此異常。而在其以上版本均存在此問題,下面就來分析一下異常原因及解決方案。
異常分析
首先上面的異常并不影響程序的正常運(yùn)行,但作為程序員看到程序中不停的出現(xiàn)異常還是難以忍受的。所以還是要刨根問底的解決一下的。
跟蹤堆棧信息會發(fā)現(xiàn)對應(yīng)的異常是從com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal方法中拋出的,對應(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) 是獲取上一次使用的時間,mysqlIdleMillis 就是計算出來空閑的時間,timeBetweenEvictionRunsMillis 是常量60秒。如果連接空閑了60秒以上,那就discardConnection(holder) 丟棄這個舊連接并順帶打印了一個日志LOG.warn(errorMsg)。
原理追蹤
在上述代碼中,我們看到進(jìn)入該業(yè)務(wù)邏輯是有前提條件的,也就是valid和isMySql變量同時為true。isMySql為true是必須的,我們使用的本身就是Mysql數(shù)據(jù)庫。那么是否可以讓valid為false呢?這樣不就不會進(jìn)入該業(yè)務(wù)處理了嗎?
來看看valid的來源,還是在該方法的上面:
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
我們找到validConnectionChecker的Mysql實(shí)現(xiàn)子類MySqlValidConnectionChecker,該類中對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);
}
}
我們可以看到上述方法中有三個返回的地方:第一個連接已關(guān)閉;第二個使用ping的形式進(jìn)行檢查;第三,使用select 1的方式進(jìn)行檢查。而使用ping的形式檢查時,無論是否拋異常都會返回true。這里我們禁用該模式即可。
進(jìn)入ping的業(yè)務(wù)邏輯主要靠變量usePingMethod來判斷,追蹤代碼會發(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);
}
}
那么,也就是說,當(dāng)我們把系統(tǒng)屬性druid.mysql.usePingMethod設(shè)置為false即可禁用該功能。
禁用Ping Method
找到了問題的根源,那么剩下的就是如何禁用了,通常有三種形式。
第一,在啟動程序時在運(yùn)行參數(shù)中增加:-Ddruid.mysql.usePingMethod=false。
第二,在Spring Boot項(xiàng)目中,可在啟動類中添加如下靜態(tài)代碼快:
static {
System.setProperty("druid.mysql.usePingMethod","false");
}
第三,類文件配置。在項(xiàng)目的DruidConfig類中新增加:
/*
* 解決druid 日志報錯:discard long time none received connection:xxx
* */
@PostConstruct
public void setProperties(){
System.setProperty("druid.mysql.usePingMethod","false");
}
至此,已可以成功關(guān)閉該功能,異常信息再也不會出現(xiàn)了。
為什么要清空空閑60秒以上的連接
猜測,阿里給數(shù)據(jù)庫設(shè)置的數(shù)據(jù)庫空閑等待時間是60秒,mysql數(shù)據(jù)庫到了空閑等待時間將關(guān)閉空閑的連接,以提升數(shù)據(jù)庫服務(wù)器的處理能力。
MySQL的默認(rèn)空閑等待時間是8小時,就是「wait_timeout」的配置值。如果數(shù)據(jù)庫主動關(guān)閉了空閑的連接,而連接池并不知道,還在使用這個連接,就會產(chǎn)生異常。
以上就是Spring Boot集成Druid出現(xiàn)異常報錯的原因及解決的詳細(xì)內(nèi)容,更多關(guān)于Spring Boot集成Druid出現(xiàn)異常的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot如何實(shí)現(xiàn)代理服務(wù)器
這篇文章主要介紹了Springboot如何實(shí)現(xiàn)代理服務(wù)器問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
Java代理模式之靜態(tài)代理與動態(tài)代理的區(qū)別及優(yōu)缺點(diǎn)
代理模式是一種常用的設(shè)計模式,它允許通過引入一個代理對象來控制對目標(biāo)對象的訪問,在Java中,代理模式被廣泛應(yīng)用,它可以提供額外的功能,如權(quán)限檢查、緩存、日志記錄等,本文將介紹靜態(tài)代理與動態(tài)代理的區(qū)別及優(yōu)缺點(diǎn),需要的朋友可以參考下2023-06-06
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之環(huán)形鏈表
無論是靜態(tài)鏈表還是動態(tài)鏈表,有時在解決具體問題時,需要我們對其結(jié)構(gòu)進(jìn)行稍微地調(diào)整。比如,可以把鏈表的兩頭連接,使其成為了一個環(huán)狀鏈表,通常稱為循環(huán)鏈表2022-02-02
線程池ThreadPoolExecutor使用簡介與方法實(shí)例
今天小編就為大家分享一篇關(guān)于線程池ThreadPoolExecutor使用簡介與方法實(shí)例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
SpringBoot org.springframework.beans.factory.Unsatisfie
本文主要介紹了SpringBoot org.springframework.beans.factory.UnsatisfiedDependencyException依賴注入異常,文中通過示例代碼介紹的很詳細(xì),具有一定的參考價值,感興趣的可以了解一下2024-02-02
WebDriver實(shí)現(xiàn)自動化打開IE中的google網(wǎng)頁并實(shí)現(xiàn)搜索
這篇文章主要介紹了WebDriver實(shí)現(xiàn)自動化打開IE中的google網(wǎng)頁并實(shí)現(xiàn)搜索,需要的朋友可以參考下2014-04-04

