為了減輕資料庫的壓力,一般會使用資料庫主從(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 切面類實現
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>(); } |
-
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public 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方法,如下:
12345678910111213public class MyTransactionManager extendsDataSourceTransactionManager{@Overrideprotected void doBegin(Object transaction, TransactionDefinitiondefinition) {//選擇master資料庫super.doBegin(transaction, definition);}}這樣既可以避免,把資料寫入到從庫的問題。
總結
本人的解決方案是基於專案實際的,不一定合適你,我只是展示瞭解決方案而已。當然你可以選擇開源的框架,像阿里的Cobar,360的Atlas。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!