MySQL使用ReplicationConnection导致的连接失效分析与解决( 二 )

ReplicationConnectionProxy的重要组成关于数据库连接代理 , 
ReplicationConnectionProxy中的主要组成如下图:

MySQL使用ReplicationConnection导致的连接失效分析与解决

文章插图
 
ReplicationConnectionProxy存在masterConnection和slavesConnection两个实际连接对象 , currentConnetion(当前连接)可以切换成mastetConnection或者slavesConnection , 切换方式可以通过设置readOnly实现 。业务逻辑中 , 实现读写分离的核心也在于此 , 简单来说:使用ReplicationConnection做读写分离时 , 只要做一个“设置connection的readOnly属性的”aop即可 。
基于
ReplicationConnectionProxy , 业务逻辑中获取到的Connection代理对象 , 数据库访问时的主要逻辑是什么样的呢?
ReplicationConnection代理对象处理过程对于业务逻辑而言 , 获取到的Connection实例 , 是ReplicationConnection代理对象 , 该代理对象通过
ReplicationConnectionProxy和ReplicationMySQLConnection相互协同完成对数据库访问的处理 , 其中ReplicationConnectionProxy在实现 InvocationHandler的同时 , 还充当对连接管理的角色 , 核心逻辑如下图:
MySQL使用ReplicationConnection导致的连接失效分析与解决

文章插图
 
对于prepareStatement等常规逻辑 , ConnectionMySQConnection获取到当前连接进行处理(普通的读写分离的处理的重点正是在此);此时 , 重点提及pingInternal方法 , 其处理方式也是获取当前连接 , 然后执行pingInternal逻辑 。
对于ping()这个特殊逻辑 , 图中描述相对简单 , 但主体含义不变 , 即:对master连接和sleves连接都要进行ping()的处理 。
图中 , pingInternal流程和druid的MySQ连接检查有关 , 而ping的特殊处理 , 也正是解决问题的关键 。
druid数据源对MySQ连接的检查druid中对MySQL连接检查的默认实现类是
MySqlValidConnectionChecker , 其中核心逻辑如下:
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);}}对应服务中使用的mysql-jdbc(5.1.45版) , 在未设置“druid.mysql.usePingMethod”系统属性的情况下 , 默认usePingMethod为true , 如下:
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);}}同时 , 可以看出
MySqlValidConnectionChecker中的 ping方法使用的是MySQLConnection中的pingInternal方法 , 而该方法 , 结合上面对ReplicationConnection的分析 , 当调用pingInternal时 , 只是对当前连接进行检验 。执行检验连接的时机是通过DrduiDatasource获取连接时 , 此时未设置readOnly属性 , 检查的连接 , 其实只是ReplicationConnectionProxy中的master连接 。
此外 , 如果通过“druid.mysql.usePingMethod”属性设置usePingMeghod为false , 其实也会导致连接失效的问题 , 因为:当通过valideQuery(例如“select 1”)进行连接校验时 , 会走到ReplicationConnection中的普通查询逻辑 , 此时对应的连接依然是master连接 。


推荐阅读