使用Spring AOP切面解決數據庫讀寫分離

爲了減輕數據庫的壓力,通常會使用數據庫主從(master/slave)的方式,可是這種方式會給應用程序帶來必定的麻煩,好比說,應用程序如何作到把數據寫到master庫,而讀取數據的時候,從slave庫讀取。若是應用程序判斷失誤,把數據寫入到slave庫,會給系統形成致命的打擊。sql

解決讀寫分離的方案不少,經常使用的有SQL解析、動態設置數據源。SQL解析主要是經過分析sql語句是insert/select/update/delete中的哪種,從而對應選擇主從。而動態設置數據源,則是經過攔截方法名稱的方式來決定主從的,例如:save*(),insert*() 形式的方法使用master庫,select()開頭的,使用slave庫。蠻多公司會使用在方法上標上自定義的@Master、@Slave之類的標籤來選擇主從,也有公司直接就調用setxxMaster,setxxSlave之類的代碼進行主從選擇。數據庫

下面我主要介紹一下基於Spring AOP動態設置數據源這種方式。注意這篇文章是基於本身項目的實際狀況的,不是通用的方案,請知曉。express

Spring AOP的切面主要的職責是攔截Mybatis的Mapper接口,經過判斷Mapper接口中的方法名稱來決定主從。併發

Spring AOP 切面配置

 

 

1app

2負載均衡

3框架

4ide

5this

6spa

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庫之間負載均衡、故障轉移以及失敗重鏈接等問題。

    一、負載均衡問題,slave很少,系統併發讀不高的話,直接使用隨機數訪問也是能夠的。就是根據slave的臺數,而後產生隨機數,隨機的訪問slave。

    二、故障轉移,若是發現connection獲取不到了,則把它從slave列表中移除,等其回覆後,再加入到slave列表中

    三、失敗重連,第一次鏈接失敗後,能夠多嘗試幾回,如嘗試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。

相關文章
相關標籤/搜索