问题背景
涉及的主要配置如下:
if (testWhileIdle) {final DruidConnectionHolder holder = poolableConnection.holder;long currentTimeMillis = System.currentTimeMillis();long lastActiveTimeMillis = holder.lastActiveTimeMillis;long lastExecTimeMillis = holder.lastExecTimeMillis;long lastKeepTimeMillis = holder.lastKeepTimeMillis;if (checkExecuteTime&& lastExecTimeMillis != lastActiveTimeMillis) {lastActiveTimeMillis = lastExecTimeMillis;}if (lastKeepTimeMillis > lastActiveTimeMillis) {lastActiveTimeMillis = lastKeepTimeMillis;}long idleMillis = currentTimeMillis - lastActiveTimeMillis;long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;if (timeBetweenEvictionRunsMillis <= 0) {timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;}if (idleMillis >= timeBetweenEvictionRunsMillis|| idleMillis < 0 // unexcepted branch) {boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);if (!validate) {if (LOG.isDebugEnabled()) {LOG.debug("skip not validate connection.");}discardConnection(poolableConnection.holder);continue;}}}
本质原因
原因分析
mysql-jdbc中,数据库驱动对连接的处理过程
public static ReplicationConnection createProxyInstance(List<String> masterHostList, Properties masterProperties, List<String> slaveHostList,Properties slaveProperties) throws SQLException {ReplicationConnectionProxy connProxy = new ReplicationConnectionProxy(masterHostList, masterProperties, slaveHostList, slaveProperties);return (ReplicationConnection) java.lang.reflect.Proxy.newProxyInstance(ReplicationConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy);}
ReplicationConnectionProxy的重要组成
ReplicationConnection代理对象处理过程
druid数据源对MySQ连接的检查
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);}}
public MySqlValidConnectionChecker(){try {clazz = Utils.loadClass("com.mysql.jdbc.MySQLConnection");if (clazz == null) {clazz = Utils.loadClass("com.mysql.cj.jdbc.ConnectionImpl");}if (clazz != null) {ping = clazz.getMethod("pingInternal", boolean.class, int.class);}if (ping != null) {usePingMethod = true;}} catch (Exception e) {LOG.warn("Cannot resolve com.mysql.jdbc.Connection.ping method. Will use 'SELECT 1' instead.", e);}configFromProperties(System.getProperties());}@Overridepublic void configFromProperties(Properties properties) {String property = properties.getProperty("druid.mysql.usePingMethod");if ("true".equals(property)) {setUsePingMethod(true);} else if ("false".equals(property)) {setUsePingMethod(false);}}
解决方式
调整依赖版本
修改读写分离实现
拓展mysql-jdbc驱动
基于druid,拓展MySQL连接检查
public class MySqlReplicationCompatibleValidConnectionChecker extends MySqlValidConnectionChecker {private static final Log LOG = LogFactory.getLog(MySqlValidConnectionChecker.class);/****/private static final long serialVersionUID = 1L;public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {if (conn.isClosed()) {return false;}if (conn instanceof DruidPooledConnection) {conn = ((DruidPooledConnection) conn).getConnection();}if (conn instanceof ConnectionProxy) {conn = ((ConnectionProxy) conn).getRawObject();}if (conn instanceof ReplicationConnection) {try {((ReplicationConnection) conn).ping();LOG.info("validate connection success: connection=" + conn.toString());return true;} catch (SQLException e) {LOG.error("validate connection error: connection=" + conn.toString(), e);throw e;}}return super.isValidConnection(conn, validateQuery, validationQueryTimeout);}}
想了解更多转转公司的业务实践,欢迎点击关注下方公众号:

