AOP實現mysql的主從數據庫:讀寫分離

1.問題

首先,爲何會碰到這樣的問題?java

昨天寫的一個業務上線了,可是在dev環境和test環境都能跑,可是到了線上環境發生數據不能插入的問題。
問了老大以後發現線上數據庫是讀寫分離的,而後經過過濾器的才能進入寫數據庫卡,個人函數命名規範問題不符合過濾器的要求,致使從controller不能進入邏輯函數。面試

主要緣由spring

  • 線上數據庫是主從分離(即讀寫分離,寫數據的狀況下鏈接主庫,讀數據的時候鏈接從庫)
  • 而爲何跟函數命名規範相關?
  • 代碼中實現數據庫讀寫分離是經過AOP的攔截器在方法開始執行先後插入咱們想要的代碼來實現動態切換數據源的功能
  • 爲何要用AOP來實現數據源的切換?爲何要讀寫分離?AOP是如何實現讀寫分離的?

2.什麼是AOP

2.1 AOP定義

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

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:主要指增長服務器的數量;
一、冷備份(定時全量/增量備份)
二、熱備份(在主從架構上實現)
三、主從架構(N主M從)
四、讀寫分離(在主從架構上實現)
五、數據分片(分庫分表(垂直分庫 水平分表))
複製代碼

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

3.3 讀寫分離的缺點

  • 成本增長
  • 數據延遲
  • 寫數據庫壓力較大

3.4 主從複製方式

  • 基於日誌 binlog
  • 基於GTID 全局事務標識符
一、Master將數據改變記錄到二進制日誌(binary log)中,也就是配置文件log-bin指定的文件,這些記錄叫作二進制日誌事件(binary log events) 
二、Slave經過I/O線程讀取Master中的binary log events並寫入到它的中繼日誌(relay log) 
三、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管理bash

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
相關文章
相關標籤/搜索