引言
隨著業(yè)務(wù)和數(shù)據(jù)量的增加,應(yīng)用采用微服務(wù)部署日益增多,但是絕大多數(shù)微服務(wù)架構(gòu)應(yīng)用也還是采用的單數(shù)據(jù)庫模式,即便是大多數(shù)讀寫分離,本質(zhì)也還是單數(shù)據(jù)庫,隨著業(yè)務(wù)量和數(shù)據(jù)量增多,數(shù)據(jù)庫讀寫效率急劇下降,此時就需要對數(shù)據(jù)庫進(jìn)行維度拆分,如水平拆分(分表)、垂直拆分(分庫)
本文討論的情況為水平拆分與垂直拆分,以及遇到的各種集成問題
版本
ShardingSphere:4.0.0-RC2-SNAPSHOT
Seata:0.5.1
Mybatis-Plus:2.3.1
DruidDataSource:1.1.10
ShardingSphere目前對應(yīng)Dev未發(fā)布版本
規(guī)則
提前將項目對應(yīng)sql目錄文件導(dǎo)入數(shù)據(jù)庫,undo_log表為Seata用于回滾的表
其余t_order_0、t_order_1、t_order_item_0、t_order_item_1為ShardingSphere對應(yīng)的分表,注意:一定要提前創(chuàng)建好分庫分表,因為ShardingSphere內(nèi)部是需要提前進(jìn)行分庫分表掃描并加入ShardingSphere對應(yīng)的DataSourceMap
簡介
Seata AT 事務(wù)模型包含 TM(事務(wù)管理器),RM(資源管理器),TC(事務(wù)協(xié)調(diào)器)。
TC為seata-server,可以理解為單獨部署的服務(wù)器,TM/RM 通過RPC與TC進(jìn)行交互

ShardingSphere 分布式事務(wù)

ShardingSphere SPI供用戶擴(kuò)展XA強(qiáng)一致性事務(wù)或者是Base柔性事務(wù),而Seata AT作為一種Base柔性事務(wù)的一種實現(xiàn),本文著重分析和使用Seata
整合SeataAT分析過程

從ShardingSphere 官方圖像顯示,Seata與ShardingSphere 框架都是對DataSource 進(jìn)行封裝和處理,所以要將Seata事務(wù)融入到ShardingSphere 框架中使用,就需要將Seata框架中DataSourceProxy包裝給ShardingSphere 框架的ShardingTransactionManager接口。不好理解?那咱們通過代碼分析

從上圖可知SeataATShardingTransactionManager實現(xiàn)了ShardingTransactionManager,這是一個SPI擴(kuò)展接口,將SeataAT事務(wù)融入到ShardingSphere 框架中
通過查看initSeataRPCClient接口可知

對Seata的TM、RM進(jìn)行初始化,以上簡要概述了ShardingSphere 使用SeataAT事務(wù)的流程,下面結(jié)合Springboot來具體分析集成各個框架的流程
分析1:ShardingSphere 創(chuàng)建DataSource

在sharding-jdbc-spring-boot-starter工程中SpringBootConfiguration會自動裝配參數(shù)

通過代碼分析,啟動之后通過遍歷配置的names字段創(chuàng)建DataSource,其中return DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);通過用戶配置的連接池類型進(jìn)行初始化,因為ShardingSphere默認(rèn)使用的HikariDataSource,而項目需要使用DruidDataSource,所以繼續(xù)分析創(chuàng)建DataSource的過程

通過代碼得知,ShardingSphere框架通過反射,將type字段同級的參數(shù)一并傳入進(jìn)行反射調(diào)用,所以我們就需要將DruidDataSource所需參數(shù)放置到type字段同級
這樣就完成了使用DruidDataSource?答案是NO,細(xì)心的同學(xué)可能會在啟動日志中發(fā)現(xiàn)輸出了Init DruidDataSource,通過繼續(xù)分析發(fā)現(xiàn)

DruidDataSource自動創(chuàng)建了DataSource,我們的期望是DataSource由ShardingSphere進(jìn)行創(chuàng)建,所以我們需要在@SpringBootApplication中排除DruidDataSourceAutoConfigure,至此ShardingSphere創(chuàng)建DataSource的過程完成
分析2:ShardingSphere使用SeataAT事務(wù)
通過上訴分析我們已經(jīng)得知ShardingSphere已經(jīng)創(chuàng)建好了各個DataSource并將其放入dataSourceMap集合中,通過Seata官網(wǎng)可知,需要使用Seata事務(wù),需要使用Seata提供的DataSourceProxy類,繼續(xù)通過源碼分析

ShardingSphere內(nèi)部通過SPI擴(kuò)展,將ShardingTransactionManager接口暴露,在ShardingSphere創(chuàng)建完DataSource之后,緊接著通過擴(kuò)展ShardingTransactionManager接口,將dataSourceMap集合中的各個DataSource代理給DataSourceProxy,至此SeataAT已經(jīng)融入ShardingSphere,但是現(xiàn)在使用Seata的@GlobalTransactional是無效的,下文會繼續(xù)分析
分析3:集成Mybatis-Plus/Mybatis
Mybatis-Plus作為Mybatis的一種增強(qiáng),引入Mybatis-Plus之后并配置參數(shù)

一切看起來是那么的輕松,啟動項目...不出意外將出現(xiàn)以下信息

為什么,為什么、為什么會這樣.我太難了...
話不多說,繼續(xù)分析原因,因為我們使用Mybatis-Plus,默認(rèn)會引入Mybatis依賴庫,然后DataSourceAutoConfiguration會自動加載,DataSourceAutoConfiguration會查找spring->datasource->url字段,因為我們用的ShardingSphere,并未配置這樣的參數(shù),知道原因了那就繼續(xù)在@SpringBootApplication中排除DataSourceAutoConfiguration
繼續(xù)啟動項目......不出意外出現(xiàn)以下信息

錯誤提示沒有發(fā)現(xiàn)sqlSessionFactory,因為我們使用Mybatis-Plus,正常情況下因由Mybatis-Plus進(jìn)行sqlSessionFactory的創(chuàng)建,繼續(xù)查看Mybatis-Plus源碼


打個斷點調(diào)試一波,發(fā)現(xiàn)確實沒有進(jìn)入,這又是為什么?查看MybatisPlusAutoConfiguration上面的注解發(fā)現(xiàn),因為我們已經(jīng)排除了DataSourceAutoConfiguration了,知道原因了,就是這個MybatisPlusAutoConfiguration沒生效,怎么辦?
自己重寫一份MybatisPlusAutoConfiguration到項目里面吧,在SpringBootApplication中直接排除MybatisPlusAutoConfiguration

至此Mybatis-Plus集成完畢
分析4:Seata 注解@GlobalTransactional
在上文中說道ShardingSphere使用SeataAT事務(wù),但是官方例子是Jdbc直連,不符合SpringBoot集成特點,所以本段落主要分析和如何使用@GlobalTransactional注解
如果我們使用Seata官方例子不難發(fā)現(xiàn),我們直接使用@GlobalTransactional注解是很方便的,也無需關(guān)心@GlobalTransactional 內(nèi)部是如何實現(xiàn)的,但是當(dāng)我們集成了ShardingSphere之后,我們按照Seata官方例子那樣直接在我們的業(yè)務(wù)Service上面使用@GlobalTransactional注解,會發(fā)現(xiàn)這個注解是無效的。這又是為什么?查看下源碼


GlobalTransactionScanner實現(xiàn)AbstractAutoProxyCreator,然后根據(jù)wrapIfNecessary判斷具體的Bean實體是否需要進(jìn)行Aop包裝/代理/增強(qiáng),包裝的條件為是否存在GlobalTransactional注解

回過頭我們發(fā)現(xiàn)ShardingSphere封裝的SeataATShardingTransactionManager類只是初始化了TMClient、RMClient。并沒有對Seata的@GlobalTransactional注解進(jìn)行處理
知道流程之后我們不難發(fā)現(xiàn)@GlobalTransactional注解的具體攔截實現(xiàn)類是GlobalTransactionalInterceptor。查看源碼

發(fā)現(xiàn)實現(xiàn)Aop的MethodInterceptor
既然已經(jīng)分析到了這里,那么方法自然而然就有了
方案1:重寫GlobalTransactionScanner類,然后通過@Bean注入,把里面的TMClient、RMClient這些剔除,因為TMClient、RMClient已經(jīng)在ShardingSphere的SeataATShardingTransactionManager類里面進(jìn)行初始化了,此方案為最佳推薦方案,因為GlobalTransactionScanner內(nèi)部做了代理判斷
方案2:直接通過AOP進(jìn)行處理,簡單粗暴,但是如果是線上不推薦該方案,因為沒有GlobalTransactionScanner處理的全面

至此Springboot集成ShardingSphere+Seata+Mybatis-Plus+DruidDataSource完畢,若有錯誤地方歡迎指出,后續(xù)文章將繼續(xù)分析和使用Nacos、Dubbo
例子已整合到Seata官方Demo中: https://github.com/seata/seata-samples/tree/master/springboot-shardingsphere-seata