Sharding Sphere
(Sharding jdbc分庫分表學習已基本完成,相關整合SpringBoot使用實戰項目netty_taxi存放在筆記相關實戰目錄中,並且實現了更完美的日期分表。)
官網地址:http://shardingsphere.apache.org/index_zh.html
簡述
Sharding Sphere是一套開源的分布式數據庫中間件解決方案,目前主要產品由Sharding-JDBC和Sharding-Proxy組成,定位為關系型數據庫中間件,可以合理的在分布式環境下利用關系型數據庫的計算和存儲能力。
此次學習,主要學習如何使用Sharding-JDBC和Sharding-Proxy進行數據庫分庫分表操作。
什么是分庫分表
當數據庫數據量愈發龐大的時候,單表數據量過多會造成數據crud的壓力,讓服務的效率變低。為解決這種情況,可以根據業務對數據庫數據進行分庫分表操作,使得單個數據庫和單個表中數據量降低,減少數據訪問的壓力,提高服務的性能。
Sharding-JDBC
簡介
Sharding-JDBC是一個輕量級的java框架,在java的JDBC層提供額外服務。它使用客戶端直連數據庫,以jar包的形式提供服務,無需額外的部署和依賴,可以理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。
Sharding-JDBC不是用來實現分庫分表的,分庫分表是需要在數據庫設計的時候就完成的,進行提前建表和分庫,而Sharding-JDBC只是以jar包的形式提供給我們一個方便訪問和管理多個數據源和多個表的中間件。
實現水平分表
1、引入sharding-jdbc依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
2、准備數據庫資源
sharding-jdbc只是簡化對於分表分庫操作的中間件,所以在使用sharding-jdbc之前需要先准備分庫分表的基本數據庫和表結構。
在此,准備一個名為traffic_pay的數據庫,數據庫中創建order_01、order_02兩個數據庫表,約定根據訂單號的奇偶將不同的訂單數據存放到不同的order表中。
如下圖所示:
order表結構如下:
3、准備操作數據庫相關代碼
准備數據庫表實體、操作數據庫mapper、單元測試等文件
4、進行數據源和sharding-jdbc配置
(關於sharding-jdbc相關配置,可以在官網找到參考案例......)
# sharding-jdbc分片策略配置
# 配置數據源名稱
spring:
shardingsphere:
datasource:
names: traffic-pay
# 配置數據源的詳細內容
traffic-pay:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/traffic_pay
username: root
password: 123456
# 配置數據庫表的分布情況,例如表的名稱,表的所在數據庫
sharding:
tables:
order:
actual-data-nodes: traffic-pay.order_$->{01..02}
# 指定數據庫表的主鍵以及主鍵的生成策略 SNOWFLAKE:雪花算法生成主鍵
key-gengrator:
column: id
type: SNOWFLAKE
# 指定分片策略,約定orderId為偶數的添加到order_01表,orderId為基數的添加到order_02表
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: order_$->{order_id % 2 + 1}
# 允許sql日志輸出
props:
sql:
show: true
# 允許一個實體類覆蓋多個相同結構的表來操作
main:
allow-bean-definition-overriding: true
以上就是對於分表的全部配置,其中表在數據庫的分布情況以及分片策略用到了行表達式來進行配置。
traffic-pay.order_$->{01..02}
表示的是這個配置所操作的表是針對於traffic-pay.order_1
和traffic-pay.order_2
表進行的。
order_$->{order_id % 2 + 1}
表示的是針對order_id
字段來進行分表策略的規定,order_id
除以二之后再加一的值拼接上前面的表名部分即為當前要操作的表。
5、進行junit單元測試
package com.xsh.study;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xsh.study.bean.Order;
import com.xsh.study.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
class StudyApplicationTests {
@Autowired
private OrderMapper orderMapper;
@Test
void addOrder(){
Order order = new Order();
order.setOrderId(2L);
order.setOrderMessage("first insert");
order.setCreateTime(new Date());
orderMapper.insert(order);
}
@Test
void getOrder(){
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("order_id",1);
Order order = orderMapper.selectOne(orderQueryWrapper);
System.out.println(order);
}
}
實戰:根據日期進行水平分表
上面我們實現了根據order_id的奇偶性來進行水平分表,但是工作中我大多接觸到的是對於龐大的訂單進行每日的分表策略,接下來看下如何實現。
1、創建數據庫表結構
event表結構如下:
可以看到,表設計中有一個shard_date的分表鍵,shard_date是日期信息分表鍵,例如event_20210606表中shard_date的值就都會是20210606日期信息,我將根據這個分表鍵字段來進行數據的分片。在進行數據插入和查詢的時候可以獲取系統當前時間來維護分表鍵,來決定數據是操作哪張表。
2、更改數據庫配置
# sharding-jdbc分片策略配置
# 配置數據源名稱
spring:
shardingsphere:
datasource:
names: traffic-pay
# 配置數據源的詳細內容
traffic-pay:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://lcoalhost:3306/traffic_pay
username: root
password: 123456
# 配置數據庫表的分布情況,例如表的名稱,表的所在數據庫
sharding:
tables:
event:
actual-data-nodes: traffic-pay.event_$->{20210606..20210608}
table-strategy:
standard:
sharding-column: shard_date
precise-algorithm-class-name: com.xsh.study.stratepy.DatePreciseShardStrategy
# 允許sql日志輸出
props:
sql:
show: true
# 允許一個實體類覆蓋多個相同結構的表來操作
main:
allow-bean-definition-overriding: true
由yml配置可以看到,對於簡單的訂單id取模實現奇偶分片不同,采取了別樣的配置。
-
actual-data-nodes: traffic-pay.event_$->{20210606..20210608}
這里的行表達式20210606..20210608
表示數據庫表命名在這個范圍區間的表,在此表示event_20210606、event_20210607、event_20210608
三張表; -
sharding-column: shard_date
指定分表鍵字段名稱,在插入和查詢的時候都會自動根據分表鍵去決定操作哪張表; -
precise-algorithm-class-name: com.xsh.study.stratepy.DatePreciseShardStrategy
指定分表策略配置類
3、創建分表策略配置類
package com.xsh.study.stratepy;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
/**
* Created with IntelliJ IDEA.
*
* @Auther: xiashihua
* @Date: 2021/06/06/10:19
* @Description:
*/
public class DatePreciseShardStrategy implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
String value = preciseShardingValue.getValue();
for (String str : collection) {
if(str.endsWith(value)){
return str;
}
}
throw new IllegalArgumentException("未找到匹配的數據表");
}
}
分表策略配置需要實現PreciseShardingAlgorithm接口,其中有一個doSharding(),有兩個參數:
PreciseShardingValue<String> preciseShardingValue
表示的是查詢sql語句中分表鍵的信息,通過preciseShardingValue.getValue()
可以獲取到分表鍵的值;Collection<String> collection
表示的是配置中的表名列表,循環這個列表並且和分表鍵參數匹配就可以找到此次sql操作想要操作的表空間。
4、編寫單元測試
@Test
void addEvent(){
Event event = new Event();
event.setShardDate("20210608");
event.setEventId(1L);
event.setEventMessage("20210606 message");
event.setCreateTime(new Date());
eventMapper.insert(event);
}
@Test
void getEvent(){
QueryWrapper<Event> eventQueryWrapper = new QueryWrapper<>();
eventQueryWrapper.eq("shard_date","20210607");
List<Event> events = eventMapper.selectList(eventQueryWrapper);
events.stream().forEach(x -> {
System.out.println(events);
});
}
(需要注意的是,sharding-jdbc只是負責對於分庫分表之后sql的操作,在之前我們還需要自己准備好多張分表。如上方實例所示,如果按照訂單的日期進行分表的話可以使用定時任務的方式,每天啟動提前執行建表語句來創建好對應的表。)
實現水平分庫
水平分庫即在水平分表的基礎之上進行水平分庫,將創建traffic_pay1和traffic_pay2兩個數據庫,其中同時存在order_1和order_2相同結構的表。約定,將主鍵id為奇數的存放在traffic_pay2數據庫,id為偶數的存放在traffic_pay1數據庫,並且,order_id為奇數的存放在order_2表中,為偶數的存放在order_1中。
1、准備數據庫和表結構
2、進行水平分庫配置
# sharding-jdbc分片策略配置
# 配置數據源名稱,因為分庫所以有多數據源
spring:
shardingsphere:
datasource:
names: traffic-pay1,traffic-pay2
# 配置數據源的詳細內容
traffic-pay1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/traffic_pay1
username: root
password: 123456
traffic-pay2:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/traffic_pay2
username: root
password: 123456
# 配置數據庫及數據庫表的分布情況,例如表的名稱,表的所在數據庫
sharding:
tables:
order:
actual-data-nodes: traffic-pay$->{1..2}.order_$->{1..2}
# 指定數據庫表的主鍵以及主鍵的生成策略 SNOWFLAKE:雪花算法生成主鍵
key-gengrator:
column: id
type: SNOWFLAKE
# 指定表的分片策略,約定orderId為偶數的添加到order_01表,orderId為奇數的添加到order_02表
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: order_$->{order_id % 2 + 1}
# 指定數據庫的分片策略,約定id為偶數的添加到traffic_pay1數據庫中,id為奇數的添加到traffic_pay2數據庫中
data-base-strategy:
inline:
sharding-column: id
algorithm-expression: traffic-pay$->{id % 2 + 1}
# default-database-strategy:
# inline:
# sharding-column: id
# algorithm-expression: traffic_pay$->{id % 2 +1} 對所配置的數據源中所有表添加此策略
# 允許sql日志輸出
props:
sql:
show: true
# 允許一個實體類覆蓋多個相同結構的表來操作
main:
allow-bean-definition-overriding: true
如上配置所示,水平分庫我們需要配置多個數據庫的數據源,然后對數據庫和數據庫表都進行數據分片的策略。其中,數據庫分片策略有兩種配置方式:
截圖中,注釋起來的部分是針對於數據庫所有的表都使用該分片策略,為default策略,而上面的配置是可以根據tables的配置來針對性的對某個表進行分片策略配置。
3、單元測試編寫
@Test
void partDatabaseAddOrder(){
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setOrderId(2L);
order.setOrderMessage("......");
order.setCreateTime(new Date());
orderMapper.insert(order);
}
}
@Test
void partDatabaseGetOrder(){
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id",1401826029816061953L);
queryWrapper.eq("order_id",2L);
Order order = orderMapper.selectOne(queryWrapper);
System.out.println(order);
}
實現垂直分庫
垂直分庫的概念體現在專表專庫,例如用戶信息的表存放在user_db中,訂單相關的表存放在order_db中,互不干擾,垂直切分。使用sharding-jdbc可以快速的在多個數據庫中操作數據,避免繁瑣的操作。
1、創建數據庫和表結構
2、准備對應實體類以及mapper
3、進行垂直分庫策略配置
# sharding-jdbc分片策略配置
# 配置數據源名稱,因為分庫所以有多數據源
spring:
shardingsphere:
datasource:
names: user,order
# 配置數據源的詳細內容
user:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://8.131.86.156:3306/user_db
username: root
password: 123456
order:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://8.131.86.156:3306/order_db
username: root
password: 123456
# 配置數據庫及數據庫表的分布情況,例如表的名稱,表的所在數據庫
sharding:
tables:
t_user:
actual-data-nodes: user.t_user
key-gengrator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_user
t_order:
actual-data-nodes: order.t_order
key-gengrator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_order
# 允許sql日志輸出
props:
sql:
show: true
# 允許一個實體類覆蓋多個相同結構的表來操作
main:
allow-bean-definition-overriding: true
4、編寫測試用例
@Test
public void addUser(){
User user = new User();
user.setUserId(1L);
user.setUserName("張三");
userMapper.insert(user);
}
@Test
public void getUser(){
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("user_name","張三");
System.out.println(userMapper.selectOne(userQueryWrapper));
}
@Test
public void addOrder(){
Order order = new Order();
order.setOrderId(1L);
order.setOrderMessage("111");
orderMapper.insert(order);
}
關於使用sharding-jdbc垂直分庫做多數據源管理的猜想
sharding-jdbc垂直分庫可以通過配置的方式使我們在操作多個數據源時得心應手,這讓我想到,平常開發中遇到多數據源的情況還是有很多的,使用sharding-jdbc做多數據源管理是否更方便。
然后,我在order_db中建立了表t_event:
然后添加對應的數據分片策略:
sharding:
tables:
t_user:
actual-data-nodes: user.t_user
key-gengrator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_user
t_order:
actual-data-nodes: order.t_order
key-gengrator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_order
t_event:
actual-data-nodes: order.t_event
key-gengrator:
column: id
type: SNOWFLAKE
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_event
果然,在進行配置之后多數據源操作變得非常簡單,不需要再額外的添加任何切換數據源的操作,並且在同一個單元測試中操作兩個不同的數據源也正常無誤!
之前,面對多數據源操作我一般會選擇Mybatis Plus的@DS注解來實現,使用這個注解需要引入如下的依賴包:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
並且在同一事務中會出現數據源切換失敗的情況,這個時候就需要對失敗的持久層方法進行新開啟一個事務來完成操作,新開啟一個事務需要使用到這個注解:@Transactional(propagation = Propagation.REQUIRES_NEW)
@DS("houseDepartment")
@MapKey(value = "id")
@Transactional(propagation = Propagation.REQUIRES_NEW)
Map<Integer, DictionaryCommonDto> getDictionValueByMap(@Param("dictType") String dictType);
如上的使用方法就可以解決@DS注解失效的問題。
所以,采用shardng-jdbc來維護多數據源的關系似乎就不需要這么繁瑣的操作,但是同時也增加了基礎架構以及配置文件的負擔,而且如果表非常多的話會造成配置文件非常臃腫。