ShardingSphere介绍

 Apache ShardingSphere是一款开源的分布式数据库中间件组成的生态圈。它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。

ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。

Sharding-JDBC:被定位为轻量级Java框架,在Java的JDBC层提供的额外服务,以jar包形式使用。 Sharding-Proxy:被定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 Sharding-Sidecar:被定位为Kubernetes或Mesos的云原生数据库代理,以DaemonSet的形式代理所有对数据库的访问。

Sharding-jdbc、Sharding-Proxy、Sharding-Sidecar之间的区别如下图所示:

Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架的使用。

  • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。

Sharding-JDBC主要功能:

  • 数据分片
  1. 分库、分表
  2. 读写分离
  3. 分片策略
  4. 分布式主键
  • 分布式事务
  1. 标准化的事务接口
  2. XA强一致性事务
  3. 柔性事务
  • 数据库治理
  1. 配置动态化
  2. 编排和治理
  3. 数据脱敏
  4. 可视化链路追踪

准备数据

数据库表: 两个数据库分别为lagou1,lagou2。两个库中都有position表,里面有id,name,salary,city四个字段。

maven工程,导入pom.xml:

1
2
3
4
5
 <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>

application.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#datasource
spring.shardingsphere.datasource.names=ds0,ds1

#配置数据库1
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lagou1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=000

#配置数据库2
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lagou2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=000

#sharding-database
#配置对应表中分库的列,此处以id来进行分库
spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
#分库的规则,对id以2求模,结果是0的去ds0,结果是1的去ds1
spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{id % 2}

编写测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class TestShardingDatabase {

@Autowired
private PositionRepository positionRepository;
@Test
public void testAdd(){
for (int i = 0; i < 20; i++) {
Position position=new Position();
position.setId(i);
position.setCity("beijing");
position.setName("lagou"+i);
position.setSalary("10000");
positionRepository.save(position);
}
}
}

运行结果:

上面配置的主键生成策略是数据库自己带的自增序列,我们可以设置不同的主键生成策略,比如uuid,雪花算法,或者自定义主键生成策略:

雪花算法

设置主键生成策略为shardingjdbc内置的雪花算法:

1
2
3
#id 设置主键生成策略为雪花算法
spring.shardingsphere.sharding.tables.position.key-generator.column=id
spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE

自定义主键生成策略

自定义主键生成器MyLagouId类实现ShardingKeyGenerator接口,重写里面的generateKey()及getType()方法。 其中generateKey()方法需要实现主键的生成策略,因为主键的生成策略比较复杂,必须保证唯一性,所以我们这里直接使用自带的雪花算法来模拟实现。 getType()方法返回自定义主键生成器的名称是"LAGOUKEY",后面在配置文件中配置主键生成策略时会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.lagou.id;

import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator;
import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;

import java.util.Properties;


public class MyLagouId implements ShardingKeyGenerator {
private SnowflakeShardingKeyGenerator snow=new SnowflakeShardingKeyGenerator();
@Override
public Comparable<?> generateKey() {
System.out.println("-----执行了自定义主键生成器----");
return snow.generateKey();
}

@Override
public String getType() {
return "LAGOUKEY";
}

@Override
public Properties getProperties() {
return null;
}

@Override
public void setProperties(Properties properties) {

}
}

使用spi的方式来使用自定义的主键生成策略: 在resources目录下新建 META-NF\services文件夹,下面新建名为 apache.shardingsphere.spi.keygen.ShardingKeyGenerator的文件,文件内容:

1
com.lagou.id.MyLagouId

文件名来源:

配置文件中置主键生成策略:

1
spring.shardingsphere.sharding.tables.position.key-generator.type=LAGOUKEY

执行测试:

效果和雪花算法一致:

两表关联操作

在实际生产中我们经常用到两表或者多张表关联的情况,对于某些大的文本字段,我们经常会分离出来一张单独的表。 比如position表中分离出一个description字段放入一张单独的position_detail表中,用position的id来与position_detail表中的pid相关联。

在position_detail表中有id,pid,description三个字段,position_detail中的pid和position中的id取值一样。

新建model及jpa的相关实现类,此处略去(详细见文末源码)。 在配置文件中配置:

1
2
3
4
5
spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=pid
spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds$->{pid % 2}

spring.shardingsphere.sharding.tables.position_detail.key-generator.column=id
spring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testAdd2(){
for (int i=1;i<=20;i++){
Position position = new Position();
position.setName("lagou"+i);
position.setSalary("1000000");
position.setCity("beijing");
positionRepository.save(position);

PositionDetail positionDetail = new PositionDetail();
positionDetail.setPid(position.getId());
positionDetail.setDescription("this is a message "+i);
positionDetailRepository.save(positionDetail);
}
}

结果:

继续进行测试对两表关联查询数据, 实现JPA的方法,写两表关联查询的sql方法:

1
2
3
4
5
6
7

public interface PositionRepository extends JpaRepository<Position,Long> {

@Query(nativeQuery = true,value = "select p.id,p.name,p.salary,p.city,pd.description from position p join position_detail pd on(p.id=pd.pid) where p.id=:id")
Object findPositionsById(@Param("id") long id);

}

写测试类,查询一个id在position的一个库中的数据:

1
2
3
4
5
6
    @Test
public void testLoad(){
Object object = positionRepository.findPositionsById(555515199944130561L);
Object[] position = (Object[])object;
System.out.println(position[0]+" "+position[1]+" "+position[2]+" "+position[3]+" "+position[4]);
}

运行结果:

1
2
3
2021-01-11 22:54:09.123  INFO 18324 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds1 ::: select p.id,p.name,p.salary,p.city,pd.description from position p join position_detail pd on(p.id=pd.pid) where p.id=? ::: [555515199944130561]

555515199944130561 lagou2 1000000 beijing this is a message 2

可以看到它只在同一个库中进行了查询。为了防止跨库操作,我们平时设计数据库分库分表的时候都要注意将同类数据放到同一个库中。

注意事项

在写单元测试的时候,执行单元测试会出现执行多次的情况,搞得我头疼了好久。具体原因是单元测试时会先build进行编译,而build的时候会执行你项目中的所有加了@Test的方法。

解决方法:

在pom中添加相应排除测试的依赖,

1
2
3
4
5
6
7
8
9
10
11
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>

源码地址

源码地址: shardingsphere学习demo