您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

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

时间:2022-06-24 10:16:15  来源:  作者:Java小仙女

MySQL数据库读写分离,是提高服务质量的常用手段之一,而对于技术方案,有很多成熟开源框架或方案,例如:sharding-jdbc、spring中的AbstractRoutingDatasource、MySQL-Router等,而mysql-jdbc中的ReplicationConnection亦可支持。本文暂不对读写分离的技术选型做过多的分析,只是探索在使用druid作为数据源、结合ReplicationConnection做读写分离时,连接失效的原因,并找到一个简单有效的解决方案。

  • 问题背景

由于历史原因,某几个服务出现连接失效异常,关键报错如下:

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

 

从日志不难看出,这是由于该连接长时间未和MySQL服务端交互,服务端已将连接关闭,典型的连接失效场景。

涉及的主要配置如下:

jdbc配置

jdbc:mysql:replication://master_host:port,slave_host:port/database_name

druid配置

testWhileIdle=true(即,开启了空闲连接检查);


timeBetweenEvictionRunsMillis=6000L(即,对于获取连接的场景,如果某连接空闲时间超过1分钟,将会进行检查,如果连接无效,将抛弃后重新获取)。

附:
DruidDataSource.getConnectionDirect中,处理逻辑如下:

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超时参数配置

wait_timeout=3600(3600秒,即:如果某连接超过一个小时和服务端没有交互,该连接将会被服务端kill)。

显而易见,基于如上配置,按照常规理解,不应该出现“The last packet successfully received from server was xxx,xxx,xxx milliseconds ago”的问题。(当然,当时也排除了人工介入kill掉数据库连接的可能)。

当“理所应当”的经验解释不了问题所在,往往需要跳出可能浮于表面经验束缚,来一次追根究底。那么,该问题的真正原因是什么呢?

  • 本质原因

当使用druid管理数据源,结合mysql-jdbc中原生的ReplicationConnection做读写分离时,ReplicationConnection代理对象中实际存在master和slaves两套连接,druid在做连接检测时候,只能检测到其中的master连接,如果某个slave连接长时间未使用,会导致连接失效问题。

  • 原因分析

mysql-jdbc中,数据库驱动对连接的处理过程

结合com.mysql.jdbc.Driver源码,不难看出mysql-jdbc中获取连接的主体流程如下:

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

 

对于以“jdbc:mysql:replication://”开头配置的jdbc-url,通过mysql-jdbc获取到的连接,其实是一个ReplicationConnection的代理对象,默认情况下,“jdbc:mysql:replication://”后的第一个host和port对应master连接,其后的host和port对应slaves连接,而对于存在多个slave配置的场景,默认使用随机策略进行负载均衡。

ReplicationConnection代理对象,使用JDK动态代理生成的,其中InvocationHandler的具体实现,是
ReplicationConnectionProxy,关键代码如下:

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的重要组成

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

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

 


ReplicationConnectionProxy存在masterConnection和slavesConnection两个实际连接对象,currentCo.NETion(当前连接)可以切换成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());
}
 
@Override
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);
    }
}

同时,可以看出
MySqlValidConnectionChecker中的 ping方法使用的是MySQLConnection中的pingInternal方法,而该方法,结合上面对ReplicationConnection的分析,当调用pingInternal时,只是对当前连接进行检验。执行检验连接的时机是通过DrduiDatasource获取连接时,此时未设置readOnly属性,检查的连接,其实只是ReplicationConnectionProxy中的master连接。

此外,如果通过“druid.mysql.usePingMethod”属性设置usePingMeghod为false,其实也会导致连接失效的问题,因为:当通过valideQuery(例如“select 1”)进行连接校验时,会走到ReplicationConnection中的普通查询逻辑,此时对应的连接依然是master连接。

题外一问 :ping方法为什么使用“pingInternal”,而不是常规的ping?原因:pingInternal预留了超时时间等控制参数。

  • 解决方式

调整依赖版本

服务中使用的mysql-jdbc版本为5.1.45,druid版本为1.1.20。经过对其他高版本依赖的了解,依然存在该问题。

修改读写分离实现

修改的工作量主要在于数据源配置和aop调整,但需要一定的整体回归验证成本,鉴于涉及该问题的服务重要性一般,暂不做大调整。

拓展mysql-jdbc驱动

基于原有ReplicationConnection的功能,拓展pingInternal调整为普通的ping,集成原有Driver拓展新的Driver。方案可行,但修改成本不算小。

基于druid,拓展MySQL连接检查

为简单高效解决问题,选择拓展
MySqlValidConnectionChecker, 并在 druid 数据源中加上对应配置即可。拓展 如下:

public class MySqlReplicationCompatibleValidConnectionChecker extends MySqlValidConnectionChecker {







    private static final Log LOG = LogFactory.getLog(MySqlValidConnectionChecker.class);

    /**

     * 

     */

    private static final long serialVersionUID = 1L;




    @Override

    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);

    }




}


ReplicatoinConnection.ping()的实现逻辑中,会对所有master和slaves连接进行ping操作,最终每个ping操作都会调用到LoadBalancedConnectionProxy.doPing进行处理,而此处,可在数据库配置url中设置loadBalancePingTimeout属性设置超时时间。



Tags:MySQL   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
怎么查询mysql的最大连接数查询命令为:show variables like &#39;%max_connections%&#39;;MySQL 默认的最大连接数为 100,可以在 mysql 客户端使用上述命令查看此命令将得到类...【详细内容】
2022-07-18  Tags: MySQL  点击:(0)  评论:(0)  加入收藏
mysql中的my.ini路径在哪里mysql中“my.ini”文件:1、linux系统中,该文件的位置是“/etc/mysql/my.cnf”;2、windows系统中,该文件的位置是“C:\Program Files\MySQL\MySQL Serv...【详细内容】
2022-07-15  Tags: MySQL  点击:(14)  评论:(0)  加入收藏
前言注:本文不涉及对MySQL协议报文研究,仅讲解原理,并且做部分演示。搭建MySQL恶意服务器读取文件这件事,虽然直接利用门槛较高,但是由于在网上看到了一种比较新颖的利用方式(利用...【详细内容】
2022-07-15  Tags: MySQL  点击:(9)  评论:(0)  加入收藏
俗话说,天下大势,合久必分、分久必合。数据库领域同样如此。过去五十余年,数据库经历OLTP和OLAP两种需求漫长的融合-分离-再融合的过程。究其原因,数据库的发展始终与用户场景需...【详细内容】
2022-07-14  Tags: MySQL  点击:(12)  评论:(0)  加入收藏
all的用法与子查询配合使用在all的用法中,有三种第一种: <>all类似于not in 等效于not in语法:select 列名 from 表名 where 列名 <> all(select 列名 from 表名 where 条件...【详细内容】
2022-07-14  Tags: MySQL  点击:(10)  评论:(0)  加入收藏
创建数据表1、 基本语法莆田IT外包|服务器虚拟化|数据存储|数据备份|网络故障排除|数据恢复 www.xiaolin.cc需要注意:表需要放在对应的数据库下面,IT外包 服务器虚拟化 数据存...【详细内容】
2022-07-13  Tags: MySQL  点击:(8)  评论:(0)  加入收藏
我熟练应用ctrl c和ctrl v 开发curd代码好多年了。 mysql查询为什么会慢,关于这个问题,在实际开发经常会遇到,而面试中,也是个高频题。 遇到这种问题,我们一般也会想到是因为索引...【详细内容】
2022-07-12  Tags: MySQL  点击:(11)  评论:(0)  加入收藏
1、查看慢查询相关参数show variables like &#39;slow_query%&#39;; 2、慢查询时间//查看慢查询时间show variables like &#39;long_query_time&#39;; 3、开启慢查询set glo...【详细内容】
2022-07-12  Tags: MySQL  点击:(9)  评论:(0)  加入收藏
问题:在阿里云远端服务器上搭建mysql数据库,安装时候提示如下:FATAL ERROR: please install the following Perl modules before executing ./scripts/mysql_install_db:Data::...【详细内容】
2022-07-12  Tags: MySQL  点击:(13)  评论:(0)  加入收藏
前言今天笔者想和大家来聊聊python接口自动化的MySQL数据连接,废话不多说咱们直接进入主题吧。 一、什么是 PyMySQL?PyMySQL是在Python3.x版本中用于连接MySQL服务器的一个库,P...【详细内容】
2022-07-11  Tags: MySQL  点击:(24)  评论:(0)  加入收藏
▌简易百科推荐
怎么查询mysql的最大连接数查询命令为:show variables like &#39;%max_connections%&#39;;MySQL 默认的最大连接数为 100,可以在 mysql 客户端使用上述命令查看此命令将得到类...【详细内容】
2022-07-18  网站建设和维护    Tags:mysql数据库   点击:(0)  评论:(0)  加入收藏
mysql中的my.ini路径在哪里mysql中“my.ini”文件:1、linux系统中,该文件的位置是“/etc/mysql/my.cnf”;2、windows系统中,该文件的位置是“C:\Program Files\MySQL\MySQL Serv...【详细内容】
2022-07-15  网站建设和维护    Tags:my.ini   点击:(14)  评论:(0)  加入收藏
前言注:本文不涉及对MySQL协议报文研究,仅讲解原理,并且做部分演示。搭建MySQL恶意服务器读取文件这件事,虽然直接利用门槛较高,但是由于在网上看到了一种比较新颖的利用方式(利用...【详细内容】
2022-07-15  合天网安实验室    Tags:MySQL   点击:(9)  评论:(0)  加入收藏
all的用法与子查询配合使用在all的用法中,有三种第一种: <>all类似于not in 等效于not in语法:select 列名 from 表名 where 列名 <> all(select 列名 from 表名 where 条件...【详细内容】
2022-07-14  秃头Java人    Tags:mysql   点击:(10)  评论:(0)  加入收藏
创建数据表1、 基本语法莆田IT外包|服务器虚拟化|数据存储|数据备份|网络故障排除|数据恢复 www.xiaolin.cc需要注意:表需要放在对应的数据库下面,IT外包 服务器虚拟化 数据存...【详细内容】
2022-07-13  晓林电脑服务    Tags:数据表   点击:(8)  评论:(0)  加入收藏
我熟练应用ctrl c和ctrl v 开发curd代码好多年了。 mysql查询为什么会慢,关于这个问题,在实际开发经常会遇到,而面试中,也是个高频题。 遇到这种问题,我们一般也会想到是因为索引...【详细内容】
2022-07-12  dbaplus社群    Tags:MySQL查询   点击:(11)  评论:(0)  加入收藏
1、查看慢查询相关参数show variables like &#39;slow_query%&#39;; 2、慢查询时间//查看慢查询时间show variables like &#39;long_query_time&#39;; 3、开启慢查询set glo...【详细内容】
2022-07-12  wcmwcmchq    Tags:mysql   点击:(9)  评论:(0)  加入收藏
问题:在阿里云远端服务器上搭建mysql数据库,安装时候提示如下:FATAL ERROR: please install the following Perl modules before executing ./scripts/mysql_install_db:Data::...【详细内容】
2022-07-12  网站建设和维护    Tags:安装mysql   点击:(13)  评论:(0)  加入收藏
异常现象开发人员反馈,有一台服务器内存几乎被 MySQL 耗尽了,执行 top 命令,输出如下: 这台机器是个测试环境,MySQL 是开发自己安装的,数据库版本 5.6.51 ,机器总内存32G,MySQL 占了...【详细内容】
2022-07-05  爱可生    Tags:MySQL   点击:(21)  评论:(0)  加入收藏
Elasticsearch的数据来自Mysql数据库中,所以当我们的MySQL发生改变时,Elasticsearch也要跟着改变,这时候我们的es的数据就要和mysql同步了同步实现思路常见的数据同步方案有三...【详细内容】
2022-06-30  Java热点    Tags:数据同步   点击:(14)  评论:(0)  加入收藏
站内最新
站内热门
站内头条