SpringBoot3分庫(kù)分表
一、簡(jiǎn)介
分庫(kù)分表的設(shè)計(jì)和實(shí)現(xiàn)方式,在之前的內(nèi)容中總結(jié)過(guò)很多,本文基于SpringBoot3和ShardingSphere5框架實(shí)現(xiàn)數(shù)據(jù)分庫(kù)分表的能力;
不得不提ShardingSphere5文檔中描述的兩個(gè)基本概念:
垂直分片
按照業(yè)務(wù)拆分的方式稱(chēng)為垂直分片,又稱(chēng)為縱向拆分,它的核心理念是專(zhuān)庫(kù)專(zhuān)用。在拆分之前,一個(gè)數(shù)據(jù)庫(kù)由多個(gè)數(shù)據(jù)表構(gòu)成,每個(gè)表對(duì)應(yīng)著不同的業(yè)務(wù)。而拆分之后,則是按照業(yè)務(wù)將表進(jìn)行歸類(lèi),分布到不同的數(shù)據(jù)庫(kù)中,從而將壓力分散至不同的數(shù)據(jù)庫(kù)。
水平分片
水平分片又稱(chēng)為橫向拆分。相對(duì)于垂直分片,它不再將數(shù)據(jù)根據(jù)業(yè)務(wù)邏輯分類(lèi),而是通過(guò)某個(gè)字段(或某幾個(gè)字段),根據(jù)某種規(guī)則將數(shù)據(jù)分散至多個(gè)庫(kù)或表中,每個(gè)分片僅包含數(shù)據(jù)的一部分。
下面從案例實(shí)踐中,看看ShardingSphere5框架是如何實(shí)現(xiàn)分庫(kù)分表的原理;
二、工程搭建
1、工程結(jié)構(gòu)
2、依賴(lài)管理
這里只看兩個(gè)核心組件的依賴(lài):shardingsphere-jdbc組件是5.2.1版本,mybatis組件是3.5.13版本,在依賴(lài)管理中還涉及MySQL和分頁(yè)等,并且需要添加很多排除配置,具體見(jiàn)源碼;
<!-- Mybatis組件 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- ShardingSphere分庫(kù)分表 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
三、配置詳解
1、配置文件
此處只展示分庫(kù)分表的相關(guān)配值,默認(rèn)數(shù)據(jù)源使用db_master庫(kù),注意tb_order庫(kù)表路由的策略和分片算法的關(guān)聯(lián)關(guān)系,其他工程配置詳見(jiàn)源碼倉(cāng)庫(kù);
spring:
# 分庫(kù)分表配置
shardingsphere:
datasource:
# 默認(rèn)數(shù)據(jù)源
sharding:
default-data-source-name: db_master
names: db_master,db_0,db_1
db_master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db
username: root
password: 123456
db_0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db_0
username: root
password: 123456
db_1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/shard_db_1
username: root
password: 123456
rules:
sharding:
tables:
# tb_order邏輯
tb_order:
actual-data-nodes: db_${0..1}.tb_order_${0..2}
# tb_order庫(kù)路由
database-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: database_inline
# tb_order表路由
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table_inline
sharding-algorithms:
# tb_order庫(kù)路由算法
database_inline:
type: INLINE
props:
algorithm-expression: db_${order_id % 2}
# tb_order表路由算法
table_inline:
type: INLINE
props:
algorithm-expression: tb_order_${order_id % 3}
props:
sql-show: true
sql-comment-parse-enabled: true
2、配置原理
在配置中需要管理三個(gè)數(shù)據(jù)源,shard_db默認(rèn)庫(kù),在操作不涉及需要路由的表時(shí)默認(rèn)使用該數(shù)據(jù)源,shard_db_0和shard_db_1是tb_order邏輯表的路由庫(kù);
邏輯表tb_order整體使用兩個(gè)數(shù)據(jù)庫(kù),每個(gè)庫(kù)建3張結(jié)構(gòu)相同相同的表,在操作tb_order數(shù)據(jù)時(shí),會(huì)根據(jù)order_id字段值定位數(shù)據(jù)所屬的分片節(jié)點(diǎn);
- 庫(kù)路由db_${0..1}采用db_${order_id%2}的算法;
- 表路由tb_order_${0..2}采用tb_order_${order_id%3}的算法;
四、測(cè)試案例
1、主庫(kù)操作
基于Mybatis持久層框架,實(shí)現(xiàn)對(duì)shard_db默認(rèn)庫(kù)的數(shù)據(jù)操作,注意控制臺(tái)的日志打印,可以看到一系列解析邏輯以及庫(kù)表節(jié)點(diǎn)的定位,分頁(yè)查詢(xún)使用PageHelper組件即可;
public class MasterTest {
@Autowired
private BuyerMapper buyerMapper ;
@Autowired
private SellerMapper sellerMapper ;
@Test
public void testBuyerQuery (){
// 主鍵查詢(xún)
Buyer buyer = buyerMapper.selectByPrimaryKey(1) ;
System.out.println(buyer.getId()+";"+buyer.getBuyerName());
}
@Test
public void testBuyerInsert (){
// 新增數(shù)據(jù)
Buyer buyer = new Buyer() ;
buyer.setBuyerName("買(mǎi)家Three");
System.out.println(buyerMapper.insert(buyer));
}
@Test
public void testBuyerUpdate (){
// 更新數(shù)據(jù)
Buyer buyer = buyerMapper.selectByPrimaryKey(3) ;
if (buyer != null){
buyer.setBuyerName("Three買(mǎi)家");
System.out.println(buyerMapper.updateByPrimaryKey(buyer));
}
}
@Test
public void testSellerPage (){
// 1、設(shè)置分頁(yè)和查詢(xún)條件
PageHelper.startPage(2,2) ;
SellerExample sellerExample = new SellerExample() ;
sellerExample.setOrderByClause("id asc");
// 2、查詢(xún)數(shù)據(jù)
List<Seller> sellerList = sellerMapper.selectByExample(sellerExample) ;
// 3、構(gòu)建分頁(yè)實(shí)體對(duì)象
PageInfo<Seller> pageInfo = new PageInfo<>(sellerList) ;
System.out.println(pageInfo);
}
}
2、分庫(kù)操作
在對(duì)tb_order表執(zhí)行增刪改查時(shí),會(huì)根據(jù)order_id的字段值計(jì)算庫(kù)表的路由節(jié)點(diǎn),注意分頁(yè)時(shí)會(huì)查詢(xún)所有的分庫(kù)和分表,然后匯總查詢(xún)的結(jié)果;
public class ShardTest {
@Autowired
private OrderMapper orderMapper ;
/**
* 寫(xiě)入100條數(shù)據(jù)
*/
@Test
public void testOrderInsert (){
for (int i=1 ; i<= 100 ; i++){
Order order = new Order(i,i%3+1,i%3+1) ;
// orderMapper.insert(order) ;
}
}
@Test
public void testOrderQuery (){
Order order = orderMapper.selectByPrimaryKey(5) ;
System.out.println(order);
}
@Test
public void testOrderUpdate (){
Order order = orderMapper.selectByPrimaryKey(100) ;
if (order != null){
// 原數(shù)據(jù):買(mǎi)家和賣(mài)家ID都是2
order.setBuyerId(1);
order.setSellerId(3);
orderMapper.updateByPrimaryKey(order) ;
}
}
@Test
public void testOrderPage (){
// 1、設(shè)置分頁(yè)和查詢(xún)條件
PageHelper.startPage(1,10) ;
OrderExample orderExample = new OrderExample() ;
orderExample.createCriteria().andBuyerIdEqualTo(2).andSellerIdEqualTo(2);
orderExample.setOrderByClause("order_id desc");
// 2、查詢(xún)數(shù)據(jù)
List<Order> orderList = orderMapper.selectByExample(orderExample) ;
// 3、構(gòu)建分頁(yè)實(shí)體對(duì)象
PageInfo<Order> pageInfo = new PageInfo<>(orderList) ;
System.out.println(pageInfo);
}
}
3、綜合查詢(xún)
編寫(xiě)一個(gè)訂單詳情查詢(xún)接口,同時(shí)使用三個(gè)庫(kù)構(gòu)建數(shù)據(jù)結(jié)構(gòu);如果是基于列表數(shù)據(jù)的檢索,比較常規(guī)做法的是構(gòu)建ES索引結(jié)構(gòu),如果沒(méi)有搜索的需求,可以在訂單表分頁(yè)查詢(xún)后去拼接其他結(jié)構(gòu);
@RestController
public class OrderController {
@Resource
private BuyerMapper buyerMapper ;
@Resource
private SellerMapper sellerMapper ;
@Resource
private OrderMapper orderMapper ;
/**
* 查詢(xún)訂單詳情
*/
@GetMapping("/order/info/{orderId}")
public Map<String,Object> orderInfo (@PathVariable Integer orderId){
Map<String,Object> orderMap = new HashMap<>() ;
Order order = orderMapper.selectByPrimaryKey(orderId) ;
if (order != null){
orderMap.put("order",order) ;
orderMap.put("buyer",buyerMapper.selectByPrimaryKey(order.getBuyerId())) ;
orderMap.put("seller",sellerMapper.selectByPrimaryKey(order.getSellerId())) ;
}
return orderMap ;
}
}
查看SQL語(yǔ)句
db_master ::: select id, buyer_name from tb_buyer where id = ? ::: [1]
db_master ::: select id, seller_name from tb_seller where id = ? ::: [3]
db_0 ::: select order_id, seller_id, buyer_id from tb_order_1 where order_id =