ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
具备以下功能:
特性 |
定义 |
数据分片 |
数据分片,是应对海量数据存储与计算的有效手段。ShardingSphere 基于底层数据库提供分布式数据库解决方案,可以水平扩展计算和存储。 |
分布式事务 |
事务能力,是保障数据库完整、安全的关键技术,也是数据库的核心技术。基于 XA 和 BASE 的混合事务引擎,ShardingSphere 提供在独立数据库上的分布式事务功能,保证跨数据源的数据安全。 |
读写分离 |
读写分离,是应对高压力业务访问的手段。基于对 SQL 语义理解及对底层数据库拓扑感知能力,ShardingSphere 提供灵活的读写流量拆分和读流量负载均衡。 |
数据迁移 |
数据迁移,是打通数据生态的关键能力。ShardingSphere 提供跨数据源的数据迁移能力,并可支持重分片扩展。 |
联邦查询 |
联邦查询,是面对复杂数据环境下利用数据的有效手段。ShardingSphere 提供跨数据源的复杂查询分析能力,实现跨源的数据关联与聚合。 |
数据加密 |
数据加密,是保证数据安全的基本手段。ShardingSphere 提供完整、透明、安全、低成本的数据加密解决方案。 |
影子库 |
在全链路压测场景下,ShardingSphere 支持不同工作负载下的数据隔离,避免测试数据污染生产环境。 |
更多信息见官网:概览 :: ShardingSphere
开发环境:JDK 1.8、Maven 3.8.8、 IDEA CE 2023.2、MySQL 8.0.34
实现目标:使用 ShardingSphere-JDBC 进行数据库的分库分表,以 4.1.1 为例
技术架构:SpringBoot+MyBatis-Plus+ShardingSphere-JDBC
数据库结构:
order_db_1:
t_order_1
t_order_2
t_order_3
order_db_2:
t_order_1
t_order_2
t_order_3
两个数据库下的表结构完全相同,建表语句见下:
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
`order_id` bigint(20) NOT NULL COMMENT '订单id',
`price` decimal(10, 2) NOT NULL COMMENT '订单价格',
`user_id` bigint(20) NOT NULL COMMENT '下单用户id',
`status` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
`create_date` datetime DEFAULT NULL,
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
pom 文件配置依赖:
4.0.0
com.zdxlz.lwq
sharding-jdbc-demo
1.0-SNAPSHOT
8
8
UTF-8
org.springframework.boot
spring-boot-starter-parent
2.6.2
org.springframework.boot
spring-boot-starter-web
com.baomidou
mybatis-plus-boot-starter
3.5.1
com.mysql
mysql-connector-j
8.0.32
compile
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
4.1.1
yml 文件配置数据库相关信息:
server:
port: 8088
servlet:
context-path: /sharding
spring:
shardingsphere:
datasource:
names: db1,db2
db1:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://192.168.22.82:3306/order_db_1?useUnicode=true&useSSL=false
username: root
password: 123456
validationQuery: SELECT 1 FROM DUAL
db2:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://192.168.22.82:3306/order_db_2?useUnicode=true&useSSL=false
username: root
password: 123456
validationQuery: SELECT 1 FROM DUAL
sharding:
tables:
t_order:
actual-data-nodes: db$->{1..2}.t_order_$->{1..3}
database-strategy:
# 自定义分片策略
# complex:
# shardingColumns: user_id
# algorithmClassName: com.zdxlz.lwq.ShardingAlgorithm.ComplexRule
inline:
sharding-column: user_id
algorithm-expression: db$->{user_id % 2 + 1}
table-strategy:
inline:
sharding-column: price
algorithm-expression: t_order_$->{price % 3 + 1}
key-generator:
column: order_id
type: SNOWFLAKE
props:
sql:
show: true
yml 文件中的配置项需要仔细确认,例如数据库的地址应使用 jdbc-url 而非 url,在只有单个数据源时,SpringBoot 走默认数据源逻辑为我们把 url 与 jdbc-url 进行映射,保证我们获得数据源。此时我们自己设置的数据源没有进行映射处理,就需要保证字段符合Hikari的要求。否则会出现 java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName 异常。还需要明确指定 type,否则会提示 shardingDataSource 创建失败,配置参考见:数据源配置 :: ShardingSphere
在上文中的 yml 文件中,sharding.tables.t_order 中已配置分库分表的策略,用 groovy 语法实现。
ShardingSphere-JDBC 分库/分表策略支持 standard(用于单分片键的标准分片场景)、complex(用于多分片的复合分片场景)、inline(行表达式分片策略),详细介绍见:Yaml配置 :: ShardingSphere
以上文最简单的 inline 说明:
shardingColumn 为分片列名称,即以该字段进行逻辑处理,进行分数据库/数据表
algorithm-expression 为 groovy 语法实现的行表达式,是该字段的具体逻辑操作
对于配置在 database-strategy 下的 inline 实现的效果是:对于插入的数据,进行 user_id 判断,如果 user_id 整除 2,数据插入到 db_1,否则插入到db_2
对于配置在 table-strategy 下的 inline 实现的效果是:对于插入的数据,进行 price 判断,如果 price_id 整除 3,数据插入到 t_order_1,否则余数 +1 分别插入到 t_order_2、t_order_3
至此完成新增数据的分配,将大量数据分别通过 user_id、price 判断,分配到 2 个数据库的 6 个表下面,完成分库分表
对于分库分表的详细介绍可见:核心概念 :: ShardingSphere
注意点:对于业务代码的实现,数据库的真实表为 t_order_1、 t_order_2、 t_order_3,参考 ShardingSphere-JDBC 处理实现,我们构建数据库实体类时,@TableName 设定为 t_order 即可,示例:
package com.zdxlz.lwq.enity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.math.BigDecimal;
import java.util.Date;
@TableName("t_order")
public class Order {
@TableId
private BigDecimal order_id;
@TableField("price")
private int price;
@TableField("user_id")
private int user_id;
@TableField("status")
private String status;
@TableField("create_date")
private Date create_date;
public BigDecimal getOrder_id() {
return order_id;
}
public void setOrder_id(BigDecimal order_id) {
this.order_id = order_id;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getCreate_date() {
return create_date;
}
public void setCreate_date(Date create_date) {
this.create_date = create_date;
}
}
对于 groovy 语法不好实现、难以实现的逻辑,可以采用 complex 模式,自定义实现分片策略,示例如下:
package com.zdxlz.lwq.ShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import java.util.*;
public class ComplexRule implements ComplexKeysShardingAlgorithm {
@Override
public Collection doSharding(Collection collection, ComplexKeysShardingValue complexKeysShardingValue) {
//collection返回数据库或数据表的集合,见yml配置的策略,此处返回的是数据库集合:[db1, db2]
Collection shardingResults = new ArrayList<>();
//返回数据表的key、value
Map> columnsMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
//columnsMap返回插入数据的key和value,key为complex中配置的shardingColumns字段值,此处返回为{user_id=[617]}
List userIds = Arrays.asList(columnsMap.get("user_id").toArray());
for (Object userId:userIds) {
//对user_id进行逻辑处理
int index = getIndex((Integer) userId);
//循环匹配数据表源
for (String availableTargetName : collection){
if (availableTargetName.endsWith(String.valueOf(index))) {
shardingResults.add(availableTargetName);
break;
}
}
//匹配到一种路由规则就可以退出
// if (shardingResults.size() > 0) {
// break;
// }
}
//返回匹配到的数据库:shardingResults:[db_2],数据表同理
return shardingResults;
}
public int getIndex (int userId) {
//大于500,存db1
if (userId>500){
return 1;
}else {
//小于500,存db2
return 2;
}
}
}
首先实现 ComplexKeysShardingAlgorithm
根据 yml 中的配置,确定处理数据库或数据表,示例中的 yml 配置
sharding:
tables:
t_order:
actual-data-nodes: db$->{1..2}.t_order_$->{1..3}
database-strategy:
# 自定义分片策略
complex:
shardingColumns: user_id
algorithmClassName: com.zdxlz.lwq.ShardingAlgorithm.ComplexRule
# inline:
# sharding-column: user_id
# algorithm-expression: db$->{user_id % 2 + 1}
table-strategy:
inline:
sharding-column: price
algorithm-expression: t_order_$->{price % 3 + 1}
key-generator:
column: order_id
type: SNOWFLAKE
对数据库进行自定义分片,对字段 user_id 的值进行逻辑处理,选择对应的数据库
因此方法 doSharding( ) 参数 collection 返回数据库集合:[db1, db2]
columnsMap 返回插入数据的 key 和 value,key 为 complex中配置的 shardingColumns字段值 user_id,此处返回为{user_id=[617]}
逻辑匹配后,最终返回的数据库 shardingResults:[db2]
即表明该条数据将插入到 db2 中
对于数据表想实现自定义分片策略,同上述操作一致,仅需在 table-strategy 下配置 complex,然后自定义实现相关逻辑
开源项目地址:GitHub - liuweiqiang2016/shardingjdbc-demo: 分库分表开源框架shardingjdbc入门学习