基于spring 切面(AOP)實現(xiàn)動態(tài)多數(shù)據(jù)源切換;基于 MyBatis 插件方式實現(xiàn)動態(tài)分表查詢。 來源于多個已上線項目實踐,本項目有完整的測試示例。
mybatis-plugin-shard
- 基于spring 切面(AOP)實現(xiàn)動態(tài)多數(shù)據(jù)源切換。
- 基于 MyBatis 插件方式實現(xiàn)動態(tài)分表策略。
- 來源于多個已上線項目實踐。
- 本項目有完整的測試示例。
以后會出詳細(xì)的文檔,敬請期待。
todo
- 將分庫分表配置與數(shù)據(jù)源配置統(tǒng)一放到文件 db-config.xml,并作為配置的切面的參數(shù),在整個分庫分表過程都可訪問。
- 完善分表邏輯,比起之前將分庫分表配置在一個文件中更加優(yōu)雅,也更加靈活,擴(kuò)展性越好。
- 完善文檔
項目地址
- github: https://github.com/uncleAndyChen/mybatis-plugin-shard
- gitee: ??https://gitee.com/uncleAndyChen/mybatis-plugin-shard
配套 MBG 增強(qiáng)插件
查看 MBG 增強(qiáng)插件請移步:mybatis-generator
- 用該 MBG 增強(qiáng)插件生成的 {xxx}Mapper.xml,會把表名用[`](不包括中括號)引起來,這樣做的目的是分表時,動態(tài)給表名添加后綴后替換原始表名時不會“添亂”。
- 注意 [`] 并非單引號,是在ESC 鍵下面、Q 鍵左上角的數(shù)字鍵 1 的左邊那個鍵對應(yīng)的“單引號”。
- 比如有兩張表:biz_trade、biz_trade_order,現(xiàn)在需要動態(tài)將 biz_trade 替換成 biz_trade_9,如果表名前后沒有[`],則 biz_trade_order 也會被替換,替換后為:biz_trade_9_order,這顯然不是我們希望發(fā)生的。
功能概述
- 分庫:簡單的分庫功能,更確切的講,是多數(shù)據(jù)源管理,可根據(jù)業(yè)務(wù)動態(tài)切換,基于切面(AOP)。
- 分表:對于同一數(shù)據(jù)源或不同數(shù)據(jù)源下的相同表結(jié)構(gòu)的表,通過簡單配置,實現(xiàn)分表查詢功能。
- 適用數(shù)據(jù)量增加迅速的業(yè)務(wù)場景。
- 底層實現(xiàn):基于 MyBatis 插件,攔截最終執(zhí)行的 SQL 語句并且根據(jù)分表配置對 SQL 語句中的表名進(jìn)行修改之后再執(zhí)行。
- 要求表名必須用 [`](不包括中括號)引起來。請使用增強(qiáng)插件(mybatis-generator)生成 Mapper 和 entity model。
動態(tài)切換數(shù)據(jù)源的三種方式
- 通過參數(shù) ShardRequest.java 指定:優(yōu)先級最高,也最靈活。
- 可以根據(jù)具體業(yè)務(wù)場景決定要連接哪個數(shù)據(jù)源。
- 注解:可用在類和方法上,方法注解優(yōu)先于類注解。
- biz service 配置
- 以上兩種方式均沒有的情況下,會讀取 ShardConfig.shardSchemaInterfaceClassNameList 配置信息,在運行過程中,通過 AOP 攔截 biz.service,從而識別應(yīng)該使用哪個數(shù)據(jù)源,達(dá)到分庫/多數(shù)據(jù)源動態(tài)切換的目的。
- 這種方式的優(yōu)點:可以由專人統(tǒng)一管理,同時生產(chǎn)環(huán)境與開發(fā)、測試環(huán)境可以用不同的配置信息,開發(fā)人員與測試人員不用關(guān)注分庫的細(xì)節(jié)。
如果以上三種方式都沒有找到數(shù)據(jù)源,則使用默認(rèn)的數(shù)據(jù)源。
分庫分表思路
- 分庫思路:
- 每個庫有一個唯一的標(biāo)志,起名叫 shardKeySchema,每個數(shù)據(jù)庫的 shardKeySchema 與 db-source.xml 定義的數(shù)據(jù)源 dataSource -> targetDataSources -> map -> key 一一對應(yīng)。
- 用戶在初始化時根據(jù)業(yè)務(wù)規(guī)則分配到某一個庫,將該庫的 shardKeySchema 保存到用戶表。
- 分表思路:
- 每個用戶分配一個用于分表的數(shù)字編號 shardKeyTableNumber,同樣保存到用戶表。
- 用戶表:
- 集中在一個庫用于統(tǒng)一登錄驗證,登錄時獲取用戶 shardKeySchema 和 shardKeyTableNumber 并將用戶登錄信息緩存于 Session 或非關(guān)系型數(shù)據(jù)庫,業(yè)界常用的如 redis、memcached。
- 業(yè)務(wù)操作請求:
- 在請求數(shù)據(jù)時,就可以根據(jù) shardKeySchema 動態(tài)切換數(shù)據(jù)源,根據(jù) shardKeyTableNumber 決定查哪張表了(分表操作通過 MyBatis 插件實現(xiàn))。
分表分庫場景
- 場景一:
- SaaS 平臺,用戶量成千上萬,交易表 biz_trade 每天100萬級增長,如果只用一個庫的一張表,寫入和讀取壓力會非常大,會成為瓶頸,所以需要分庫分表。
- 請求數(shù)據(jù)時,需要通過 ShardRequest.java 傳 shardKeySchema 和 shardKeyTableNumber 參數(shù)。
- 業(yè)務(wù)場景之:平均分配
- 每個數(shù)據(jù)庫實例最多分配 10 萬用戶,超過 10 萬的用戶,再分配到新庫。
- 交易記錄平均分到 10 張表,這就意味著用于分表的 shardKeyTableNumber,一個數(shù)字編號最多同時分配給一萬個用戶。
- 用戶請求數(shù)據(jù)時,將用戶的 shardKeyTableNumber 除以 10,將余數(shù)作為分表后綴,比如用戶的 shardKeyTableNumber=8888,那么,8888%10=8,則用戶的交易表是 biz_trade_8。
- 同理,如果要平均分配到 100 張表,那么就除以 100 再取余作為分表后綴,8888%100=88,則用戶的交易表是 biz_trade_88。
- 業(yè)務(wù)場景之:區(qū)別對待
- 在平均分配的基礎(chǔ)上,由于運營需要,現(xiàn)在有 vip 客戶,要保證 vip 客戶的用戶體驗,vip 客戶的數(shù)據(jù)庫讀寫速度要快,那怎么辦呢?
- 其實只要針對這部分用戶再制定一套規(guī)則就可以了,因為 shardKeySchema 和 shardKeyTableNumber 都是可以指定的。
- 如果用戶由一般用戶變?yōu)榱?vip 用戶,那么在重新指定 shardKeySchema 和 shardKeyTableNumber 之后,用戶原來的數(shù)據(jù)做相應(yīng)的遷移即可。
- 場景二:
- 不同于場景一,在某一些業(yè)務(wù)場景,需要與其它業(yè)務(wù)系統(tǒng)做對接,在其它系統(tǒng)不能提供 api 的情況下,直接操作數(shù)據(jù)庫無疑是最快也最直接的方式。
- 這種情況,不同業(yè)務(wù)數(shù)據(jù)保存在不同的數(shù)據(jù)庫,請求數(shù)據(jù)的時候,對于從哪個數(shù)據(jù)庫請求數(shù)據(jù)是明確的,那么最直接的方式就是使用注解,或者配置 ShardConfig.shardSchemaInterfaceClassNameList。
- 在不需要分表的情況下,用注解和配置 ShardConfig.shardSchemaInterfaceClassNameList 就夠了,這種情況下請求數(shù)據(jù)時,不需要通過 ShardRequest(ShardRequest.java)傳 shardKeySchema 和 shardKeyTableNumber 參數(shù)。
- 當(dāng)然,也可以不用注解也不用配置 ShardConfig.shardSchemaInterfaceClassNameList,還是通過 ShardRequest 傳遞參數(shù)也行,怎么靈活怎么來。
- 場景三:
- 分表是確定的,不是動態(tài)分配的,那么 ShardRequest.java 只傳 shardKeyTable 即可。
運行
git clone https://github.com/uncleAndyChen/mybatis-plugin-shard.git- 因為依賴統(tǒng)一管理,添加了一個父模塊:dependencies,只有一個 pom.xml 文件,需要先把這個 model 安裝到本地倉庫,否則會去 maven 配置的倉庫下載。打開 cmd 窗口,在項目根目錄下操作:
cd dependencies
mvn clean
mvn compile
mvn install
- 強(qiáng)烈建議:maven 遠(yuǎn)程倉庫添加阿里云鏡像。
- 修改 maven 根目錄下
config/settings.xml,在<mirrors>下添加:
- 修改 maven 根目錄下
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/jcenter</url>
<mirrorOf>central</mirrorOf>
</mirror>
- 用你喜歡的 IDE 導(dǎo)入項目,如果你要我推薦一款 IDE,那么我強(qiáng)烈推薦 IntelliJ IDEA,官網(wǎng):http://www.jetbrains.com/
- IDE 安裝 Lombok 插件。
- MySQL 數(shù)據(jù)庫,導(dǎo)入
docs/schemas.sql - 修改
biz/biz-config/src/main/resources/jdbc.properties中連接數(shù)據(jù)庫的參數(shù) - 啟動
- 訪問:
http://localhost:81,可以測試以三種不同方式切換數(shù)據(jù)源來查詢數(shù)據(jù)。具體細(xì)節(jié)請看源代碼,以后會出詳細(xì)的文檔,敬請期待。
image
數(shù)據(jù)源配置(部分)
<bean id="dataSource" class="common.aspect.ChooseDataSource" primary="true">
<property name="defaultTargetDataSource" ref="dataSourceSystem"/>
<!-- 下面的各個 0key 需要配置到 shardTableConfigView 的 schemaKeyList -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="system" value-ref="dataSourceSystem"/>
<entry key="student" value-ref="dataSourceStudent"/>
<entry key="finance" value-ref="dataSourceFinance"/>
<entry key="biz" value-ref="dataSourceBiz"/>
</map>
</property>
</bean>
配置分表分庫配置類
<!-- 以下配置,部分表名只是用于配置示例,僅為了更好的展示如何配置。
本項目沒有用到的表名有:edu_class、biz_trade_order、biz_item、biz_item_sku
-->
<bean id="shardConfig" class="common.shard.ShardConfig" >
<!-- 列表值為 dataSource.targetDataSources 的 keys -->
<property name="schemaKeyList">
<list>
<value>system</value>
<value>student</value>
<value>finance</value>
<value>biz</value>
</list>
</property>
<!-- 基于服務(wù)接口分庫策略,
把針對某個 schema 的接口配置在該數(shù)據(jù)源 key 對應(yīng)的 list 下,沒有就不配置
-->
<property name="shardSchemaInterfaceClassNameList">
<map>
<entry key="student">
<list>
<value>biz.service.facade.IEduStudentService</value>
</list>
</entry>
</map>
</property>
<!-- 分表策略
直接將 ShardRequest.shardKeyTable(優(yōu)先級高于后者) 或 ShardRequest.shardKeyTableNumber 作為分表后綴的表。
ShardRequest 參見:https://github.com/uncleAndyChen/mybatis-plugin-shard/blob/master/common/common-shard/src/main/java/common/shard/ShardRequest.java
-->
<property name="shardTableDirectlyList">
<list>
<value>edu_student</value>
<value>edu_class</value>
</list>
</property>
<!-- 分表策略
通過兩個數(shù)相除取余作為后綴的表,配合 ShardRequest.shardKeyTableNumber 使用
ShardRequest 參見:https://github.com/uncleAndyChen/mybatis-plugin-shard/blob/master/common/common-shard/src/main/java/common/shard/ShardRequest.java
-->
<!-- key 將作為 shardKeyTableNumber 的除數(shù)(取余), 余數(shù)作為分表后綴-->
<!-- shardKeyTableNumber 通過 ShardRequest 傳遞,在請求 api 時傳遞 -->
<property name="shardTableDivideList">
<map>
<entry key="10">
<list>
<value>biz_trade</value>
<value>biz_trade_order</value>
</list>
</entry>
<entry key="5">
<list>
<value>biz_item</value>
<value>biz_item_sku</value>
</list>
</entry>
</map>
</property>
<!-- 打印分表的 sql 語句,默認(rèn)為 false 即不打印。-->
<property name="printShardSqlInfo" value="true" />
<!-- 不需要分表的 sql 語句列表,以下這句為 MyBatis 操作數(shù)據(jù)庫新增記錄時,查詢新增的主鍵值的語句 -->
<property name="notNeedShardSqlList">
<list>
<value>SELECT LAST_INSERT_ID()</value>
</list>
</property>
</bean>
切面配置
<!-- 用于切面,實現(xiàn)攔截數(shù)據(jù)庫操作,實現(xiàn)分庫分表的類 -->
<bean id="dataSourceAspect" class="common.aspect.DataSourceAspect">
<property name="shardTableConfigView" ref="shardConfig" />
</bean>
<!-- 定義切面,用于攔截數(shù)據(jù)庫操作,實現(xiàn)分庫分表 -->
<aop:config proxy-target-class="true">
<aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="1">
<aop:pointcut id="point" expression="(execution(* biz.service.impl.*.*(..)))"/>
<aop:before pointcut-ref="point" method="before"/>
<aop:after pointcut-ref="point" method="afterHandler"/>
</aop:aspect>
</aop:config>
請求參數(shù) ShardRequest.java 類
public class ShardRequest {
/**
* 分庫標(biāo)志 key,是定義數(shù)據(jù)源時指定的 key,在執(zhí)行數(shù)據(jù)庫操作之前,通過該 key 動態(tài)切換數(shù)據(jù)源。
* 如果只是分庫,除了用到個屬性,還可利用 ShardTableConfig.shardSchemaInterfaceNameList 實現(xiàn)。
* 有關(guān)這兩項配置的詳細(xì)信息,請參見:https://github.com/uncleAndyChen/mybatis-plugin-shard/blob/master/biz/biz-config/src/main/resources/db-source.xml
*/
private String shardKeySchema;
/**
* 分表標(biāo)志 key,直接用作分表后綴的 key 值,針對直接添加后綴的表
* 舉例:應(yīng)用該規(guī)則的原始表名為 table_name,則對應(yīng)的分表為:table_name_key
* 需要配合 ShardTableConfig 使用,與該類位于同一個目錄,在 db-source.xml 中配置各屬性值
* 應(yīng)用該規(guī)則的原始表名:ShardTableConfig.shardTableDirectlyList
* 詳細(xì)描述,請參見:https://github.com/uncleAndyChen/mybatis-plugin-shard/blob/master/biz/biz-config/src/main/resources/db-source.xml
*/
private String shardKeyTable;
/**
* 動態(tài)分表參數(shù)編號,整形,一般與用戶綁定,針對需要除一個數(shù)得到后綴的表
* 需要配合 ShardTableConfig 使用,與該類位于同一個目錄,在 db-source.xml 中配置各屬性值
* 應(yīng)用該規(guī)則的原始表名:ShardTableConfig.shardTableDivideList
* 詳細(xì)描述,請參見:https://github.com/uncleAndyChen/mybatis-plugin-shard/blob/master/biz/biz-config/src/main/resources/db-source.xml
*
* 場景:SaaS 平臺,每個用戶分配一個編碼值,可以按一定規(guī)則平均分配,比如現(xiàn)有有10萬個用戶,我們打算分10張表,那么,平均分配的話,就意味著每一萬個用戶有一個分表編號。
* 極端地,對于 SasS 的超級 VIP 用戶,可以分配一個唯一的分表編號,這就意味著這個 VIP 用戶獨享一套表。
* 多個用戶的數(shù)據(jù)可能存在于同一個數(shù)據(jù)庫實例,也可能存在于多個數(shù)據(jù)庫實例,可根據(jù)業(yè)務(wù)靈活分配。
*/
private int shardKeyTableNumber;
// getter and setter
// ...
}
重新生成 mapper 和 entity
請參考 生成 Mapper 操作
有關(guān) {xxx}Mapper.xml 文件
我是直接把 MBG 生成的 {xxx}Mapper.xml 文件放到了 biz-service-dal 模塊下與 {xxx}Mapper.java 平級的目錄下了,包名為:biz.mapper.xml.original 和 biz.mapper.xml.extend
默認(rèn)情況下,xml 文件不會被打包,所以,運行的時候會出現(xiàn)類似這樣的錯誤:
Invalid bound statement (not found): biz.service.dal.mapper.original.EduStudentMapper.selectByExample
解決:需要在 pom.xml 里設(shè)置為需要將 xml 一起打包,如下:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
directory 配置到 xml 的父目錄
src/main/java/biz/mapper/xml不會生效,配置成src/main/java就好。
技術(shù)清單
- JDK 1.8,理論上支持 1.8 以上的版本,如需升級,比如要改為 JDK 11,將文件
./dependencies/pom.xml中<java.version>1.8</java.version>改為<java.version>11</java.version> - MySQL 5.6.46、MySQL 5.7,用這兩個版本作的測試,理論上支持 5.6 及以上版本。
- maven 依賴庫
- maven 依賴版本在
./dependencies/pom.xml維護(hù),如果要升級某一框架的版本,只需要修改這個文件就行,模塊 dependencies 被作為其它模塊的 parent,目的就是統(tǒng)一管理版本,同樣的依賴庫只定義一次版本號。 - 以下依賴為當(dāng)前(2020-01-06)最新版本
- Spring Boot 2.2.2.RELEASE
- Spring Framework 5.2.2.RELEASE (common-shard 模塊直接依賴了 spring framework 下的 spring-aspects)
- MyBatis 3.5.3
- druid 1.1.21
- lombok 1.18.10
- jackson 2.10.1
- maven 依賴版本在
支持
如果有疑問或建議,歡迎請?zhí)?Issue。
可能不會立即回復(fù),尤其上班時間,不過我會盡量抽業(yè)余時間回復(fù)的。
如果幫到了你
請 Star 一下,讓我有動力繼續(xù)完善和優(yōu)化。
