使用java Spring实现读写分离( MySQL实现主从复制)

我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案,

其中一个是主库,负责写入数据,我们称之为:写库;

其它都是从库,负责读取数据,我们称之为:读库;

 

那么,对我们的要求是:

1、读库和写库的数据一致;

2、写数据必须写到写库;

3、读数据必须到读库;

2.  方案

解决读写分离的方案有两种:应用层解决和中间件解决。

 

2.1. 应用层解决:

 

优点:

1、多数据源切换方便,由程序自动完成;

2、不需要引入中间件;

3、理论上支持任何数据库;

缺点:

1、由程序员完成,运维参与不到;

2、不能做到动态增加数据源;

 

2.2. 中间件解决

 

优缺点:

 

优点:

1、源程序不需要做任何改动就可以实现读写分离;

2、动态添加数据源不需要重启程序;

 

缺点:

1、程序依赖于中间件,会导致切换数据库变得困难;

2、由中间件做了中转代理,性能有所下降;

 

相关中间件产品使用:

MySQL-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07

Amoeba for mysqlhttp://www.iteye.com/topic/188598http://www.iteye.com/topic/1113437

3.  使用Spring基于应用层实现

3.1. 原理

 

在进入Service之前,使用AOP来做出判断,是使用写库还是读库,判断依据可以根据方法名判断,比如说以query、find、get等开头的就走读库,其他的走写库。

3.2. DynamicDataSource

[java]  view plain  copy
  1. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
  2.   
  3. /** 
  4.  * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可 
  5.  *  
  6.  * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。 
  7.  *  
  8.  * @author zhijun 
  9.  * 
  10.  */  
  11. public class DynamicDataSource extends AbstractRoutingDataSource{  
  12.   
  13.     @Override  
  14.     protected Object determineCurrentLookupKey() {  
  15.         // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key  
  16.         return DynamicDataSourceHolder.getDataSourceKey();  
  17.     }  
  18.   
  19. }  

3.3. DynamicDataSourceHolder

[java]  view plain  copy
  1. <pre name="code" class="java">/** 
  2.  *  
  3.  * 使用ThreadLocal技术来记录当前线程中的数据源的key 
  4.  *  
  5.  * @author zhijun 
  6.  * 
  7.  */  
  8. public class DynamicDataSourceHolder {  
  9.       
  10.     //写库对应的数据源key  
  11.     private static final String MASTER = "master";  
  12.   
  13.     //读库对应的数据源key  
  14.     private static final String SLAVE = "slave";  
  15.       
  16.     //使用ThreadLocal记录当前线程的数据源key  
  17.     private static final ThreadLocal<String> holder = new ThreadLocal<String>();  
  18.   
  19.     /** 
  20.      * 设置数据源key 
  21.      * @param key 
  22.      */  
  23.     public static void putDataSourceKey(String key) {  
  24.         holder.set(key);  
  25.     }  
  26.   
  27.     /** 
  28.      * 获取数据源key 
  29.      * @return 
  30.      */  
  31.     public static String getDataSourceKey() {  
  32.         return holder.get();  
  33.     }  
  34.       
  35.     /** 
  36.      * 标记写库 
  37.      */  
  38.     public static void markMaster(){  
  39.         putDataSourceKey(MASTER);  
  40.     }  
  41.       
  42.     /** 
  43.      * 标记读库 
  44.      */  
  45.     public static void markSlave(){  
  46.         putDataSourceKey(SLAVE);  
  47.     }  
  48.   
  49. }  


 
 

3.4. DataSourceAspect

[java]  view plain  copy
  1. import org.apache.commons.lang3.StringUtils;  
  2. import org.aspectj.lang.JoinPoint;  
  3.   
  4. /** 
  5.  * 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库 
  6.  *  
  7.  * @author zhijun 
  8.  * 
  9.  */  
  10. public class DataSourceAspect {  
  11.   
  12.     /** 
  13.      * 在进入Service方法之前执行 
  14.      *  
  15.      * @param point 切面对象 
  16.      */  
  17.     public void before(JoinPoint point) {  
  18.         // 获取到当前执行的方法名  
  19.         String methodName = point.getSignature().getName();  
  20.         if (isSlave(methodName)) {  
  21.             // 标记为读库  
  22.             DynamicDataSourceHolder.markSlave();  
  23.         } else {  
  24.             // 标记为写库  
  25.             DynamicDataSourceHolder.markMaster();  
  26.         }  
  27.     }  
  28.   
  29.     /** 
  30.      * 判断是否为读库 
  31.      *  
  32.      * @param methodName 
  33.      * @return 
  34.      */  
  35.     private Boolean isSlave(String methodName) {  
  36.         // 方法名以query、find、get开头的方法名走从库  
  37.         return StringUtils.startsWithAny(methodName, "query""find""get");  
  38.     }  
  39.   
  40. }  


 

3.5. 配置2个数据源

3.5.1.  jdbc.properties

[java]  view plain  copy
  1. jdbc.master.driver=com.mysql.jdbc.Driver  
  2. jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true  
  3. jdbc.master.username=root  
  4. jdbc.master.password=123456  
  5.   
  6.   
  7. jdbc.slave01.driver=com.mysql.jdbc.Driver  
  8. jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true  
  9. jdbc.slave01.username=root  
  10. jdbc.slave01.password=123456  

3.5.2.  定义连接池

  

[html]  view plain  copy
  1. <!-- 配置连接池 -->  
  2. <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.master.driver}" />
    <property name="url" value="${jdbc.master.url}" />
    <property name="username" value="${jdbc.master.username}" />
    <property name="password" value="${jdbc.master.password}" />
    <property name="maxActive" value="30" />
    <property name="maxIdle" value="5" />
    </bean>


    <bean id="slave01DataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.slave01.driver}" />
    <property name="url" value="${jdbc.slave01.url}" />
    <property name="username" value="${jdbc.slave01.username}" />
    <property name="password" value="${jdbc.slave01.password}" />
    <property name="maxActive" value="30" />
    <property name="maxIdle" value="5" />
    </bean>

3.5.3.  定义DataSource

    

[html]  view plain  copy
  1. <!-- 定义数据源,使用自己实现的数据源 -->  
  2.     <bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">  
  3.         <!-- 设置多个数据源 -->  
  4.         <property name="targetDataSources">  
  5.             <map key-type="java.lang.String">  
  6.                 <!-- 这个key需要和程序中的key一致 -->  
  7.                 <entry key="master" value-ref="masterDataSource"/>  
  8.                 <entry key="slave" value-ref="slave01DataSource"/>  
  9.             </map>  
  10.         </property>  
  11.         <!-- 设置默认的数据源,这里默认走写库 -->  
  12.         <property name="defaultTargetDataSource" ref="masterDataSource"/>  
  13.     </bean>  

3.6. 配置事务管理以及动态切换数据源切面

3.6.1.  定义事务管理器

 

[html]  view plain  copy
  1. <!-- 定义事务管理器 -->  
  2.     <bean id="transactionManager"  
  3.         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  4.         <property name="dataSource" ref="dataSource" />  
  5.     </bean>  

3.6.2.  定义事务策略

   

[html]  view plain  copy
  1. <!-- 定义事务策略 -->  
  2.     <tx:advice id="txAdvice" transaction-manager="transactionManager">  
  3.         <tx:attributes>  
  4.             <!--定义查询方法都是只读的 -->  
  5.             <tx:method name="query*" read-only="true" />  
  6.             <tx:method name="find*" read-only="true" />  
  7.             <tx:method name="get*" read-only="true" />  
  8.   
  9.             <!-- 主库执行操作,事务传播行为定义为默认行为 -->  
  10.             <tx:method name="save*" propagation="REQUIRED" />  
  11.             <tx:method name="update*" propagation="REQUIRED" />  
  12.             <tx:method name="delete*" propagation="REQUIRED" />  
  13.   
  14.             <!--其他方法使用默认事务策略 -->  
  15.             <tx:method name="*" />  
  16.         </tx:attributes>  
  17.     </tx:advice>  

3.6.3.  定义切面

  

[html]  view plain  copy
  1. <!-- 定义AOP切面处理器 -->  
  2.     <bean class="cn.itcast.usermanage.spring.DataSourceAspect" id="dataSourceAspect" />  
  3.   
  4.     <aop:config>  
  5.         <!-- 定义切面,所有的service的所有方法 -->  
  6.         <aop:pointcut id="txPointcut" expression="execution(* xx.xxx.xxxxxxx.service.*.*(..))" />  
  7.         <!-- 应用事务策略到Service切面 -->  
  8.         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>  
  9.           
  10.         <!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->  
  11.         <aop:aspect ref="dataSourceAspect" order="-9999">  
  12.             <aop:before method="before" pointcut-ref="txPointcut" />  
  13.         </aop:aspect>  
  14.     </aop:config>  

猜你喜欢

转载自blog.csdn.net/huxiangen/article/details/81066406
今日推荐