`
whitesock
  • 浏览: 483771 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

Testing MySQL Replication Connection

阅读更多

    目前项目中使用了MySQL replication,并通过LVS对slaves进行负载均衡,数据库连接池使用的是c3p0。在使用过程中发现, LVS TCP timeout可能导致数据库连接被切断,从而应用程序中报数据库连接异常。
    ReplicationConnection内部保持了两个数据库连接,分别是masterConnection和slaveConnection。实际生效的连接取决于连接的readOnly属性,即readOnly ? currentConnection=slaveConnection : currentConnection=masterConnection。
    c3p0的提供了两种处理空闲连接的机制,对应的配置参数分别是:idleConnectionTestPeriod和maxIdleTime。但是这两种机制在默认情况下对ReplicationConnection不奏效。原因如下:

  • idleConnectionTestPeriod:c3p0在定期检查ReplicationConnection时只是检查了其内部的currentConnection,如果在某段时间内连接的readOnly属性为false,那么c3p0只会检查masterConnection,而不会检测slaveConnection是否仍然有效。
  • maxIdleTime:如果应用程序在一段内只以readOnly=false使用某个ReplicationConnection,那么c3p0不会认为该连接空闲,而实际上其内部的slaveConnection可能已经超时。

    默认情况下,c3p0使用DefaultConnectionTester(通过connectionTesterClassName配置)进行连接检查(基于Query),该类有以下两个比较重要的方法:

  • public int activeCheckConnection(Connection c, String query, Throwable[] rootCauseOutParamHolder):通常用于checkin,checkout和定期的连接检查。
  • public int statusOnException(Connection c, Throwable t, String query, Throwable[] rootCauseOutParamHolder):在连接抛异常的情况下,c3p0通过此方法确认连接本身是否可用(即是否需要销毁此连接)。

    需要注意的是,MySQL connector也提供了一个实现了c3p0的ConnectionTester接口的类:MysqlConnectionTester。该类使用com.mysql.jdbc.Connection的ping方法(相对于执行query,ping更轻量级)来确认连接是否正常。笔者认为该类仍然不能正确检测ReplicationConnection。

    以下是笔者实现的一个ConnectionTester,用于检查MySQL ReplicationConnection:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mchange.v2.c3p0.AbstractConnectionTester;
import com.mysql.jdbc.CommunicationsException;


public final class MysqlReplicationConnectionTester extends AbstractConnectionTester {
	//
	private static final Logger LOGGER = LoggerFactory.getLogger(MysqlReplicationConnectionTester.class);
	
	//
	private static final long serialVersionUID = -7348778746126099053L;
	
	//
	private static final String DEFAULT_QUERY = "SELECT 1";
	
	public boolean equals(Object o) {
		return (o != null && o.getClass() == MysqlReplicationConnectionTester.class);
	}

	public int hashCode() {
		return MysqlReplicationConnectionTester.class.getName().hashCode();
	}
	    
	public int activeCheckConnection(Connection c, String query, Throwable[] outParamCause) {
		//
		boolean readOnly = false;
		boolean needRestoreReadOnly = false;
		try {
			//
			readOnly = c.isReadOnly();
			
			//
			int r = checkConnection(c, query, outParamCause, readOnly);
			if(r == CONNECTION_IS_OKAY) {
				needRestoreReadOnly = true;
				r = checkConnection(c, query, outParamCause, !readOnly);
			}
			return r;
		} catch(Exception e) {
			//
			LOGGER.warn("the connection: " + c + " was marked invalid", e);
			if (outParamCause != null) {
				outParamCause[0] = e;
			}
			return CONNECTION_IS_INVALID;
		} finally {
			try {
				if(needRestoreReadOnly) {
					c.setReadOnly(readOnly);
				}
			} catch (SQLException e) {
				LOGGER.error("failed to restore read only: " + readOnly + " on connection", e);
			}
		}
	}

	public int statusOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {
		//
		int r = checkConnectionOnException(c, t, query, outParamCause);
		
		//
		if(r != CONNECTION_IS_OKAY) {
			if (outParamCause != null) {
				outParamCause[0] = t;
			}
		}
		return r;
	}

	private int checkConnection(Connection c, String query, Throwable[] outParamCause, Boolean readOnly) {
		//
		if (query == null || query.equals("")) {
			query = DEFAULT_QUERY;
		}
		
		//
		ResultSet rs = null;
		Statement stmt = null;
		try {
			//
			boolean ro = c.isReadOnly();
			if(readOnly != null && readOnly != ro) {
				c.setReadOnly(readOnly);
			}
			
			//
			if(LOGGER.isInfoEnabled()) {
				LOGGER.info("testing connection: {} with query: {}, read only: {}", new Object[]{c, query, ro});
			}
			
			//
			stmt = c.createStatement();
			rs = stmt.executeQuery(query);
			return CONNECTION_IS_OKAY;
		} catch (SQLException e) {
			LOGGER.warn("failed to test connection: " + c + " with query: " + query + ", state: " + e.getSQLState(), e);
			if (outParamCause != null) {
				outParamCause[0] = e;
			}
			return CONNECTION_IS_INVALID;
		} catch (Exception e) {
			LOGGER.warn("failed to test connection: " + c + " with query: " + query, e);
			if (outParamCause != null) {
				outParamCause[0] = e;
			}
			return CONNECTION_IS_INVALID;
		} finally {
			closeQuietly(rs);
			closeQuietly(stmt);
		}
	}
	
	private int checkConnectionOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {
		//
		if (t instanceof CommunicationsException) {
			return CONNECTION_IS_INVALID;
		}
		
		//
		if (t instanceof SQLException) {
			final String sqlState = ((SQLException) t).getSQLState();
			if (sqlState != null && sqlState.startsWith("08")) {
				return CONNECTION_IS_INVALID;
			} else {
				return CONNECTION_IS_OKAY;
			}
		}
		
		// Runtime/Unchecked?
		return CONNECTION_IS_INVALID;
	}
	
	private void closeQuietly(ResultSet rs) {
		//
		if(rs == null) {
			return;
		}
		
		//
		try {
			rs.close();
		} catch (SQLException e) {
			LOGGER.warn("failed to close result set", e);
		}
	}
	
	private void closeQuietly(Statement stat) {
		//
		if(stat == null) {
			return;
		}
		
		//
		try {
			stat.close();
		} catch (SQLException e) {
			LOGGER.warn("failed to close statement", e);
		}
        }
}

    需要注意的是,以上代码适用于MySQL 5.0。对于MySQL 5.1,需要修改checkConnectionOnException方法,如下:

 if (t instanceof CommunicationsException || "com.mysql.jdbc.exceptions.jdbc4.CommunicationsException".equals(throwable.getClass().getName())) {
    return CONNECTION_IS_INVALID;
}

 

    此外, 由于Spring Dao会转译SQLException,因此在Spring环境中,不能使用sqlState判断连接是否正常,而是需要使用基于query的方式,如下:

public int statusOnException(Connection c, Throwable t, String query, Throwable[] outParamCause) {
	return checkConnection(c, query, outParamCause, null);
}

 

5
0
分享到:
评论
2 楼 ppxieppp 2012-10-18  
因为这个问题,找了3天原因.基本和你说的一致
1 楼 Technoboy 2011-04-13  

相关推荐

    mysql replication搭建.docx

    ### MySQL Replication 架构与实现 #### 一、MySQL Replication 概念与优势 MySQL Replication 是一种数据复制机制,它允许将一个MySQL服务器(主服务器或Master)的数据异步复制到一个或多个其他MySQL服务器(从...

    MySql Replication Tutorial

    MySql Replication Tutorial,关于MySql Replication 的 PPT

    完整精品数据库课件 MySQL从入门到精通 第18章 MySQL Replication(共27页).ppt

    MySQL Replication是MySQL数据库系统中的一个重要特性,它允许数据从一个主服务器(master)自动同步到一个或多个从服务器(slaves)。这种技术主要用于数据备份、负载均衡和高可用性设置,确保即使在主服务器出现...

    利用JDBC和MySQL Replication实现数据库集群

    针对这一情况,提出在现有硬件的基础上利用JDBC规范与MySQL Replication实现数据库集群从而解决数据访问瓶颈。其主要方法是在进行JDBC连接之前实现负载均衡,所有SQL请求由负载均衡器进行统一调度。在数据库端利用...

    MySQL Replication一主多从环境搭建.docx

    MySQL Replication是一种数据库复制技术,允许数据从一个MySQL服务器(主服务器)实时同步到其他一个或多个MySQL服务器(从服务器)。这种技术对于实现高可用性、负载均衡和数据备份至关重要。以下是对一主多从环境...

    MySQL Group Replication 详细搭建部署过程

    MySQL Group Replication 详细搭建部署过程 MySQL Group Replication 是一种基于组的复制技术,用于容错系统中。它由多个服务器(节点)组成,每个节点都可以独立执行事务,而读写事务则会在于 group 内的其他节点...

    深入理解MySQL Group Replication.pdf

    深入理解MySQL Group Replication MySQL Group Replication是一种高可用性和高性能的解决方案,旨在提供数据库的高可用性和高性能。它是MySQL数据库的一部分,旨在提供高可用性和高性能的解决方案。 背景: 数据库...

    第18章 MySQL Replication PPT

    MySQL复制(Replication)是MySQL数据库系统中一种强大的功能,它允许数据从一个服务器(主服务器)异步地复制到一个或多个其他服务器(从服务器)。这种架构为高可用性、负载均衡和数据备份提供了基础。 在MySQL ...

    Ubuntu上MySQL的Replication配置

    胖子摸索出来的,Ubuntu上MySQL的Replication配置,的简单记录步骤

    mysql replication修改库名及复制单个表

    在深入探讨如何通过MySQL Replication实现库名修改与单个表的复制之前,我们先来了解MySQL Replication的基本概念及其工作原理。MySQL Replication是一种数据复制机制,它允许从一台服务器(主服务器)向另一台或多...

    基于MySQL Replication的数据库集群解决方案.pdf

    【MySQL Replication数据库集群解决方案】 在构建电子商务系统数据库时,常常面临单一服务器处理能力和网络带宽不足的问题,以及对系统可靠性的高要求和快速故障恢复的需求。随着用户数量的增加,需要灵活扩展...

    深入理解MySQL Group Replication

    ### 深入理解MySQL Group Replication #### 背景与定义 MySQL Group Replication是一种高可用性和可扩展性的解决方案,它通过在多个MySQL服务器之间自动同步数据来确保数据的一致性和可用性。该技术自MySQL 5.7.17...

    mysql-replication配置文档

    MySQL复制(replication)是一种将数据从一个MySQL服务器(主服务器)实时同步到另一个或多个服务器(从服务器)的技术,这种技术有助于实现高可用性、负载均衡和数据备份。在MySQL中,复制主要分为单向异步复制和...

    mysql-replication mysql数据库主从复制步骤

    GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; ``` #### 5. 获取主服务器的二进制日志位置和偏移量 - 使用以下命令获取二进制日志的位置和偏移量: ```sql SHOW MASTER STATUS; ``` ###...

    MySQL Replication(复制)用MySQL5.5提高可扩展性和可用性_5.5

    MySQL Replication(复制)已经在一些著名的网站和企业广泛应用以将数据库的扩展性提升到极限水平。对用户而言可以简单快速地为数据库创建多个副本,超越单个数据库实例容量的限制,弹性扩展数据库系统以满足快速增长...

    python-mysql-replication, 在PyMYSQL之上,MySQL复制协议构建的纯 python 实现.zip

    python-mysql-replication, 在PyMYSQL之上,MySQL复制协议构建的纯 python 实现 python-mysql-replication MySQL复制协议在PyMYSQL之上的纯 python 实现。 这允许你接收诸如插入。更新。delete 和它们的数据和原始...

    MySQLReplication主从复制全方位解决方案.docx

    MySQLReplication主从复制全方位解决方案.docx

Global site tag (gtag.js) - Google Analytics