http://blog.jobbole.com/103496/

为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如何做到把数据写到master库,而读取数据的时候,从slave库读取。如果应用程序判断失误,把数据写入到slave库,会给系统造成致命的打击。

解决读写分离的方案很多,常用的有SQL解析、动态设置数据源。SQL解析主要是通过分析sql语句是insert/select/update/delete中的哪一种,从而对应选择主从。而动态设置数据源,则是通过拦截方法名称的方式来决定主从的,例如:save*(),insert*() 形式的方法使用master库,select()开头的,使用slave库。蛮多公司会使用在方法上标上自定义的@Master、@Slave之类的标签来选择主从,也有公司直接就调用setxxMaster,setxxSlave之类的代码进行主从选择。

下面我主要介绍一下基于Spring AOP动态设置数据源这种方式。注意这篇文章是基于自己项目的实际情况的,不是通用的方案,请知晓。

原理图

Spring AOP的切面主要的职责是拦截Mybatis的Mapper接口,通过判断Mapper接口中的方法名称来决定主从。

Spring AOP 切面配置

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<aop:config expose-proxy="true">  
  
<aop:pointcut id="txPointcut" expression="execution(* com.test..persistence..*.*(..))" />  
  
<aop:aspect ref="readWriteInterceptor" order="1">  
  
<aop:around pointcut-ref="txPointcut" method="readOrWriteDB"/>  
  
</aop:aspect>  
  
</aop:config>  
  
   
  
<bean id="readWriteInterceptor" class="com.test.ReadWriteInterceptor">  
  
   <property name="readMethodList">  
  
     <list>  
  
       <value>query*</value>  
  
       <value>use*</value>  
  
       <value>get*</value>  
  
       <value>count*</value>  
  
       <value>find*</value>  
  
       <value>list*</value>  
  
       <value>search*</value>  
  
    </list>  
  
  </property>  
  
<property name="writeMethodList">  
  
    <list>  
  
        <value>save*</value>  
  
        <value>add*</value>  
  
        <value>create*</value>  
  
        <value>insert*</value>  
  
        <value>update*</value>  
  
        <value>merge*</value>  
  
        <value>del*</value>  
  
        <value>remove*</value>  
  
        <value>put*</value>  
  
        <value>write*</value>  
  
   </list>  
  
</property>  
  
</bean>

把所有Mybatis接口类都放置在persistence下。配置的切面类是ReadWriteInterceptor。这样当Mapper接口的方法被调用时,会先调用这个切面类的readOrWriteDB方法。在这里需要注意<aop:aspect>中的order=“1” 配置,主要是为了解决切面于切面之间的优先级问题,因为整个系统中不太可能只有一个切面类。

Spring AOP 切面类实现

 
 
 
 
 

Java

 
1
2
3
4
5
public class ReadWriteInterceptor {  
   private static final String DB_SERVICE = "dbService";  
   private List<String> readMethodList = new ArrayList<String>();  
   private List<String> writeMethodList = new ArrayList<String>(); 
}

  •  
     
     
     
     
     

    Java

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public Object readOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {  
            String methodName = pjp.getSignature().getName();  
            if (isChooseReadDB(methodName)) {  
                //选择slave数据源  
            } else if (isChooseWriteDB(methodName)) {  
               //选择master数据源  
            } else {  
              //选择master数据源  
            }  
           return pjp.proceed();  
    }  
      
     private boolean isChooseWriteDB(String methodName) {  
         for (String mappedName : this.writeMethodList) {  
             if (isMatch(methodName, mappedName)) {  
                 return true;  
             }  
         }  
        return false;  
    }  
      
     private boolean isChooseReadDB(String methodName) {  
        for (String mappedName : this.readMethodList) {  
           if (isMatch(methodName, mappedName)) {  
               return true;  
           }  
        }  
        return false;  
    }  
      
     private boolean isMatch(String methodName, String mappedName) {  
        return PatternMatchUtils.simpleMatch(mappedName, methodName);  
    }  
      
     public List<String> getReadMethodList() {  
        return readMethodList;  
     }  
      
     public void setReadMethodList(List<String> readMethodList) {  
       this.readMethodList = readMethodList;  
    }  
      
     public List<String> getWriteMethodList() {  
        return writeMethodList;  
     }  
      
     public void setWriteMethodList(List<String> writeMethodList) {  
        this.writeMethodList = writeMethodList;  
    }

    覆盖DynamicDataSource类中的getConnection方法

    ReadWriteInterceptor中的readOrWriteDB方法只是决定选择主还是从,我们还必须覆盖数据源的getConnection方法,以便获取正确的connection。一般来说,是一主多从,即一个master库,多个slave库的,所以还得解决多个slave库之间负载均衡、故障转移以及失败重连接等问题。

    1、负载均衡问题,slave不多,系统并发读不高的话,直接使用随机数访问也是可以的。就是根据slave的台数,然后产生随机数,随机的访问slave。

    2、故障转移,如果发现connection获取不到了,则把它从slave列表中移除,等其回复后,再加入到slave列表中

    3、失败重连,第一次连接失败后,可以多尝试几次,如尝试10次。

    处理业务方法中的@Transactional注解

    我参与的这个项目,大部分业务代码是不需要事务的,只有极个别情况需要。那么按照上面提到的方案,如果不对业务方法中@Transactional注解进行特殊处理的话,主从的选择会出现问题。大家都知道,如果使用了Spring的事务,那么在同一个业务方法内,只会调用一次数据源的getConnection方法,如果该业务方法内,调用的mapper接口刚好以select开头的,就会选择slave库,那么接下来调用以insert开头的mapper接口方法时,会把数据写入到slave库。如何解决这个问题呢?必须在进入标有@Transactional注解的业务方法前,指定选择master主库。可以通过覆盖DataSourceTransactionManager类中的doBegin方法,如下:

     
     
     
     
     
     

    Java

     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyTransactionManager extendsDataSourceTransactionManager{  
      
    @Override  
      
    protected void doBegin(Object transaction, TransactionDefinitiondefinition) {  
      
    //选择master数据库  
      
    super.doBegin(transaction, definition);  
      
    }  
      
    }

    这样既可以避免,把数据写入到从库的问题。

    总结

    本人的解决方案是基于项目实际的,不一定合适你,我只是展示了解决方案而已。当然你可以选择开源的框架,像阿里的Cobar,360的Atlas。

使用Spring AOP切面解决数据库读写分离的更多相关文章

  1. 170301、使用Spring AOP实现MySQL数据库读写分离案例分析

    使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...

  2. 161220、使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  3. 使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  4. 在应用层通过spring特性解决数据库读写分离

    如何配置mysql数据库的主从? 单机配置mysql主从:http://my.oschina.net/god/blog/496 常见的解决数据库读写分离有两种方案 1.应用层 http://neore ...

  5. Spring + Mybatis项目实现数据库读写分离

    主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...

  6. 使用Spring AOP实现MySql的读写分离

    转自:http://blog.csdn.net/xlgen157387/article/details/53930382 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解 ...

  7. spring结合mybatis实现数据库读写分离

    随着系统用户访问量的不断增加,数据库的频繁访问将成为我们系统的一大瓶颈之一.由于项目前期用户量不大,我们实现单一的数据库就能完成.但是后期单一的数据库根本无法支撑庞大的项目去访问数据库,那么如何解决这 ...

  8. Spring aop应用之实现数据库读写分离

    Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ...

  9. Spring AOP 实现数据库读写分离

    背景 我们一般应用对数据库而言都是"读多写少",也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案, 其中一个是主库,负责写入数据,我们称之为:写库: 其它都 ...

随机推荐

  1. 怎么在notepad里面,将字符串替换成换行

    用Notepad++可以,利用查找和替换功能,选择正则表达式,查找目标框里输入你想要替换的字符串,替换为框里输入\r,点击替换即可.这是个 很强大的功能,利用还它可以批量替换任何字符串,比如你链接是& ...

  2. eclipse中更改配置使得switch语句不出错

    分别点击: windows---preference--->java---->compiler--->error/waring---->potential programmin ...

  3. UVaLive 7457 Discrete Logarithm Problem (暴力)

    题意:求一个x使得 a^x%p = b p为素数: 析:从1开始扫一下就好,扫到p-1就可以了,关键是这个题为什么要用文件尾结束,明明说是0,但是不写就WA... 代码如下: #pragma comm ...

  4. $.ajax与$.post、$.get的一点区别

    后台代码: [HttpPost] public string DoLogin(string username,string password) { return "success" ...

  5. 网易蜂巢(云计算基础服务)MongoDB服务重磅来袭

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. MongoDB是目前最为流行的NoSQL数据库,在2017年1月新鲜出炉的数据库权威排行榜上,MongoDB ...

  6. 洛谷 - P2887 - 防晒霜Sunscreen - 贪心

    https://www.luogu.org/problemnew/show/P2887 感觉可以: 把防晒霜拆点限制流量为瓶数,奶牛拆点限制流量为1,当某个防晒霜与奶牛匹配时连一条边,求最大流.但是这 ...

  7. poj2385【基础DP】

    挑战DP 题意: 有两棵树,给每分钟有苹果掉落的苹果树,一开始B在 1 树下,B最多只能移动W步,求一个最大数量拿到的. 思路: 一开始想的是,我对于每分钟情况来说无非是等于之前的在本位置+1,或者等 ...

  8. hdoj1007【几何】【未完待续】

    题意: 在一个平面上有n(1e5)个点,然后求一个圆来包住这些点,求这个圆的最小半径. 思考: 要使一个圆直接包了这些点,没有任何思路..

  9. TNS-12508 When Issuing Any SET Command For The Listene

    TNS-12508 When Issuing Any SET Command For The Listener fact: Oracle Net Services    fact: TNS Liste ...

  10. 500 Keyboard Row 键盘行

    给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词. 详见:https://leetcode.com/problems/keyboard-row/description/ C++: cl ...