`
ahuaxuan
  • 浏览: 639660 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Master-Slave,Spring,Hibernate,故事曲折离奇,情结跌宕起伏

阅读更多
/**
*作者:张荣华
*日期:2008-02-05
**/

前言,这篇文章写于08年12月份,现在发布出来望同行点评

------------------------------------------------------------

本文将要阐述或者讨论的是spring+hibernate和mysql的master-slave模式之间的一些不得不说的故事.

那么开始之前,按照惯例,我们要介绍一下这个两个东西
1,Hibernate,按照惯例,我们不介绍大家都知道的东西.

2, Master-Slave:Mysql的Master-Slave模式是比较常用的数据库lb模型,Master负责数据更新,Slave负责数据读取,在需要备用数据库以及读操作大于写操作的场景下尤其多见.相信熟悉的人一定不在少数.按照惯例,论坛上多次出现的东西,我们只作简要叙述.


在ahuaxuan之前的文章中,曾经多次提到mysql的master-slave,主要是为了解决在流行的ssh框架和master-slave可以无耦合的整合在一起的问题.我们的目的很简单,就是在无需改动我们的业务代码的前提下使用hibernate + master-slave.

之前我们讲到,使用jee中使用master-slave可以有几种方式,
1,mysql-proxy,和应用完全解耦
2,replicationdriver或者使用replication协议头
3,多数据源配置,使用多个jdbcTemplate或者多个hibernateTemplate等.

第一点对应用是最透明的,第二点对业务代码是透明的,第三点需要我们的持久层有较大的改动.同样他们也有各自的优缺点,mysql-proxy在写操作频繁的时候会有一些小问题(一个朋友的公司出现过),replicationdriver和其他driver不能共存,replication协议头比较好,不过和hibernate好像不是很谈得来,而多数据源配置是下下策,ahuaxuan向来讨厌这种做法,如果管理不当,很容易出问题(是指出问题得概率比第一点和第二点大).

以上三种方案对程序员得要求也是各不相同,mysql-proxy对程序员要求最高,replicationdriver或者协议头其次,多数据源最简单.

Ahuaxuan没有尝试过mysql-proxy(因为生产环境早已存在,并且配置好,运维不会让人随便动),不过倒确实有尝试过replicationdriver和replication协议头(它们的本质都是一样的,都是使用ReplicationConnection),根据测试,replicationdriver和replication协议头在使用jdbc或者ibatis是没有什么问题,不过和hibernate在一起得时候就有问题了,mysql服务器cpu使用率无故飙高到80%,应用cpu也上升很多.怕怕+惶惶.而且使用replicationdriver和replication还有一点点小缺点,那就是任何一个ReplicationConnection其实是两个connection(master-connection或者slave connection),哇,真是占着xxx不xxx,虽然只用一个,但是另外一个也不能让别人用,ReplicationConnection不能无耻到这个地步.这可是浪费数据库连接数的典范(其实也没啥,不就是多浪费几个连接嘛,小题大作).

下面我们首先来详细分析一下使用replicationdriver或者replication协议头时的内部细节.

以下是详细的概要步骤(这词儿用的!):
1,用户发起一个http请求,tomcat收到请求之后把从线程池中拿到一个线程,由这个请求线程来负责余下的流程(old io模型,new io模型在这个环节上稍微有点变化,但是接下来的不变)

2,请求线程执行到open session in view filter,拿到一个拿到一个session,通过Threadlocal绑定到该请求线程中.

3,请求线程执行到service,被事务拦截器拦截,在HibernateTransactionManager中, 同时拿到这个session所依赖的connection,而这个时候拿到的connection是数据库连接池的connection实现,也就是说这个connection是一个代理,该代理的target是ReplicationConnection.接着判断当前的事务的读写设置,如果是只读,那么调用ReplicationConnection#setReadonly方法把connection的readonlyflag设置为true.

4,线程在执行setReadonly方法的时候,其实是在调换ReplicationConnection中的currentConnection的引用所指向的对象,原来指向master-connection,如果设置为readonly,那么就重新指向slave-connection,不过事情没有这么简单,调换引用之前,需要把master-connection的状态同时赋值给slave-connection.一共有3个状态需要转移,一个是Catalog,还有一个是autocommit,还有一个是Isolation(注意这里,slave-connection拿到了master-connection的isolation,而它自己原来的isolation却没有保存下来).

5,当前线程退出setReadonly方法,继续在HibernateTransactionManager中游戈,这个时候,准备开始一个事务.

6, 当前线程经过拦截器的前半部分,进入我们的service(假设没有其他代理对象),开始执行我们的业务方法,包含持久化逻辑(查询操作),这里拿到的connection其实是slave-connection.

7, 请求线程退出service方法,回到拦截器的后半部分,这里有一个重要的方法,在提交事务之后,需要resetconnection,代码如下:
public static void resetConnectionAfterTransaction(Connection con, Integer previousIsolationLevel) {
		Assert.notNull(con, "No Connection specified");
		try {
			// Reset transaction isolation to previous value, if changed for the transaction.
			if (previousIsolationLevel != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Resetting isolation level of JDBC Connection [" +
							con + "] to " + previousIsolationLevel);
				}
				con.setTransactionIsolation(previousIsolationLevel.intValue());
			}

			// Reset read-only flag.
			if (con.isReadOnly()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");
				}
				con.setReadOnly(false);
			}
		}
		catch (Throwable ex) {
			logger.debug("Could not reset JDBC Connection after transaction", ex);
		}
	}

我们可以看到,spring是先把事务开始之前的master-connection 的isolationlevel设置回来,然后再改变ReplicationConnection的currentConnection,拿现在的情况来说,spring把slave-connection的isolation重新设置为事务开始之前的isolation(也就是原始的master-connection的isolation),但是问题是事务之前的isolation是设置在master-connection上的.接着,spring又调用setReadonly方法,把currentConnection引用又指向了master-connection(当然,在这之前还需要把slave-connection的状态复制过来),把slave-connection的isolation(同时还有catalog和autocommit)设置给master-connection.不过这个时候,slave-connection的isolation就变成了master-connnection的isolation了,这也许是有问题的,因为这两个connection在开始的时候,isolation有可能是不一样的,但是一次请求之后,它们的isolation级别就变成一样的了.所以这里的代码应该是先setReadOnly,然后再设置isolation.

8,请求线程再次拿到了master-connection,那么一旦以下的流程有延迟加载的情况发生,便会使用这个master-connection来执行查询操作(延迟加载难道应该用master-connection吗,显然不是,延迟加载应该用slave-connection,不过由于这里已经出了事务范围,所以ahuaxuan也没有办法来强制使用slave-connection进行延迟加载了).

9,退出open session in view  filter,从当前请求线程中清空这个session和connection,也就是取消connection和请求线程的绑定.关闭session,并建connection重新返回connection pool.


10,从请求线程中拿到返回数据,将请求线程返回线程池,并返回数据到客户端.

总结:使用replicationdriver和replication协议头时,基本上就是以上这个流程,我们可以看到,在上面这个流程中,Master-connection和slave-connection在被交替使用,他们的状态也在整个流程中有2次相互覆盖(而且假设master和slave隔离级别不一样,那么可能目前的spring代码可能会导致一次请求之后改变slave-connection的隔离级别)


由此看来,replicationdriver和replication协议头和spring+hibernate八字确实不太合啊,那只能另寻出路了,mysql-proxy由于政策原因被否决,那么只能在多数据源上下功夫了.


那么怎么分析呢,以下是a某人的自言自语:replicationdriver和replication协议头最大的优点是在驱动上做手脚通过代理connection来透明的选择访问master或者是slave,但是也正因为这个特点,导致hibernate无法一开始(在osiv中)就知道使用哪个connection,也导致了以后一系列的connection转换之类,.

那么如果有办法在osiv中就决定这次请求使用的connection,芑不是很帅气.这样说的话,那么要解决这个问题就是在osiv中确定connection了??,嗯,好像是这么回事,不过在osiv中确定connection好像有点难度啊,咋整啊,那换个思路,在osiv中确定datasource也行啊, 哦,有了.

想到这里,觉悟了,在osiv中确定多数据源的问题的本质就是hibernate+spring的多数据源问题啊.真是苦海无边,回头是岸呀.之前了解过spring2.0之后多了一个类叫作: AbstractRoutingDataSource.那么我们来看一下它的功能:
Abstract DataSource implementation that routes {@link #getConnection()} calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context.

这段注释告诉我们可以用一个ThreadLocal把一个key绑定到当前线程,然后通过这个key,可以获得当前线程需要的datasource.又是一个代理,ReplicationConnection是代理slave-connection和master-connection,而AbstractRoutingDataSource是代理master-datasource和slave-datasource.既然Juergen Hoeller大叔把标准的使用方法都告诉我们了,我们还有什么担心的呢,按照他老人家的谆谆教导,我们有了以下实现:
1,一个AbstractRoutingDataSource类,控制着应该使用哪个targetdatasource.
/**
 * 
 * @author ahuaxuan
 * @date 2008-6-7
 * @version $id$
 */
public class MasterSlaveRoutingDataSource extends AbstractRoutingDataSource{
	private static transient Log logger = LogFactory.getLog(MasterSlaveRoutingDataSource.class);
	
//DbType是一个标示符,代表datasource的key
	private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
	
	public static void setDbType(DbType type) {
		contextHolder.set(type);
	}

	public static DbType getDbType() {
		return contextHolder.get();
	}
	
	public static void clearCustomerType() {
		contextHolder.remove();
	}
	
	protected Object determineCurrentLookupKey() {
		
		Object o = contextHolder.get();
		if (logger.isDebugEnabled()) {
			logger.debug("------- The current data source is " + o);
		}
		return o != null ? o : DbType.Slave;
	}

	public boolean isWrapperFor(Class iface) throws SQLException {
		return false;
	}

	public Object unwrap(Class iface) throws SQLException {
		return null;
	}
}


2,一个filter,用来覆写原来的osiv的doFilterInternal方法.
/**
 * hiddenPostSearch field means if we use post method to do searching, use slave datasource
 * 
 * @author ahuaxuan
 * @date 2008-6-7
 * @version $id$
 */
public class MsOpenSessionInViewFilter extends OpenSessionInViewFilter{

	private static final String SLAVE_METHOD = "get";
	private static final String HIDDEN_FIELD_NAME = "hiddenPostSearch_001";
	
	protected void doFilterInternal(HttpServletRequest request, 
						HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		if (SLAVE_METHOD.equals(request.getMethod()) || 
				SLAVE_METHOD.equals(request.getParameter(HIDDEN_FIELD_NAME))) {
			
			MasterSlaveRoutingDataSource.setDbType(DbType.Slave);
		} else {
			MasterSlaveRoutingDataSource.setDbType(DbType.Master);
		}
		super.doFilterInternal(request, response, filterChain);
	}
}

3,在我们的spring配置文件上加上:
<bean id="dataSource" class="com.xx.MasterSlaveRoutingDataSource">  
     <property name="targetDataSources">  
        <map key-type="com.xx.DbType">  
           <entry key="Master" value-ref="writeDataSource"/>  
           <entry key="Slave" value-ref="readDataSource"/>  
        </map>  
     </property>  
     <property name="defaultTargetDataSource" ref="readDataSource"/>  
   </bean>  

那么这样一来,我们就可以通过http的method来判断该次请求是使用master还是slave.而且在将session,connection绑定到线程之前就确定了使用master还是slave的connection.

看到这里,地球人都明白了,这种方式和ReplicationConnection里通过readonly来判断使用master-connection还是slave-connection的原理真的是一摸一样啊.小样,别以为穿了个马甲我们就不认识你了.
AbstractRoutingDataSource类虽然很简单,但是却很有效,之前坛子上也有人写过多数据源问题,原理也是一样的,只不过我们当然会用spring自带的东东啦.

总结一下:
本文分为两部分内容,第一部分分析了spring+hibernate在使用opensessioninview的情况下使用replicationdriver或者replication协议头时候的大体流程与内部操作,第二部分分析了spring+hibernate+msyql的master-slave场景下,如何使应用尽可能完美透明的使用mysql的master-slave模式,绕了一圈之后发现动态切换数据源的方法还是比较好的方案,spring2.0之后的版本提供了一个AbstractRoutingDataSource类可以帮助我们快速便捷的实现这个特性.


注:由于ahuaxuan水平有限,理解难免有错误之处,还望不吝指出,不甚感激.
分享到:
评论
9 楼 ahuaxuan 2010-12-09  
blackhost 写道
对于楼主的这种方式,我有一个不小的疑问,盼望楼主解答!

这个多数据源的配置,取决于用户在MasterSlaveRoutingDataSource中时,根据Key获取是从master还是从Slave取Connection。

问题是,假如两个不同的用户,不同的Session,在取Connection钱,分别作了Master和Slave的请求,假如两个请求非常快。比如如下:

A-》Master-》getHiberateTemplate();
       b-》Slave->getHiberateTemplate();

在A实际获取Connection之前,B抢先改变了MasterSlaveRoutingDataSource的key值,则A就取到了Slave的Connnection,而实际上A期望得到的是Master

请问这个问题不会发生吗?

key是指绑定到thread中的那个key,两个请求是两个线程,他们不会相互影响的。
8 楼 blackhost 2010-09-21  
对于楼主的这种方式,我有一个不小的疑问,盼望楼主解答!

这个多数据源的配置,取决于用户在MasterSlaveRoutingDataSource中时,根据Key获取是从master还是从Slave取Connection。

问题是,假如两个不同的用户,不同的Session,在取Connection钱,分别作了Master和Slave的请求,假如两个请求非常快。比如如下:

A-》Master-》getHiberateTemplate();
       b-》Slave->getHiberateTemplate();

在A实际获取Connection之前,B抢先改变了MasterSlaveRoutingDataSource的key值,则A就取到了Slave的Connnection,而实际上A期望得到的是Master

请问这个问题不会发生吗?
7 楼 niyunjiu 2009-03-15  
ahuaxuan 写道

生产环境没有用mysql-proxy,现在运维估计也不敢去动生产环境,现在是master-slave模式,具体是访问master还是slave这个都是由程序控制的

谢谢
6 楼 ahuaxuan 2009-03-09  
生产环境没有用mysql-proxy,现在运维估计也不敢去动生产环境,现在是master-slave模式,具体是访问master还是slave这个都是由程序控制的
5 楼 niyunjiu 2009-03-09  
ahuaxuan 写道

我们没有用mysql-proxy,replicationdriver 是支持事务的,ameoba 我没有用过,不过对它我还是比较感兴趣的

谢谢!!我周末查证过,mysql-proxy是支持事务的,你们生产环境不是在用mysql-prxy吗?呵呵
4 楼 ahuaxuan 2009-03-06  
我们没有用mysql-proxy,replicationdriver 是支持事务的,ameoba 我没有用过,不过对它我还是比较感兴趣的
3 楼 niyunjiu 2009-03-06  
请问博主,
1、到目前为止,你们的mysql-proxy还稳定吗?
2、replicationdriver 支持事务吗?我们用mysql innodb引擎,所以要求支持事务。
ameoba类似于mysql-proxy,比mysql-proxy好,唯一的缺点就是不支持事务。

谢谢!
2 楼 ahuaxuan 2009-03-02  
进入事务得方法不一定会设置成非只读,以为有只读事务,也就是说事务还是有得,延迟加载得时候应该选择slave得datasource,我也是这样做得
1 楼 zhang_ly520 2009-03-02  
最近也在做这方便的研究,看了文章后受益良多。
因为对于spring底层了解的不多,我想知道要是在spring中配置了事务管理,拦截了所有需要事务的方法的话,哪么进入事务的方法是否都被设置成“非只读”呢?哪么其他的方法,比如“get”都不进入事务也就都是“只读”,哪么延迟加载的时候是不是应该使用slave的连接,否则我所有的方法可能都是通过master连接执行的。
不知道能看懂我说的不?

相关推荐

    Modbus-Master-Slave-for-Arduino-master.zip_Master/Slave_arduino

    标题中的"Modbus-Master-Slave-for-Arduino-master.zip_Master/Slave_arduino"指出这是一个关于Arduino平台的Modbus主从通信库。Modbus是一种广泛使用的工业通信协议,允许不同设备之间交换数据,尤其在自动化系统中...

    sharding-master-slave

    在`sharding-master-slave`项目中,我们将看到如何将`Sharding-JDBC`与`SpringBoot`相结合,以实现数据的高效管理和访问。 1. **分库分表策略** `Sharding-JDBC`支持自定义分片策略,这在`描述`中提到。通常,分片...

    数据库-Mongodb的master-slave模式与master-master模式实验.rar

    在分布式系统中,为了确保数据的高可用性和容错性,MongoDB提供了两种复制模式:master-slave(主从模式)和master-master(主主模式)。本实验将深入探讨这两种模式的工作原理、设置方法以及它们在实际应用中的优...

    activemq_master-slave集群安装文档

    ActiveMQ Master-Slave集群是一种高可用性和容错性的解决方案,确保即使主节点(Master Broker)发生故障,消息也不会丢失,因为它们已经被复制到从节点(Slave Broker)。这种配置是ActiveMQ推荐的策略之一,提供了...

    Master-Slave 的应用一

    在企业级开发中,主从关系(Master-Slave)是一种常见的架构模式,广泛应用于数据库复制、分布式系统、任务调度等多个领域。这种模式的核心在于,一个主节点(Master)负责处理请求、执行关键操作或决策制定,而一个...

    activemq-master-slave集群安装文档.doc

    在ActiveMQ中,主要有三种Master-Slave实现:Pure Master Slave、Shared File System Master Slave和JDBC Master Slave。这里主要讨论的是JDBC Master Slave模式,它依赖于数据库来确定哪个Broker是Master,哪个是...

    spring-master-slave-commondao

    标题“spring-master-slave-commondao”暗示我们正在讨论一个与Spring框架相关的项目,可能是用于数据库主从复制或分片的通用数据访问对象(DAO)实现。这个项目可能提供了一种方式来管理和操作在主库和从库之间的...

    第1步 master-slave1和slave2配置网络和搭建Hadoop集群环境.docx

    第1步 master-slave1和slave2配置网络和搭建Hadoop集群环境.docx

    activemq master-slave搭建的NFSV4文档

    ** activemq master-slave 架构与 NFSV4 的集成** 在企业级消息传递系统中,Apache ActiveMQ 是一个广泛使用的开源消息代理,它提供了可靠的消息传递服务。为了实现高可用性和容错性,ActiveMQ 支持主从(master-...

    Modbus-Master-Slave-for-Arduino-master_fierce2gz_arduinomodbus_M

    本篇文章将深入探讨如何在Arduino平台上实现Modbus主从通信,基于"Modbus-Master-Slave-for-Arduino-master"库,帮助开发者更好地理解和应用这一强大的工具。 一、Modbus协议简介 Modbus是一种公开的通信协议,由...

    Master-slave-alarm-system.zip_Master/Slave_上位机报警

    本文将深入探讨一个名为"Master-slave-alarm-system"的项目,该系统旨在通过主从结构实现对温度的实时检测,并具备上位机报警功能。我们将分析系统的架构、工作原理以及其核心组件,为理解此类系统提供详尽的解析。 ...

    modbus-master-slave模拟软件

    这个“modbus-master-slave模拟软件”就是专为测试和调试基于Modbus协议的系统而设计的工具。 首先,我们来理解一下Modbus协议的基础。Modbus协议是1979年由施耐德电气公司开发的一种串行通信协议,它定义了主设备...

    【源码】基于python+scrapy+redis实现主从式master-slave爬虫.zip

    【源码】基于python+scrapy+redis实现主从式master-slave爬虫.zip 【源码】基于python+scrapy+redis实现主从式master-slave爬虫.zip 【源码】基于python+scrapy+redis实现主从式master-slave爬虫.zip 【源码】基于...

    Master_slave_multi_machine.rar_Master-slave_Master/Slave_multi m

    标题“Master_slave_multi_machine.rar_Master-slave_Master/Slave_multi m”暗示了我们正在讨论的焦点是关于主从结构在多机器环境中的应用。 描述“主从多机通信系统问题研究建模及其实性研究”表明我们将深入探讨...

    Master-_-Slave-Core

    "Master-_-Slave-Core"标题暗示了这是一个关于I2C主从核心的项目,其中包含了一套用于实现I2C主从模式的代码。 I2C协议是由飞利浦(现NXP半导体)在1980年代初设计的,它允许在一个总线上连接多个设备,减少了所需...

    DeviceNet Master-Slave Module(Q系列)

    ### DeviceNet Master-Slave Module (Q系列) #### 概述 本文档旨在提供有关DeviceNet Master-Slave模块QJ71DN91的重要安全和操作指导。本产品为三菱可编程逻辑控制器(PLC)的一个扩展模块,用于实现DeviceNet网络...

    36 为多线程们安排一位经理—Master-Slave模式详解.pdf

    【Master-Slave模式详解】 在Java并发编程中,Master-Slave模式是一种常见的多线程处理策略,它借鉴了“分而治之”的思想,将一个大任务分解为多个小任务,分配给多个工作线程(Slave)执行,由一个主控线程...

    FreeModbus_Slave-Master-RTT-STM32-master_stm32mastermodbus_stm32

    《FreeModbus_Slave-Master-RTT-STM32-master_stm32mastermodbus_stm32:深入理解MODBUS通信在STM32中的应用》 MODBUS通信协议,作为工业自动化领域的标准通信协议,因其简单、开放、易实现的特点,在嵌入式系统...

    jenkins-jnlp-slave镜像

    jenkins-jnlp-slave镜像

    master-slave-i-o-keypad-lcd-master_arduino_

    【标题】"master-slave-i-o-keypad-lcd-master_arduino_" 涉及的是一个基于Arduino的主从式I/O、键盘和LCD显示的项目。这个项目的主要目的是通过Arduino来构建一个主从通信系统,其中主设备控制从设备,从设备接收...

Global site tag (gtag.js) - Google Analytics