Spring Boot интегрирует исключения Druid
В проекте Druid, интегрированном с Spring Boot, в журнале ошибок часто появляются следующие сообщения об ошибках:
discard long time none received connection. , jdbcUrl : jdbc:mysql://******?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8, version : 1.2.3, lastPacketReceivedIdleMillis : 172675
После расследования было обнаружено, что аномалия была вызвана версией Друида, и такой аномалии не было в 1.2.2 и предыдущих версиях. Эта проблема существует в вышеперечисленных версиях, давайте проанализируем ненормальную причину и решение.
Анализ аномалий
Во-первых, указанное выше исключение никак не влияет на нормальную работу программы, но как программисту невыносимо видеть постоянное возникновение исключений в программе. Так что надо еще докопаться до сути.
Отслеживание информации о стеке обнаружит, что соответствующее исключение выдается из метода com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal, и соответствующий код выглядит следующим образом:
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).
Принцип отслеживания
В приведенном выше коде мы видим, что есть предпосылки для входа в бизнес-логику, то есть обе переменные valid и isMySql истинны. Необходимо, чтобы isMySql был истинным, и мы используем саму базу данных Mysql. Так можно ли сделать действительное ложным? Не войдет ли это в бизнес-процесс?
Давайте посмотрим на источник действительного или над методом:
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
Мы находим подкласс реализации Mysql MySqlValidConnectionChecker от validConnectionChecker, Реализация isValidConnection в этом классе выглядит следующим образом:
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);
}
}
Мы видим, что в приведенном выше методе есть три возврата: первое соединение закрывается, второе проверяется с помощью ping, третье проверяется с помощью select 1. При проверке в форме ping он вернет true независимо от того, было ли выброшено исключение. Здесь мы можем отключить этот режим.
О бизнес-логике ввода пинга в основном судят по переменной usePingMethod, и код отслеживания найдет сделанные здесь настройки:
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);
}
}
То есть, когда мы устанавливаем для системного свойства druid.mysql.usePingMethod значение false, функцию можно отключить.
Отключить метод проверки связи
Найдите источник проблемы, остальное как отключить, обычно в трех формах.
Во-первых, добавьте: -Ddruid.mysql.usePingMethod=false в параметры запуска при запуске программы.
Во-вторых, в проекте Spring Boot в класс запуска можно добавить следующий статический код:
static {
System.setProperty("druid.mysql.usePingMethod","false");
}
В-третьих, конфигурация файла класса. Недавно добавленный в класс DruidConfig проекта:
/*
* 解决druid 日志报错:discard long time none received connection:xxx
* */
@PostConstruct
public void setProperties(){
System.setProperty("druid.mysql.usePingMethod","false");
}
На данный момент функция успешно отключена, и аномальная информация больше никогда не появится.
Почему вы хотите очистить подключения, которые не использовались более 60 секунд?
Предполагается, что время ожидания простоя базы данных, установленное Али для базы данных, составляет 60 секунд, и база данных mysql закроет соединение бездействия, когда время ожидания простоя будет достигнуто, чтобы улучшить вычислительную мощность сервера базы данных.
Время ожидания бездействия MySQL по умолчанию составляет 8 часов, что является значением конфигурации «wait_timeout». Если база данных активно закрывает бездействующее соединение, а пул соединений не знает, что оно все еще использует это соединение, возникнет исключение.