`
阅读更多

前言: 

         本文转子(http://www.l99.com/EditText_view.action?textId=383249)
         之前downpour有一个贴(http://www.iteye.com/topic/143714)讨论了在java中如何使用mysql的master-slave模式(master-slave模式的介绍见Qieqie的这个贴:http://www.iteye.com/topic/162717),其中readonly大大提到我们可以使用ReplicationDriver来从connection层把read或者write操作分开。这确实是一个比较好的方案,在那个帖子讨论后不久,我就在自己的机器上搭了一个mysql的master-slave模式,然后使用ReplicationDriver来控制读写访问不同的机器,测试通过了,事隔几个月之后,我准备把它用于生产环境中,但是问题来了,因为我的应用访问的数据库有多个,主要访问的库是master-slave模式,其他辅助库是就是指定的一台机器,这时候问题来了。 

Mysql的文档是这么写的:ReplicationDriver does not currently work with java.sql.DriverManager -based connection creation unless it is the only MySQL JDBC driver registered with the DriverManager . DriverManager是一个单例模式,一个DriverManager只能注册一个ReplicationDriver驱动,也就是说ReplicationDriver和Driver两个类不能同时使用,郁闷,及其郁闷,由于我之前没有仔细看这段说明,所以没有预料到这种情况。摆在前面的路有几条 

一,使用多个datasource解决问题, 
二,所有得datasource都使用这个驱动,但是这样做有一个缺点,在文章后面我会详细阐述这种做法得缺点。 
三,扩展再扩展,hack再hack。 
四,这种方案是第二种方案的补充,详见后文。

 

首先,我们来看一下ReplicationDriver的官方使用教程: 

写道
public static void main(String[] args) throws Exception {
ReplicationDriver driver = new ReplicationDriver();

Properties props = new Properties();

// We want this for failover on the slaves
props.put("autoReconnect", "true");

// We want to load balance between the slaves
props.put("roundRobinLoadBalance", "true");

props.put("user", "foo");
props.put("password", "bar");

//
// Looks like a normal MySQL JDBC url, with a
// comma-separated list of hosts, the first
// being the 'master', the rest being any number
// of slaves that the driver will load balance against
//

Connection conn =
driver.connect("jdbc:mysql://master,slave1,slave2,slave3/test",
props);

//
// Perform read/write work on the master
// by setting the read-only flag to "false"
//

//这个节点应该是通过spring的事务管理来设置,同时这个conn对象应该不是一个真正的connection,
//而是一个代理类,通过设置readonly,代理类会去使用不同的connection,
//那么问题是它该代理类使用的connection是哪里取的,抑或说难道它每次都会新开一个connection?,需要看源代码

conn.setReadOnly(false);

conn.setAutoCommit(false);
conn.createStatement().executeUpdate("UPDATE some_table ....");
conn.commit();

//
// Now, do a query from a slave, the driver automatically picks one
// from the list
//

conn.setReadOnly(true);

ResultSet rs =
conn.createStatement().executeQuery("SELECT a,b FROM alt_table");

.......
}

        这个示例看上去非常之简单,我们可以很容易的就通过ReplicationDriver拿到了一个Connection,首先,对于我们来说,conn.setReadOnly对我们来说这个方法应该是通过spring的事务管理来设置,同时这个conn对象应该不是一个真正的connection,而是一个代理类,通过设置readonly,代理类会去使用不同的connection,那么问题是它该代理类使用的connection是哪里取的,抑或说难道它每次都会新开一个connection?,这就需要看源代码 


               那么现在我们要弄清楚ReplicationDriver是怎么回事,反编译之后我们看到: 

写道
public ReplicationDriver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new NonRegisteringReplicationDriver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}

       看来看去,这个类中没有什么东西,那么再看看NonRegisteringReplicationDriver类吧。如下面的代码所示,这个类中主要就是这个方法connect方法 

 

写道
public Connection connect(String url, Properties info) throws SQLException {
Properties parsedProps = parseURL(url, info);
if (parsedProps == null) {
return null;
}
Properties masterProps = (Properties) parsedProps.clone();
Properties slavesProps = (Properties) parsedProps.clone();
slavesProps.setProperty("com.mysql.jdbc.ReplicationConnection.isSlave",
"true");

String hostValues = parsedProps.getProperty("HOST");

if (hostValues != null) {
StringTokenizer st = new StringTokenizer(hostValues, ",");
StringBuffer masterHost = new StringBuffer();
StringBuffer slaveHosts = new StringBuffer();
if (st.hasMoreTokens()) {
String hostPortPair[] = parseHostPortPair(st.nextToken());
if (hostPortPair[0] != null) {
masterHost.append(hostPortPair[0]);
}
if (hostPortPair[1] != null) {
masterHost.append(":");
masterHost.append(hostPortPair[1]);
}
}
boolean firstSlaveHost = true;
do {
if (!st.hasMoreTokens()) {
break;
}
String hostPortPair[] = parseHostPortPair(st.nextToken());
if (!firstSlaveHost) {
slaveHosts.append(",");
} else {
firstSlaveHost = false;
}
if (hostPortPair[0] != null) {
slaveHosts.append(hostPortPair[0]);
}
if (hostPortPair[1] != null) {
slaveHosts.append(":");
slaveHosts.append(hostPortPair[1]);
}
} while (true);
if (slaveHosts.length() == 0) {
throw SQLError
.createSQLException(
"Must specify at least one slave host to connect to for master/slave replication "
+ "load-balancing functionality",
"01S00");
}
masterProps.setProperty("HOST", masterHost.toString());
slavesProps.setProperty("HOST", slaveHosts.toString());
}
return new ReplicationConnection(masterProps, slavesProps);
}

      上面这个方法也很简单,就是解析url后,然后访问确定master和slave机器一些properties的配置。越来越接近真相了,继续往下看,让我们掀起ReplicationConnection的头盖来: 

先看构造方法: 

写道
public ReplicationConnection(Properties masterProperties,
Properties slaveProperties) throws SQLException {
Driver driver = new Driver();

StringBuffer masterUrl = new StringBuffer("jdbc:mysql://");
StringBuffer slaveUrl = new StringBuffer("jdbc:mysql://");
String masterHost = masterProperties.getProperty("HOST");
if (masterHost != null) {
masterUrl.append(masterHost);
}
String slaveHost = slaveProperties.getProperty("HOST");
if (slaveHost != null) {
slaveUrl.append(slaveHost);
}
String masterDb = masterProperties.getProperty("DBNAME");
masterUrl.append("/");
if (masterDb != null) {
masterUrl.append(masterDb);
}
String slaveDb = slaveProperties.getProperty("DBNAME");
slaveUrl.append("/");
if (slaveDb != null) {
slaveUrl.append(slaveDb);
}

//从这里可以看出,笔者前文提出的猜想是正确的,每一个ReplicationDriver其实是两个Connection的代理,这两个
//Connection才是真正访问DB的connection。 masterConnection = (com.mysql.jdbc.Connection) driver.connect(masterUrl
.toString(), masterProperties);
slavesConnection = (com.mysql.jdbc.Connection) driver.connect(slaveUrl
.toString(), slaveProperties);
currentConnection = masterConnection;
}

 这个构造方法没有任何的玄机,从这里也可以看出,那么前文提出的猜想是正确的,每一个ReplicationDriver其实是两个Connection的代理,这两个Connection才是真正访问DB的connection。好了,看到这里看客们大概也看出来了,当调用connection.setReadonly的时候,其实就是把需要的masterConnection或者slavesConnection赋值给当前的currentConnection,ReplicationDriver就是这么个实现,原理也非常简单,那么怎么解决文章中开头提出的那个问题呢。 


第一种方案: 
改成多个datasource,这种方式是最简单,最粗鲁的,然后我们就可以看到一堆有一堆,一坨又一坨的datasource,然后你还有一堆堆一坨坨的JdbcTemplate,HibernateTemplate,SqlMapClientTemplate,等等。 

第二种方案: 
第二种方案是所有的驱动都是用ReplicationDriver,有同学问:那怎么行呢,因为我又的datasource不是master-slave模式的。还好,没有什么关系,即使是这样配置jdbc:mysql://192.168.1.1:3306,192.168.1.1:3306/xx也是没有关系的,带来的结果就是一个ReplicationDriver其实hold了两个connection,而这两个connection其实是连着同一个数据库。那么也就是说如果连接池里配置了50个connection,那么实际上却有100个connection连着数据库,这种事情还是比较让人郁闷的。 


第三种方案: 
看来看去,问题都出现在DriverManager上,如果我新建一个DriverManager,行否,于是新建一个类,名约ReplicationDriverManager。这样系统中就有两个DriverManager了,普通的DriverManager注册的驱动为Driver.java,ReplicationDriverManager注册的驱动为ReplicationDriver。大家互不干扰,貌似可行。粗略的看了一下代码,也是可以实现的,关键在于需要扩展连接池(至少c3p0是这样的,需要重写c3p0的两个类),然后还需要重写一个ReplicationDriver,将静态块中的DriverManager换成我们自己的DriverManager。然后还需要重写ReplicationConnection,Driver类等等,也是非常麻烦的。 

想来想去,想破了头了,终于,还是有点头绪,就是在第二种方案的基础上,再次修改ReplicationConnection,也就是说,如果我的配置为jdbc:mysql://192.168.1.1:3306/xx,那么我强行把currentConnection设置为masterConnection,这样ReplicationConnection中的slavesConnection就一直是空着的,或者masterConnection和slavesConnection还有currentConnection这3个引用都指向同一个对象,那么连接池中配置50个连接,那么就是50个连接,不会变成100个连接了,而其他的master-slave模式的配置依旧,这个方式貌似看上去还是不错的。我们看看代码怎么写: 
首先,来一个EasyReplicationDriver,代码如下: 

写道
public class EasyReplicationDriver extends EasyNonRegisteringReplicationDriver
implements Driver {

public EasyReplicationDriver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new EasyNonRegisteringReplicationDriver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}

 接着再来一个EasyNonRegisteringReplicationDriver,如下: 

写道
/**
* @author ahuaxuan(aaron zhang)
* @since 2008-6-18
* @version $Id$


*/

public class EasyNonRegisteringReplicationDriver extends NonRegisteringDriver {

public EasyNonRegisteringReplicationDriver() throws SQLException {
}

public Connection connect(String url, Properties info) throws SQLException {
Properties parsedProps = parseURL(url, info);
if (parsedProps == null) {
return null;
}
Properties masterProps = (Properties) parsedProps.clone();
Properties slavesProps = (Properties) parsedProps.clone();
slavesProps.setProperty("com.mysql.jdbc.ReplicationConnection.isSlave",
"true");

String hostValues = parsedProps.getProperty("HOST");

if (hostValues != null) {
StringTokenizer st = new StringTokenizer(hostValues, ",");
StringBuffer masterHost = new StringBuffer();
StringBuffer slaveHosts = new StringBuffer();
if (st.hasMoreTokens()) {
String hostPortPair[] = parseHostPortPair(st.nextToken());
if (hostPortPair[0] != null) {
masterHost.append(hostPortPair[0]);
}
if (hostPortPair[1] != null) {
masterHost.append(":");
masterHost.append(hostPortPair[1]);
}
}
boolean firstSlaveHost = true;
do {
if (!st.hasMoreTokens()) {
break;
}
String hostPortPair[] = parseHostPortPair(st.nextToken());
if (!firstSlaveHost) {
slaveHosts.append(",");
} else {
firstSlaveHost = false;
}
if (hostPortPair[0] != null) {
slaveHosts.append(hostPortPair[0]);
}
if (hostPortPair[1] != null) {
slaveHosts.append(":");
slaveHosts.append(hostPortPair[1]);
}
} while (true);
/*if (slaveHosts.length() == 0) {
throw SQLError
.createSQLException(
"Must specify at least one slave host to connect to for master/slave replication "
+ "load-balancing functionality",
"01S00");
}*/
masterProps.setProperty("HOST", masterHost.toString());
slavesProps.setProperty("HOST", slaveHosts.toString());
}
return new EasyReplicationConnection(masterProps, slavesProps);
}

 注意上面我注释掉的这段代码,如果我们想要ReplicationDriver支持jdbc:mysql://192.168.1.1:3306/xxx,那么就必须把上面那段代码注释掉。 

       第3步,让我们看看EasyReplicationConnection这个类: 

写道
public EasyReplicationConnection(Properties masterProperties,
Properties slaveProperties) throws SQLException {
Driver driver = new Driver();

StringBuffer masterUrl = new StringBuffer("jdbc:mysql://");
StringBuffer slaveUrl = new StringBuffer("jdbc:mysql://");
String masterHost = masterProperties.getProperty("HOST");
if (masterHost != null) {
masterUrl.append(masterHost);
}
String slaveHost = slaveProperties.getProperty("HOST");
if (slaveHost != null) {
slaveUrl.append(slaveHost);
}
String masterDb = masterProperties.getProperty("DBNAME");
masterUrl.append("/");
if (masterDb != null) {
masterUrl.append(masterDb);
}
String slaveDb = slaveProperties.getProperty("DBNAME");
slaveUrl.append("/");
if (slaveDb != null) {
slaveUrl.append(slaveDb);
}

//从这里可以看出,笔者前文提出的猜想是正确的,每一个ReplicationDriver其实是两个Connection的代理,这两个
//Connection才是真正访问DB的connection。
masterConnection = (com.mysql.jdbc.Connection) driver.connect(masterUrl
.toString(), masterProperties);

if (slaveUrl.toString().contains("///")) {
if (logger.isDebugEnabled()) {
logger.debug(" ----- the salveUrl contains the '///', " +
"that means there is no slaver, make slavesConnection = masterConnection --");
}
slavesConnection = masterConnection;
} else {
slavesConnection = (com.mysql.jdbc.Connection) driver.connect(slaveUrl
.toString(), slaveProperties);
}

 主要就是加了一个判断,一旦路径中出现///,那么就证明没有slave机器,那么就可以把masterConnection赋值给slavesConnection了。这样一来就ok了。 


经过3个类的改写之后,终于,我们可以使用ReplicationDriver的功能了,看来看去还是这种方式最美好。

 

分享到:
评论

相关推荐

    MYSQL

    4.12.1 在 Win32 上安装 MySQL 4.12.2 在 Win95 /Win98上启动 MySQL 4.12.3 在 NT 上启动 MySQL 4.12.4 在 Win32 上运行 MySQL 4.12.5 用 SSH 从 Win32 连接一个远程MySQL 4.12.6 MySQL-Win...

    MySQL中文参考手册.chm

    MySQL中文参考手册.chm 449kb <br/>0 译者序 1 MySQL的一般的信息 1.1 什么是MySQL? 1.2 关于本手册 1.2.1 本手册中使用的约定 1.3 MySQL的历史 1.4 MySQL的主要特征 1.5...

    mysql5.5、mysql5.6、mysql5.7、mysql8 各种版本最新下载合集

    mysql5.5、mysql5.5、mysql5.7、mysql8 各种版本最新下载合集 MySQL是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提 高了灵活性。 MySQL...

    MySql 5.1 参考手册.chm

    1.7.3. MySQL论坛上的MySQL社区支持 1.8. MySQL标准的兼容性 1.8.1. MySQL遵从的标准是什么 1.8.2. 选择SQL模式 1.8.3. 在ANSI模式下运行MySQL 1.8.4. MySQL对标准SQL的扩展 1.8.5. MySQL与标准SQL的差别 1.8.6. ...

    新版 MySQL DBA 高级视频 基于MySQL 5.7 MySQL 8.0版本.rar

    ├─新版MySQL DBA 课件ppt │ 第一课数据库介绍篇.pdf │ 第七课MySQL数据库设计.pdf │ 第三十一课percona-toolkits 的实战及自动化.pdf │ 第三课MySQL授权认证.pdf │ 第九课MySQL字符集.pdf │ 第二十一课MySQL...

    mysql8.0升级版本

    MySQL 8.0 升级版本 在本文中,我们将详细介绍 MySQL 8.0 升级版本的步骤和注意事项。MySQL 是一个流行的开源关系数据库管理系统,广泛应用于各种Web 应用程序中。升级 MySQL 版本可以提高数据库性能、修复 bugs 和...

    C#连接MySQL需要的MySql.Data.dll,MySql.Web.dll

    在C#编程环境中,连接MySQL数据库通常依赖于特定的数据提供者,这就是MySql.Data.dll和MySql.Web.dll的角色。这两个动态链接库(DLL)文件是MySQL官方提供的.NET数据访问组件,使得C#开发者能够轻松地与MySQL服务器...

    mysql5.7.17资源安装包

    mysql5.7.17资源安装包。MySQL是一种流行的开源关系型数据库管理系统,由Oracle公司开发和维护。MySQL 5.7.17是MySQL的一个重要版本 MySQL 5.7.17增加了很多新功能和改进,其中比较重要的包括: JSON数据类型,以便...

    mysql57驱动jar包

    mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57驱动jar包mysql57...

    Mysql5.6、Mysql5.7 JDBC驱动

    MySQL是世界上最受欢迎的关系型数据库管理系统之一,而JDBC(Java Database Connectivity)是Java语言与数据库交互的标准接口。本文将深入探讨Mysql5.6和Mysql5.7的JDBC驱动,以及如何使用`mysql-connector-java-...

    Windows mysql8.0.36.0详细安装教程,附带MySQL安装包

    在本教程中,我们将深入探讨如何在Windows操作系统上详细安装MySQL 8.0.36.0。MySQL是一个广泛使用的开源关系型数据库管理系统(RDBMS),它在各种规模的企业和项目中扮演着核心角色。MySQL 8.0版本带来了许多性能...

    MySQL ODBC 5.1 Driver(Mysql驱动免安装版)

    MySQL ODBC 5.1 Driver 是一个用于连接MySQL数据库的开放数据库连接(ODBC)驱动程序,它允许用户通过标准的ODBC接口访问MySQL数据库,适用于Windows操作系统。这个版本是免安装的,意味着用户可以直接解压使用,...

    mysql驱动jar 文件适用MySQL5.7

    MySQL驱动jar文件是Java应用程序与MySQL数据库之间通信的关键组件,主要功能是提供Java Database Connectivity (JDBC) API,使得Java开发者能够通过编写Java代码来执行SQL语句,从而操作MySQL数据库。标题提到的...

    mysql5.1中文手册

    MySQL论坛上的MySQL社区支持 1.8. MySQL标准的兼容性 1.8.1. MySQL遵从的标准是什么 1.8.2. 选择SQL模式 1.8.3. 在ANSI模式下运行MySQL 1.8.4. MySQL对标准SQL的扩展 1.8.5. MySQL与标准SQL的...

    mysql 5.7 中文文档

    MySQL 5.7中文文档是针对这个特定数据库版本的详细技术参考,包含了丰富的信息,适合开发者、管理员和学习者使用。文档的翻译质量被描述为“还行”,尽管资源可能不易找到,但提供了全面的指南。 该文档涵盖了从...

    mysql数据库镜像安装包+教程(5.7版本)

    mysql5.7安装教程+mysql5.7镜像安装+mysql学习+mysql5.7镜像包 mysql5.7安装教程+mysql5.7镜像安装+mysql学习+mysql5.7镜像包 mysql5.7安装教程+mysql5.7镜像安装+mysql学习+mysql5.7镜像包 mysql5.7安装教程+mysql...

    mysql8、mysql5两个版本驱动

    MySQL是世界上最受欢迎的关系型数据库管理系统之一,其驱动程序是Java应用程序连接MySQL数据库的关键组件。本文将详细介绍MySQL 8和MySQL 5驱动的区别以及与JDK版本的兼容性问题。 首先,MySQL 8和5的主要区别在于...

    涂抹MySQL 跟着三思一步一步学MySQL

    《涂抹MySQL:跟着三思一步一步学MySQL》是一本旨在帮助初学者和中级用户深入理解MySQL数据库系统的教程。这本书通过清晰的解释和实例,逐步引导读者掌握MySQL的核心概念和技术。MySQL是一种广泛使用的开源关系型...

    mysql 5.5版 头文件(mysql.h等)

    例如,`mysql_init()`用于初始化一个MySQL连接结构,`mysql_real_connect()`用于建立实际的数据库连接,`mysql_query()`用于执行SQL查询,而`mysql_store_result()`和`mysql_use_result()`则用于处理查询结果。...

Global site tag (gtag.js) - Google Analytics