目录
ShardingSphere简介
Apache ShardingSphere 是一款分布式 SQL 事务和查询引擎,可通过数据分片、弹性伸缩、加密等能力对任意数据库进行增强。它的核心功能有:数据分片、分布式事务、读写分离、联邦查询、数据库加密及脱敏、数据迁移等,本文重点阐述数据分片。
在大数据,高并发的场景下,数据库会存在性能瓶颈,需要对数据进行分片处理,比如分库分表处理。在分布式应用场景下,每个服务往往对应独立的数据库,天然进行了分库处理。在某个特定的服务中,由于某张表的数据量过大导致系统性能下降,需要对表进行拆分处理,如水平拆分和垂直拆分。
水平拆分:表结构相同,不同的数据落到不同的表中;垂直拆分:多字段的表拆成多个少字段的表;分表分库常见的解决方案有:shardingsphere和mycat,本文重点阐述基于shardingsphere分表-水平拆分。
基于INLINE行表达式的分片算法
环境介绍:springboot:2.7.6;shardingsphere:5.2.1;数据源使用Hikari;or mapping框架采用mybatis-plus 3.4.2;数据库mysql,实现基于shardingsphere分表-水平拆分的步骤如下:
1、建立数据库ds0,建立2张相同结构的订单表
CREATE TABLE `t_order_0` (
`order_id` bigint(20) NOT NULL,
`user_id` varchar(100) NOT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `t_order_1` (
`order_id` bigint(20) NOT NULL,
`user_id` varchar(100) NOT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2、添加项目依赖shardingsphere
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gingko</groupId>
<artifactId>data-sharding</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>data-sharding</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- springboot默认数据源(HikariCP)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 整合MyBatis -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- 整合shardingsphere -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.gingko.datasharding.DataShardingApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3、配置shardingsphere,参考官方配置说明:
核心的配置:algorithm-expression: t_order_${order_id % 2} #订单号对2取余的结果,决定数据分片到对应的t_order_0还是t_order_1表。
server:
port: 8080 #配置应用端口
spring:
shardingsphere:
# 配置显示sql
props:
sql-show: true
datasource:
names: ds0 #配置数据源列表,多个数据源使用逗号分割
ds0: #数据源配置
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/ds0?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: 123456
rules:
sharding:
tables:
t_order: #需要分表的逻辑表名
actualDataNodes: ds0.t_order_${0..1} #表的真实表列表
tableStrategy: # 分片策略配置
standard:
shardingColumn: order_id #分表列
shardingAlgorithmName: t_order_inline #分片算法是 t_order_inline, 这个名称是在下面配置的
sharding-algorithms: # 配置分片算法
t_order_inline: # 分片算法名称
type: INLINE
props:
algorithm-expression: t_order_${order_id % 2} #订单号对2取余
4、entity、mapper、测试类
package com.gingko.datasharding.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_order")
public class Order {
@TableId
private long orderId;//订单号
private String userId;//订单发起用户
}
package com.gingko.datasharding.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gingko.datasharding.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
package com.gingko.datasharding;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gingko.datasharding.entity.Order;
import com.gingko.datasharding.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@SpringBootTest
class OrderTest {
@Resource
private OrderMapper orderMapper;
@Test
void batchAdd() {
for(int i=0;i<10;i++) {
Order order = new Order(i,"admin" + i);
orderMapper.insert(order);
}
}
@Test
void getByOrderId() {
Order order = orderMapper.selectById(5);
log.info("订单信息:{}",order);
}
@Test
void getByUserId() {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.like("user_id","admin");
List list = orderMapper.selectList(queryWrapper);
log.info("获取用户的订单信息个数是:{}",list.size());
}
}
5、测试
运行batchAdd方法后,表t_order_0和t_order_1分别插进5条数据,按照order_id%2的规则插入。
运行getByOrderId方法后,由于参数order_id=5,%2=1,所以实际运行的sql只会从表t_order_1查询。
运行getByUserId方法,查询方法是按照user_id 模糊查询,由于不是按照user_id字段进行数据分表的,所以此查询会同时查询t_order_0和t_order_1表。
基于CLASS_BASED自定义类的分片算法
实际项目中,数据分片的规则往往基于实际的业务需求,下方示例演示基于user_id数据进行分片,模拟当user_id包含admin时,数据进入t_order_0表,否则数据进入t_order_1表,步骤如下:
1、修改配置,修改增加自定义的分片算法
2、实现自定义的分片算法,实现StandardShardingAlgorithm接口,泛型的类型String就是user_id属性的类型
package com.gingko.datasharding.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import java.util.Collection;
import java.util.Properties;
/**
* 自定义分片算法,算法基于user_id属性值分片,user_id 包含admin的进入t_order_0表,其他进入t_order_1表
*/
@Slf4j
public class CustomShardingAlgorithm implements StandardShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection,
PreciseShardingValue<String> shardingValue) {
String value = shardingValue.getValue();
log.info("shardingValue = {}",value);
/**
* 根据value的值,本项目就是user_id,依据实际的业务规则生成精确的操作的表名actuallyTableName
* 目前模拟为:user_id 包含admin的进入t_order_0表,其他进入t_order_1表
*/
String suffixTableName = "";
if(value.contains("admin")) {
suffixTableName = "_0";
}else {
suffixTableName = "_1";
}
String actuallyTableName = shardingValue.getLogicTableName() + suffixTableName;
if (collection.contains(actuallyTableName)) {
return actuallyTableName;
}
return null;
}
/**
* SPI方式的 SPI名称,配置文件中配置的时候需要用到
*/
@Override
public String getType() {
return "CUSTOM_SPI_BASED";
}
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {
return null;
}
@Override
public Properties getProps() { return null;}
@Override
public void init(Properties properties) {}
}
3、 按照官方文档配置
在项目的resources目录下建立文件夹META-INF/services,并在该文件夹下创建文件 org.apache.shardingsphere.sharding.spi.ShardingAlgorithm, 文件内写上我们自定义的分片算法:com.gingko.datasharding.config.CustomShardingAlgorithm
4、测试类测试
运行后,发现user_id包含admin的都在表 t_order_0中,其他的在表 t_order_1中,符合预期。