spring 数据源动态切换 与dubbo服务

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1:问题描述,以及分析

       项目用了spring数据源动态切换,服务用的是dubbo。在运行一段时间后程序异常,更新操作没有切换到主库上。这个问题在先调用读操作后再调用写操作会出现。经日志分析原因: 第一:当程序运行一段时间后调用duboo服务时,读操作与写操作有可能会在一个线程里(读操作的事务propagation是supports,写是required),当这种情况出现时MethodBeforeAdvice.before先执行,DataSourceSwitcher.setSlave()被调用,然后DynamicDataSource.determineCurrentLookupKey(此方法调用contextHolder.get获取数据源的key)被调用,此时数据源指向从库也就是只读库。当读操作执行完成后,dubbo可能在同一个线程里执行更新的操作(比如以update,insert开头的服务方法),这时会先执行DynamicDataSource.determineCurrentLookupKey,指向的是读库,然后执行MethodBeforeAdvice.before,DataSourceSwitcher.setMaster()被调用,注意,这时DynamicDataSource.determineCurrentLookupKey不会被再次调用,所以这时数据源仍然指向读库,异常发生了。

      DynamicDataSource.determineCurrentLookupKey 与DataSourceSwitcher.setXXX()方法的执行顺序是导致问题的关键,这个跟事务的advice与动态设置数据源的advice执行顺序有关吧

2:数据源配置



 
  
  
  
  
  
  
  
  
  
  
 
           
  
  
 


  
   
    
   

  

  
 

public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
 
 // service方法执行之前被调用
 public void before(Method method, Object[] args, Object target) throws Throwable {
  System.out.println("切入点: " + target.getClass().getName() + "类中" + method.getName() + "方法");
  if (method.getName().startsWith("add") || method.getName().startsWith("create")
    || method.getName().startsWith("save") || method.getName().startsWith("edit")
    || method.getName().startsWith("update") || method.getName().startsWith("delete")
    || method.getName().startsWith("remove")) {
   System.out.println("切换到: master");
   DataSourceSwitcher.setMaster();
  } else {
   System.out.println("切换到: slave");
   DataSourceSwitcher.setSlave();
  }
 }

 // service方法执行完之后被调用
 public void afterReturning(Object arg0, Method method, Object[] args, Object target) throws Throwable {

         DataSourceSwitcher.setMaster(); // *****  加上这句解决运行数据库切换问题
 }

 // 抛出Exception之后被调用
 public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
  DataSourceSwitcher.setSlave();
  System.out.println("出现异常,切换到: slave");
 }

}

 

public class DataSourceSwitcher {
 @SuppressWarnings("rawtypes")
 private static final ThreadLocal contextHolder = new ThreadLocal();

 @SuppressWarnings("unchecked")
 public static void setDataSource(String dataSource) {
  Assert.notNull(dataSource, "dataSource cannot be null");
  contextHolder.set(dataSource);
 }

 public static void setMaster(){
  clearDataSource();
    }
 
 public static void setSlave() {
  setDataSource("slave");
 }
 
 public static String getDataSource() {
  return (String) contextHolder.get();
 }

 public static void clearDataSource() {
  contextHolder.remove();
 }
}

package com.flzc.base.aop;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

----- 问题的关键在这个方法什么时候会调用

 @Override
 protected Object determineCurrentLookupKey() {
  return DataSourceSwitcher.getDataSource();
 }

}

转载于:https://my.oschina.net/mrXhuangyang/blog/500743

你可能感兴趣的:(spring 数据源动态切换 与dubbo服务)