Sharding-jdbc学习笔记:基础概念和示例

1、前言

  随着时间和业务的发展,数据库中的表会越来越多,表中的数据也会越来越多,带来的问题就是对于数据的操作会越来越慢。当遇见这种情况,如何解决这些问题呢?提升单台服务器的配置?但是单台服务器的资源毕竟有限,最终数据库的数量和数据处理能力还是会遇到瓶颈,再者无限提升服务器配置也会急剧增加硬件成本。还有什么方法可以解决这些问题,这就是今天我们要讨论的分库分表

  分库分表主要是为了解决互联网应用的大数据量存储问题,分库分表通常分为:垂直划分、水平划分。

  1. 垂直划分
    按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。
    在这里插入图片描述
  2. 水平划分
    水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。
    在这里插入图片描述

2、主角:Sharding-jdbc

  Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
  Sharding-jdbc是ShardingSphere中一个模块,定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 支持任意实现JDBC规范的数据库。目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。
    在这里插入图片描述

3、核心概念

  • 逻辑表(LogicTable):水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
  • 真实表(ActualTable):在分片的数据库中真实存在的物理表。即上个示例中的t_order_0到t_order_9。
  • 数据节点(DataNode):数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。
  • 动态表(DynamicTable):逻辑表和真实表不一定需要在配置规则中静态配置。比如按照日期分片的场景,真实表的名称随着时间的推移会产生变化。此类需求Sharding-JDBC是支持的,不过目前配置并不友好,会在新版本中提升。
  • 广播表
    指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
  • 绑定表(BindingTable):指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中t_order在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item表的分片计算将会使用t_order的条件。故绑定表之间的分区键要完全相同。

  • 分片键
    用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。

  • 分片算法
    通过分片算法将数据分片,支持通过=、>=、<=、>、<、BETWEEN和IN分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。目前提供4种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

    1>、精确分片算法
    对应 PreciseShardingAlgorithm,用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景。需要配合 StandardShardingStrategy 使用。

    2>、范围分片算法
    对应 RangeShardingAlgorithm,用于处理使用单一键作为分片键的 BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合 StandardShardingStrategy 使用。

    3>、复合分片算法
    对应 ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。

    4>、Hint分片算法
    对应 HintShardingAlgorithm,用于处理使用 Hint 行分片的场景。需要配合 HintShardingStrategy 使用。

  • 分片策略
    包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供 5 种分片策略。

    1>、标准分片策略
    对应 StandardShardingStrategy。提供对 SQ L语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。 StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。 PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。 RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND, >, <, >=, <=分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 将按照全库路由处理。

    2>、复合分片策略
    对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。 ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

    3>、行表达式分片策略
    对应 InlineShardingStrategy。使用 Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持,只支持单分片键。 对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示 t_user 表根据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。 详情请参见行表达式。

    4>、Hint分片策略
    对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。

    5>、不分片策略
    对应 NoneShardingStrategy。不分片的策略。

  • SQL Hint
    对于分片字段非 SQL 决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。 例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。 详情请参见强制分片路由。

4、实践1-读写分离

  前面《MySql5.7 数据库安装及主从同步配置》中,我们知道了MySQL主从复制的环境搭建,这个是读写分离的基础。而在《基于MyCat实现的Mysql数据库的读写分离》中,我们又实现了基于MyCat的读写分离实现,这里我们实现通过Sharding-jdbc的读写分离,这里数据库环境还沿用了之前的配置。

4.1、pom.xml

  引入Sharding-jdbc依赖,完整内容如下:

<?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>

     <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.qriver.shardingjdbc</groupId>
    <artifactId>qriver-sharding-jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
    	<!--sharding-jdbc依赖-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
4.2、application.properties配置文件

  这里主要配置了主从数据库源,完整内容如下:

server.port=8080
        
# 数据源名称集合,对应下面数据源配置的名称
spring.shardingsphere.datasource.names=master,slave

# 主数据源
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://192.168.1.8:3306/test?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456

# 从数据源
spring.shardingsphere.datasource.slave.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave.url=jdbc:mysql://192.168.1.9:3306/test?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.slave.username=root
spring.shardingsphere.datasource.slave.password=123456

# 用于配置从库负载均衡算法类型,可选值:ROUND_ROBIN(轮询),RANDOM(随机),(这里的demo,只使用了一个从数据库)
spring.shardingsphere.masterslave.load-balance-algorithm-type=round_robin
# 最终的数据源名称
spring.shardingsphere.masterslave.name=dataSource
# 主库数据源名称
spring.shardingsphere.masterslave.master-data-source-name=master
# 从库数据源名称列表,多个逗号分隔
spring.shardingsphere.masterslave.slave-data-source-names=slave

#配置Mybatis
mybatis.mapper-locations=classpath:mappings/**/*Mapper.xml
mybatis.type-aliases-package=com.qriver.shardingjdbc

#日志
logging.level.com.qriver.shardingjdbc=debug
4.3、业务实现

 &esmp;我们为了演示读写分离,我们这里主要实现一个insert方法,一个findAll方法。首先,entity类如下:

/**
 *  实体类
 */
public class DemoEntity implements Serializable {
    
    

    private int id;

    private String name;
	
	//省略setter or getter 方法
}

  Mapper映射接口:

@Mapper
public interface DemoMapper {
    
    

    public void insert(DemoEntity entity);

    public List<DemoEntity> findAll();
}

  Mapper映射接口对应的配置文件:

<mapper namespace="com.qriver.shardingjdbc.mapper.DemoMapper">

       <select id="findAll" resultType="com.qriver.shardingjdbc.entity.DemoEntity">
            SELECT    T.* FROM test T
       </select>
    
        <insert id="insert" parameterType="com.qriver.shardingjdbc.entity.DemoEntity">
            INSERT INTO test (id,name) VALUES (#{id},#{name})
        </insert>

</mapper>

  DemoService类:

@Service
public class DemoService {
    
    

    @Autowired
    private DemoMapper demoMapper;

    public void insert(DemoEntity entity){
    
    
        demoMapper.insert(entity);
    }

    public List<DemoEntity> findAll(){
    
    
        return demoMapper.findAll();
    }
}

4.4、测试类

  这里为了演示读写分离,我们取消了两个数据库间的同步。首先,通过执行insertTest方法,可以发现数据会插入到master数据库中,而执行findAllTest()方法时,读取的数据是slave数据库中的数据。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ShardingJdbcApplication.class)
public class DemoTest {
    
    

    @Autowired
    private DemoService demoService;

    @Test
    public void insertTest(){
    
    
        DemoEntity demo = new DemoEntity();
        demo.setId(16);
        demo.setName("test");
        demoService.insert(demo);
    }
    @Test
    public void findAllTest(){
    
    
        List<DemoEntity> list = demoService.findAll();

        for(int i=0;list !=null && i < list.size();i++){
    
    
            DemoEntity demo = list.get(i);
            System.out.println(demo.toString());
        }
    }
}

5、实践2-分库分表

  分库分表一般分以下情况:

  1. 同库不同表(同库水平拆分)
  2. 分库不分表(垂直拆分)
  3. 分库分表(不同库水平拆分)

 为了实现上述三种情况,我们还是使用前面基于Mycat实现的情况进行验证(创建三个表user、role和order。其中,user只在192.168.1.8上(数据库db8),且分成user_0、user_1两个表,role只在192.168.1.9上(db9),order在192.168.1.8/9上)。

数据库 user(逻辑表) role(逻辑表) org(逻辑表) order(逻辑表)
db8(192.168.1.8) user_0、user_1 org order_0、order_1
db9(192.168.1.9) role order_0、order_1
5.1、同库不同表

  这里主要实现在db_8这一个库中,把user表拆分成了user_0和user_1两个表。分表采用内置的算法。

# 数据源名称集合,对应下面数据源配置的名称
spring.shardingsphere.datasource.names=master

# 主数据源
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://192.168.1.8:3306/db_8?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456

# 分表配置
spring.shardingsphere.sharding.tables.user.actual-data-nodes=master.user_${
    
    0..1}

# inline 表达式(根据Id求余)
spring.shardingsphere.sharding.tables.user.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.user.table-strategy.inline.algorithm-expression=user_${
    
    id.longValue() % 2}
5.2、分库不分表

 &esmp;这里主要实现org落在db_8上,role落在db_9上。配置如下:

# 数据源名称集合,对应下面数据源配置的名称
spring.shardingsphere.datasource.names=db8,db9

# 数据源2
spring.shardingsphere.datasource.db8.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db8.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db8.url=jdbc:mysql://192.168.1.8:3306/db_8?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db8.username=root
spring.shardingsphere.datasource.db8.password=123456

# 数据源1
spring.shardingsphere.datasource.db9.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db9.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db9.url=jdbc:mysql://192.168.1.9:3306/db_9?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db9.username=root
spring.shardingsphere.datasource.db9.password=123456

# 绑定role表所在节点
spring.shardingsphere.sharding.tables.role.actual-data-nodes=db9.role

# 绑定org表所在节点
spring.shardingsphere.sharding.tables.org.actual-data-nodes=db8.org
5.3、分库分表

 &esmp;主要实现拆分order表,首先根据id,拆分数据库,然后根据f1字段拆分表,配置如下:

# 数据源名称集合,对应下面数据源配置的名称
spring.shardingsphere.datasource.names=db0,db1

# 数据源2
spring.shardingsphere.datasource.db0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db0.url=jdbc:mysql://192.168.1.8:3306/db_8?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db0.username=root
spring.shardingsphere.datasource.db0.password=123456

# 数据源1
spring.shardingsphere.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db1.url=jdbc:mysql://192.168.1.9:3306/db_9?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db1.username=root
spring.shardingsphere.datasource.db1.password=123456

# 分表配置
spring.shardingsphere.sharding.tables.order.actual-data-nodes=db$->{
    
    0..1}.order_$->{
    
    0..1}
spring.shardingsphere.sharding.tables.order.table-strategy.inline.sharding-column=f1
spring.shardingsphere.sharding.tables.order.table-strategy.inline.algorithm-expression=order_$->{
    
    f1 % 2}
#spring.shardingsphere.sharding.tables.order.key-generator.column=id
#spring.shardingsphere.sharding.tables.order.key-generator.type=SNOWFLAKE

# 自定义算法分库配置
#spring.shardingsphere.sharding.default-database-strategy.standard.sharding-column=id
#spring.shardingsphere.sharding.default-database-strategy.standard.precise-algorithm-class-name=xxx.MyPreciseShardingAlgorithm

# 分库配置
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=db$->{
    
    id % 2}

  在使用sharding-jdbc进行分库分表、读写分离操作时,主要是通过配置文件来完成的。其他业务逻辑的实现和普通的java逻辑是没有区别的。

参考

1、《ShardingSphere - 概览》

猜你喜欢

转载自blog.csdn.net/hou_ge/article/details/106409054
今日推荐