dubbo服务+spring动态数据源出错

1:问题描述,以及分析

        项目用了spring数据源动态切换,服务用的是dubbo。在运行一段时间后程序异常,更新操作没有切换到主库上。

这个问题在先调用读操作后再调用写操作会出现

经日志分析原因:

第一:当程序运行一段时间后调用duboo服务时..([DubboServerHandler-192.168.1.106:20880-thread-199] [DubboServerHandler-192.168.1.106:20880-thread-200]) dubbo服务默认最大200线程(超过200个线程以后服务不会创建新的线程了),读操作与写操作有可能会在一个线程里(读操作的事务propagation是supports,写是required),当这种情况出现时MethodBeforeAdvice.before先执行,DataSourceSwitcher.setSlave()被调用,然后DynamicDataSource.determineCurrentLookupKey(此方法调用contextHolder.get获取数据源的key)被调用,此时数据源指向从库也就是只读库。当读操作执行完成后,dubbo在同一个线程(thead-200)里执行更新的操作(比如以update,insert开头的服务方法),这时会先执行DynamicDataSource.determineCurrentLookupKey,指向的是读库,然后执行MethodBeforeAdvice.before,DataSourceSwitcher.setMaster()被调用,注意,这时DynamicDataSource.determineCurrentLookupKey不会被再次调用,所以这时数据源仍然指向读库,异常发生了。(写从库了)

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

2:application.xml配置

<bean id="parentDataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="initialSize" value="20"/>
<property name="maxTotal" value="50"/>
<property name="maxIdle" value="10"/>
<property name="testOnBorrow" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="defaultAutoCommit" value="false"/>
</bean>
<!-- 主数据源-->
<bean id="masterDataSource" parent="parentDataSource">
<property name="url" value="jdbc:mysql://192.168.60.45:13306/ac_vote?autoReconnect=true&amp;useSSL=false"/>
<property name="username" value="data"/>
<property name="password" value="acfundata"/>
</bean>
<!-- 从数据源-->
<bean id="slaveDataSource" parent="parentDataSource">
<property name="url" value="jdbc:mysql://192.168.60.45:23306/ac_vote?autoReconnect=true&amp;useSSL=false"/>
<property name="username" value="data"/>
<property name="password" value="acfundata"/>
</bean>
<!-- 配置自定义动态数据源-->
<bean id="dataSource" class="tv.acfun.service.common.database.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="slave" value-ref="slaveDataSource" />
<entry key="master" value-ref="masterDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>

<!--开启自动代理功能 true使用CGLIB -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 声明AOP 切换数据源通知 类中加@Component 自动扫描xml中不用配<bean>了
<bean id="dataSourceAdvice" class="tv.acfun.service.vote.aop.DataSourceAdvice" />
-->
<!-- 配置通知和切点 注意这个一定要配置在事务声明(txAdvice)之前 否则就会出现数据源切换出错  -->
<aop:config>
<aop:advisor
pointcut="execution(* tv.acfun.service.vote.manager.impl.*ManagerImpl.*(..))"
advice-ref="dataSourceAdvice" />
</aop:config>

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置事务的传播特性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 对增、删、改方法进行事务支持 -->
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<!-- 对查找方法进行只读事务 -->
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="query*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<!-- 对其它方法进行只读事务 -->
<!--<tx:method name="*" propagation="SUPPORTS" read-only="true" />-->
</tx:attributes>
</tx:advice>
<!--开启注解式事务扫描 要开启事务的service实现类中 加上@Transactional注解-->
<tx:annotation-driven/>
<!--未开启事务扫描时 需指定aop配置 声明那些类的哪些方法参与事务
<aop:config>
<aop:advisor
pointcut="execution(* tv.acfun.service.vote.manager..*Service.*(..))"
advice-ref="txAdvice" />
<aop:advisor
pointcut="execution(* tv.acfun.service.vote.manager..*ServiceImpl.*(..))"
advice-ref="txAdvice" />
</aop:config>
-->

 

3. DataSourceAdvice类


@Slf4j
@Aspect
@Component
public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {

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

// service方法执行完之后被调用
public void afterReturning(Object var1, Method var2, Object[] var3, Object var4) throws Throwable {
DataSourceSwitcher.setMaster(); // ***** 加上这句解决运行数据库切换问题
}

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

4. DataSourceSwitcher 类

public class DataSourceSwitcher {

private static final ThreadLocal contextHolder = new ThreadLocal();

private static final String DATA_SOURCE_SLAVE = "slave" ;

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( DATA_SOURCE_SLAVE);
}

public static String getDataSource() {
return (String) contextHolder.get();
}

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

5. DynamicDataSource 类

public class DynamicDataSource extends AbstractRoutingDataSource {

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

}

和 http://my.oschina.net/mrXhuangyang/blog/500743 这个遇到一样问题

你可能感兴趣的:(dubbo服务+spring动态数据源出错)