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:advisorpointcut="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@Componentpublic 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 {

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

}

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

另外看到一个文章讲

@Order(-1)
DataSourceAdvice前面加上一个Order注解 可以保证 数据源切换通知 在 事务通知前执行.

dubbo服务+Spring事务+AOP动态数据源切换 出错的更多相关文章

  1. Spring(AbstractRoutingDataSource)实现动态数据源切换--转载

    原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目 ...

  2. Spring(AbstractRoutingDataSource)实现动态数据源切换

    转自: http://blog.51cto.com/linhongyu/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直 ...

  3. spring AbstractRoutingDataSource实现动态数据源切换

    使用Spring 提供的 AbstractRoutingDataSource 实现 创建 AbstractRoutingDataSource 实现类,负责保存所有数据源与切换数据源策略:public ...

  4. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

  5. Spring动态切换多数据源事务开启后,动态数据源切换失效解决方案

    关于某操作中开启事务后,动态切换数据源机制失效的问题,暂时想到一个取巧的方法,在Spring声明式事务配置中,可对不改变数据库数据的方法采用不支持事务的配置,如下: 对单纯查询数据的操作设置为不支持事 ...

  6. Spring主从数据库的配置和动态数据源切换原理

    原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配 ...

  7. 【开发笔记】- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    AbstractRoutingDataSource动态数据源切换 上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求.一般的情况下我们都是使 ...

  8. AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u012881904/article/de ...

  9. 30个类手写Spring核心原理之动态数据源切换(8)

    本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...

随机推荐

  1. JavaScript Date(日期) 对象

    日期对象用于处理日期和时间. 如何使用 Date() 方法获得当日的日期. getFullYear()使用 getFullYear() 获取年份. getTime()getTime() 返回从 197 ...

  2. undefined与null的区别(待修整)

    没有实体的对象称为空对象.只用对象的引用,而不存在引用的实体对象 就叫做空对象 在常见的强类型语言中,通常有一个表示"空"的值,比如NULL.但是在Javascript中,空(或者 ...

  3. npm不能安装任何包,报错:npm WARN onload-script failed to require onload script npm-autoinit/autoinit及解决方法

    想要利用Hexo搭建一个博客,但是安装时npm一直报错,不仅仅是Hexo包,连别的其他包也不行,会提示下面的一堆错误 npm WARN onload-script failed to require ...

  4. MySQL的C++简单封装

    /* *介绍:MySQL的简单封装,支持流操作输入输出MySQL语句,然而并没有什么软用,大二学生自娱自乐,有不足求指点 *作者:MrEO *日期:2016.3.26 */ 头文件 my_sql.h ...

  5. SGU 185.Two shortest (最小费用最大流)

    时间限制:0.25s 空间限制:4M 题意: 在n(n<=400)个点的图中,找到并输出两条不想交的最短路.不存在输出“No sulotion”: Solution: 最小费用最大流 建图与po ...

  6. 搜索所有的路径-矩阵运算-暴力-ACM

    给定一个n*n整数矩阵,定义对I行的SHIFT操作( 0 <= i < n ),是将第I行所有元素都右移一位,最右边的移到最左边. 你可以对任意行进行任意次SHIFT操作,使得: max0 ...

  7. Redux1

    Redux 写在前面 写React也有段时间了,一直也是用Redux管理数据流,最近正好有时间分析下源码,一方面希望对Redux有一些理论上的认识:另一方面也学习下框架编程的思维方式. Redux如何 ...

  8. 香港house of hello品牌包包是怎样被模仿呢?

    今天,作为女性的朋友来说,house of hello包包可能对大家都不会陌生吧,特别是一些白领阶层的女性同胞们,这个品牌比较熟悉,现在,也有网友称,这个是恶搞包包.house of hello包包原 ...

  9. 设计模式——如何避免在OO设计中违反依赖倒置原则

    1 变量不可以包含具体类的引用.一旦new,就对具体类产生依赖,用工厂模式来避开. 2 类不要派生至具体类.用派生抽象类避开. 3 不要覆盖基类已经实现的方法.基类中已实现的方法应该由所有子类共享.

  10. check sql server edition (version 版本)

    SELECT @@VERSION refer : https://www.mssqltips.com/sqlservertip/1140/how-to-tell-what-sql-server-ver ...