1. 程式人生 > 程式設計 >AOP實現mysql的主從資料庫:讀寫分離

AOP實現mysql的主從資料庫:讀寫分離

1.問題

首先,為什麼會碰到這樣的問題?

昨天寫的一個業務上線了,但是在dev環境和test環境都能跑,但是到了線上環境發生資料不能插入的問題。
問了老大之後發現線上資料庫是讀寫分離的,然後通過過濾器的才能進入寫資料庫卡,我的函式命名規範問題不符合過濾器的要求,導致從controller不能進入邏輯函式。

主要原因

  • 線上資料庫是主從分離(即讀寫分離,寫資料的情況下連線主庫,讀資料的時候連線從庫)
  • 而為什麼跟函式命名規範相關?
  • 程式碼中實現資料庫讀寫分離是通過AOP的攔截器在方法開始執行前後插入我們想要的程式碼來實現動態切換資料來源的功能
  • 為什麼要用AOP來實現資料來源的切換?為什麼要讀寫分離?AOP是如何實現讀寫分離的?

2.什麼是AOP

2.1 AOP定義

AOP目標是把業務的共性問題提取出來集中放到一個統一的地方管理和控制。

2.2 應用場景

  • 引數驗證或者判空等公共方法
  • 異常處理
  • 事務管理
  • 快取
  • 熱修復:比如程式碼上線之後,有小改動,可以發起一個熱修。即把有bug的方法替換成我們修復之後的方法

2.3 AOP織入方法

不同方法在java的完整週期(類載入期間,編譯期,執行期間等)中不同的時間段插入切面的方式不同(即我們想要他在這個時間完成的方法,這裡建議先了解AOP的5種增強型別)。

  • 動態織入Hook方式:比靜態織入方式靈活,在執行期間,目標類載入之後,為介面動態生成代理類,將切面植入到代理類中。 常見的有:Dexposed,Xposed,epic(在native層修改java method對應的native指標)
  • 動態位元組碼生成:cglib+DexMaker;
Cglib 是一個強大的,高效能的 Code 生成類庫。
原理是在執行期間目標位元組碼載入後,通過位元組碼技術為一個類建立子類,
並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。
由於是通過子類來代理父類,因此不能代理被 final 欄位修飾的方法。
複製程式碼
  • 靜態織入方式 :編譯期間 APT AspectJ Javassist

在java編譯期間有不同的織入方法

2.3 AspectJ

自動代理 基於註解的方式 或 xml方式 本專案是使用Spring+AspectJ:基於xml:aop:config的方式實現AOP織入。

2.4 Spring aop

基於代理(jdk動態代理、cglib動態代理)實現的aop Spring aop使用了兩種代理機制。一種是jdk動態代理,另一種是cglib動態代理。 Jdk動態代理只支援介面代理,cglib支援類的代理。
下面這張退很好的總結了AOP的知識點。本人可能總結的不對,有問題請評論提醒。

參考:juejin.im/post/5c0153…

3.讀寫分離

3.1 為什麼要master-slave

1.將讀操作和寫操作分離到不同的資料庫上,避免主伺服器出現效能瓶頸;
2.主伺服器進行寫操作時,不影響查詢應用伺服器的查詢效能,降低阻塞,提高併發;
3.資料擁有多個容災副本,提高資料安全性。
4.同時當主伺服器故障時,可立即切換到其他伺服器,提高系統可用性;
複製程式碼

3.2讀寫分離能解決什麼問題

  • 1.高可用:master宕機,立即切換到slave機器上;
  • 2.負載均衡:讀寫分離也算是負載均衡的一種,主要指多臺從資料庫(slave)與主資料庫(master)之間的資料均衡;
  • 3.資料備份:一般會寫定時任務備份到不同的slave,保證資料安全;
  • 4.業務模組化:一個業務模組讀取一個slave,這個可以針對不同的業務模組對不同的資料庫進行處理(比如索引的建立以及儲存引擎的選擇);
  • 5.擴充套件性:scale-up:主要指伺服器效能擴充套件;scale-out:主要指增加伺服器的數量;
1、冷備份(定時全量/增量備份)
2、熱備份(在主從架構上實現)
3、主從架構(N主M從)
4、讀寫分離(在主從架構上實現)
5、資料分片(分庫分表(垂直分庫 水平分表))
複製程式碼

參考:www.jianshu.com/p/b7834c990…

3.3 讀寫分離的缺點

  • 成本增加
  • 資料延遲
  • 寫資料庫壓力較大

3.4 主從複製方式

  • 基於日誌 binlog
  • 基於GTID 全域性事務識別符號
1、Master將資料改變記錄到二進位制日誌(binary log)中,也就是配置檔案log-bin指定的檔案,這些記錄叫做二進位制日誌事件(binary log events) 
2、Slave通過I/O執行緒讀取Master中的binary log events並寫入到它的中繼日誌(relay log) 
3、Slave重做中繼日誌中的事件,把中繼日誌中的事件資訊一條一條的在本地執行一次,完成資料在本地的儲存,從而實現將改變反映到它自己的資料(資料重放)
複製程式碼

4.專案中AOP是如何實現主從讀寫分離的?

1.資料來源配置spring_mybatis.xml

data資料來源列表:master 寫庫 slave 讀庫 
資料來源DynamicDataSource類需要通過繼承AbstractRoutingDataSource,class位於com.A.B.DynamicDataSource
  <bean id="dynamicDataSource" class="com.A.B.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="master" value-ref="masterDataSource" />
                <entry key="slave" value-ref="slaveDataSource" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource" />
    </bean>


配置dataAOP 動態設定資料來源,class位於com.T.A.dsadvice.DataSourceAdvice
    <bean id="dataSourceAdvice" class="com.T.A.dsadvice.DataSourceAdvice" />
配置事務
<!-- spring aop manager transaction -->
    <tx:advice id="txTransactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- add transaction -->
            <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="change*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
通過配置aop:config實現AOP:AspectJ
    <aop:config>
        <aop:advisor advice-ref="dataSourceAdvice" pointcut="execution(* com.T.A.service..*Service.*(..))" order="1"/>
        <aop:advisor advice-ref="txTransactionAdvice" pointcut="execution(* com.T.A.service..*Service.*(..)))" order="2" />
    </aop:config>
複製程式碼

2.資料來源配置DynamicDataSource

//determineCurrentLookupKey是重寫的AbstractRoutingDataSource的方法
//主要是確定當前應該使用哪個資料來源的key,因為AbstractRoutingDataSource 中儲存的多個資料來源是通過Map的方式儲存的
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceSwitcher.getDataSource();
    }
}
複製程式碼

3.動態資料來源配置

public class DataSourceAdvice implements MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice
複製程式碼

4.例項化資料來源

一共三個資料來源:master slave 動態資料來源 儲存在master和slave,為了防止spring注入異常,所以master和slave都是主動例項化的,並不是交給spring管理

5.mybatis配置及事務配置

MybatisConfiguration 主要是配置的sqlSessionFactory和sqlSessionTemplate,以及Mybatis的擴充套件框架Mapper的配置,如果不需要Mapper,可以不用配置scannerConfigurer

6.AOP過濾

@Before是在方法執行前執行
@After在方法執行後執行
@Around環繞執行,可以再方法執行前後操作
@Aspect放在類名上面,把當前類標識為一個切面供容器讀取
@Pointcut切入點,此註解放在方法上面,指向需要使用的切面程式設計的方法。此註解下面的方法並不會執行
複製程式碼

總結本專案中使用AOP實現主從分離的方式:

  • 使用@Before增強模式,在方法執行執行插入切面:Spring+AspectJ:aop:config
  • before方法中通過動態獲取資料來源的配置即寫資料庫的dbname
  • 設定過濾方法(設定方法名條件),如果滿足寫操作(函式名滿足過濾方法),則把DataSource設定為master(呼叫這個函式後的dao層對主庫進行處理),否則設定為slave。

參考資料:raye.wang/springboot-…

5.總結

  • 其實讀寫分離和AOP以前為了面試看過,沒有實際應用過或者沒有一個應用場景,怎麼看也只能理解表面。

對於AOP來說,最重要的點在於:

  • 理解應用場景:比如這個專案中是用了AOP解決了讀寫資料庫分離;
  • 考慮在什麼期間插入程式碼,選用合適的AOP方法:本專案選擇.java到.class之間的編譯期間,靜態織入的方法。使用的插入方法是AspectJxml的方式配置 aop:config
  • 考慮怎麼過濾方法,找到注入點的描述,比如公司的專案(是否有update insert 等關鍵詞判斷是否為寫操作)通過方法名來過濾
  • 考慮以怎樣的方式處理程式碼,是在程式碼執行之前?執行之後?(5種增強型別):Before advice