`
阅读更多

前言: 

         本文转子(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. ...

    mysql5.7.20镜像安装包

    mysql5.7.20镜像安装包,该镜像为mysql数据库镜像,安装后可直接使用。 mysql5.7.20镜像安装包,该镜像为mysql数据库镜像,安装后可直接使用。 安装完成后,我们可以使用以下命令来运行 mysql 容器: $ docker run ...

    新版 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 和...

    MySQL5.7docker离线包

    Docker 离线安装 MySQL 5.7 使用说明 本文将指导你如何使用离线的 Docker 镜像包 (docker-mysql-5.7.tar.zip) 来在 Linux 系统中安装并运行 MySQL 5.7 容器。 前提条件 • 已安装 Docker 环境。 • MySQL 5.7 ...

    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官方中文参考手册

    MySQL 5.1参考手册 目录 前言 1. 一般信息 1.1. 关于本手册 1.2. 本手册采用的惯例 1.3. MySQL AB概述 1.4. MySQL数据库管理系统概述 1.4.1. MySQL的历史 1.4.2. MySQL的的主要特性 1.4.3. MySQL稳定性 1.4.4. ...

    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...

Global site tag (gtag.js) - Google Analytics