乐趣区

关于java:ShardingJDBC-分库分表真香

大家好,我是不才陈某~

这是《ShardingSphere 进阶》专栏的第一篇文章,介绍一下 Sharding-JDBC 实现分库分表的具体配置。

分库分表带来的问题

对于什么是分库分表这里不再细说了,置信大家都晓得,有不分明的能够看我之前的文章:聊聊分库分表

从繁多表、繁多库切分成多库、多表对于性能的晋升是必然的,然而同时也带来了一些问题。

1. 分布式事务问题

因为垂直分库、程度分库,将数据摊派在不同库中,甚至不同的服务器上,势必带来了分布式事务的问题。

对于单库单表的事务很好管制,分布式事务的管制却是十分头疼,然而好在当初曾经有成熟的解决方案,想要理解的能够看我之前的文章:七种分布式事务解决方案

2. 跨节点关联 join 问题

在切分之前关联查问非常简单,间接 SQL JOIN 便能解决,然而切分之后数据摊派在不同的节点上,此时 JOIN 就比拟麻烦了,因而切分之后尽量避免 JOIN。

解决这一问题的有些办法:

1、全局表

这种很好了解,对于一些全局须要关联的表能够在每个数据节点上都存储一份,个别是一些 数据字典表

全局表在 Sharding-JDBC 称之为播送表

2、字段冗余

这是一种典型的反范式设计,为了防止关联 JOIN,能够将一些冗余字段保留,比方订单表保留 userId 时,能够将 userName 也一并保留,这样就防止了和 User 表的关联 JOIN 了。

字段冗余这种计划存在数据一致性问题

3、数据组装

这种还是比拟好了解的,间接不应用 JOIN 关联,分两次查问,从第一次的后果集中找出关联数据的惟一标识,而后再次去查问,最初对失去的数据进行组装

须要进行手动组装,数据很大的状况对 CPU、内存有肯定的要求

4、绑定表

对于互相关联的数据节点,通过 分片规定 将其切分到同一个库中,这样就能够间接应用 SQL 的 JOIN 进行关联查问。

Sharding-JDBC 中称之为绑定表,比方订单表和用户表的绑定

3. 跨节点分页、排序、函数问题

对于跨数据节点进行分页、排序或者一些聚合函数,筛选进去的仅仅是针对以后节点,比方排序,仅仅可能保障在繁多数据节点上是有序,并不能保障在所有节点上都是有序的,须要将各个节点的数据的进行汇总从新手动排序。

Sharding-JDBC 正是 依照上述流程进行分页、排序、聚合

4. 全局主键避重问题

单库单表个别都是应用的自增主键,然而在切分之后每个自增主键将无奈应用,因为这样会导致数据主键反复,因而必须从新设计主键。

目前支流的分布式主键生成计划如下:

1、UUID

UUID 应该是大家最为相熟的一种计划,长处非常明显本地生成,性能高,毛病也很显著,太长了存储耗空间,查问也十分耗性能,另外 UUID 的无序性将会导致 InnoDB 下的数据地位变动。

2、Snowflake

Twitter 开源的由 64 位整数组成分布式 ID,性能较高,并且在单机上 递增

不再具体介绍,更多信息自行查找材料

3、UidGenerator

UidGenerator 是百度开源的分布式 ID 生成器,其基于雪花算法实现。具体参考:

https://github.com/baidu/uid-…

4、Leaf

Leaf 是美团开源的分布式 ID 生成器,能保障全局惟一,趋势递增,但须要依赖关系数据库、Zookeeper 等中间件。具体参考:

https://tech.meituan.com/2017…

5. 数据迁徙、扩容问题

当业务高速倒退,面临性能和存储的瓶颈时,才会思考分片设计,此时就不可避免的须要思考历史数据迁徙的问题。个别做法是先读出历史数据,而后按指定的分片规定再将数据写入到各个分片节点中。此外还须要依据以后的数据量和 QPS,以及业务倒退的速度,进行容量布局,推算出大略须要多少分片。

如果采纳数值范畴分片,只须要增加节点就能够进行扩容了,不须要对分片数据迁徙。如果采纳的是数值取模分片,则思考前期的扩容问题就绝对比拟麻烦。

分库分表尽管晋升了性能,然而在切分过程中肯定要思考上述总结的 5 种问题。

Sharding-JDBC 介绍

Sharding-JDBC 是当当网研发的开源分布式数据库中间件,从 3.0 开始 Sharding-JDBC 被蕴含在 Sharding-Sphere 中,之后该我的项目进入进入 Apache 孵化器,4.0 版本之后的版本为 Apache 版本。

ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,由 Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar(布局中)组成。

官网:https://shardingsphere.apache…

目前咱们只须要关注 Sharding-JDBC,前面的两种组件后文介绍。

Sharding-JDBC 的定位是一款轻量级 JAVA 框架,基于 JDBC 实现分库分表,通过 Sharding-JDBC 能够通明的拜访曾经通过分库、分表的数据源。

Sharding-JDBC 的个性如下:

  1. 实用于任何基于 Java 的 ORM 框架,如:Hibernate, Mybatis, Spring JDBC Template 或间接应用 JDBC。
  2. 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  3. 反对任意实现 JDBC 标准的数据库。目前反对 MySQL,Oracle,SQLServer 和 PostgreSQL。

Sharding-JDBC 中的一些概念

在介绍 Sharding-JDBC 实战之前须要理解其中的一些概念,如下:

1. 逻辑表

在对表进行分片后,一张表分成了 n 个表,比方订单表 t_order 分成如下三张表:t_order_1,t_order_2,t_order_3。

此时订单表的逻辑表就是t_order,Sharding-JDBC 在进行分片规定配置时针对的就是这张逻辑表

2. 实在表

上述 t_ordr_1,t_order_2,t_order_3 称之为 实在表

3. 数据节点

数据分片的最小单元,由数据源名称和表名称组成,比方:ds1.t_order_1

4. 分片键

用于分片的数据库字段,是将数据库 (表) 程度拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订

单主键为分片字段。SQL 中如果无分片字段,将执行全路由,性能较差。除了对单分片字段的反对,Sharding- Jdbc 也反对依据多个字段进行分片。

5. 分片算法

通过分片算法将数据分片,反对通过 =BETWEENIN 分片。

分片算法须要利用方开发者自行实现,可实现的灵 活度十分高。包含:准确分片算法 范畴分片算法 复合分片算法 等。例如:where order_id = ? 将采纳准确分片算法,where order_id in (?,?,?) 将采纳准确分片算法,where order_id BETWEEN ? and ? 将采纳范畴分片算 法,复合分片算法用于分片键有多个简单状况。

Sharding-JDBC 中的分片算法须要开发者依据业务自定义

6. 分片策略

蕴含分片键和分片算法,因为分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也 就是分片策略。

内置的分片策略大抵可分为尾数取模、哈希、范畴、标签、工夫等。由用户方配置的分片策略则更加灵便,罕用的应用行表达式配置分片策略,它采纳 Groovy 表达式示意,如: t_user_$->{u_id % 8} 示意 t_user 表依据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。

1、规范分片策略

规范分片策略实用于单分片键,此策略反对 PreciseShardingAlgorithmRangeShardingAlgorithm 两个分片算法。

其中 PreciseShardingAlgorithm 是必选的,用于解决 =IN 的分片。RangeShardingAlgorithm 是可选的,用于解决BETWEEN AND><>=<= 条件分片,如果不配置RangeShardingAlgorithm,SQL 中的条件等将依照全库路由解决。

2、复合分片策略

复合分片策略,同样反对对 SQL 语句中的 =><>=<=INBETWEEN AND 的分片操作。不同的是它反对多分片键,具体调配片细节齐全由利用开发者实现。

3、行表达式分片策略

行表达式分片策略,反对对 SQL 语句中的 =IN 的分片操作,但只反对单分片键。这种策略通常用于简略的分片,不须要自定义分片算法,能够间接在配置文件中接着写规定。

t_order_$->{t_order_id % 4} 代表 t_order 对其字段 t_order_id取模,拆分成 4 张表,而表名别离是t_order_0t_order_3

4、Hint 分片策略

Hint 分片策略,对应上边的 Hint 分片算法,通过指定分片健而非从 SQL中提取分片健的形式进行分片的策略。

7. 分布式主键生成策略

通过在客户端生成自增主键替换以数据库原生自增主键的形式,做到分布式主键无反复。

Sharding-JDBC 外部反对 UUID 和 Snowflake 生成分布式主键

Sharding-JDBC 实战

上述内容根本介绍了 Sharding-JDBC 的根本知识点,上面通过 Spring Boot + Sharding-JDBC 的形式实战演示一下。

1. Sharding-JDBC 的 pom 依赖

想要应用 Sharding-JDBC 只须要增加一个 maven 依赖即可,如下:

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>${sharding-sphere.version}</version>
</dependency>

笔者这里应用的版本为:4.1.1

源码曾经上传 GitHub,关注公号:码猿技术专栏,回复关键词:9532 获取!

2. 垂直分表、分库

垂直切分个别针对数据行数不大,然而单行的某些字段数据很大,表占用空间很大,检索的时候须要执行大量的 IO,重大升高性能,此时须要将拆分到另外一张表,且与原表是一对一的关系,这就是 垂直分表

比方商品表中的商品形容数据很大,重大影响查问性能,能够将商品形容这个字段独自抽离进去存储,这样就拆分成了两张表(垂直分表),如下图:

通过垂直分表性能失去了肯定水平的晋升,然而还没有达到要求,并且磁盘空间也快不够了,因为数据还是始终限 制在一台服务器,库内垂直分表只解决了繁多表数据量过大的问题,但没有将表散布到不同的服务器上,因而每个 表还是竞争同一个物理机的 CPU、内存、网络 IO、磁盘。

此时就须要进行垂直分库,如下之前是在独自的卖家库存储的,当初须要将商品的信息给垂直切分进来,分成了两个库:商品库product_db、店铺库shop_db

计划曾经有了,那么当初就须要用 Sharding-JDBC 去实现。

product_db 和 shop_db 的 SQL 会放在源码中,这里就不再贴了

Sharding-JDBC 应用非常简单,只须要在配置文件中指定数据源信息和切片规定即可实现分库分表。

这里反对三种配置,如下:

  • yml 配置文件
  • properties 配置文件
  • Java Config 编码配置

这里笔者应用的是第一种 yml 配置形式,具体配置如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    datasource:
      # 数据源,这里配置两个,别离是 ds1,ds2
      names: ds1,ds2
      # ds1 的配置信息,product_db1
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/product_db?useUnicode=true&characterEncoding=utf-8
        username: root
        password: Nov2014
      # ds2 的配置信息,shop_db
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/shop_db?useUnicode=true&characterEncoding=utf-8
        username: root
        password: Nov2014
    # 分片的配置
    sharding:
      # 表的分片策略
      tables:
          ## product_base 是逻辑表的名称
        product_base:
          # 数据节点配置,采纳 Groovy 表达式,切分之后的实在表所在的节点
          actual-data-nodes: ds$->{1}.product_base
          # 主键生成策略
          key-generator:
            # 主键
            column: product_id
            # 生成算法
            type: SNOWFLAKE
        product_description:
          # 数据节点配置,采纳 Groovy 表达式
          actual-data-nodes: ds$->{1}.product_description
        shop:
          # 数据节点配置,采纳 Groovy 表达式
          actual-data-nodes: ds$->{2}.shop
          # 主键生成策略
          key-generator:
            # 主键
            column: shop_id
            # 生成算法
            type: SNOWFLAKE
    props:
      sql:
        # 日志显示具体的 SQL
        show: true

上述配置非常简单,分为如下几个步骤:

1、数据源配置

因为垂直分库波及到 shop_db,product_db,必定是要配置两个数据源,如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    datasource:
      # 数据源,这里配置两个,别离是 ds1,ds2
      names: ds1,ds2
      # ds1 的配置信息,product_db1
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/product_db?useUnicode=true&characterEncoding=utf-8
        username: root
        password: Nov2014
      # ds2 的配置信息,shop_db
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/shop_db?useUnicode=true&characterEncoding=utf-8
        username: root
        password: Nov2014

这个很好了解,两个数据源名称别离为 ds1,ds2,本人任意取名,而后配置相干信息。

2、数据节点配置

这里数据节点很重要,你要通知 Sharding-JDBC 你要操作的那张表在哪个库中,对应表的名称。

上述波及到三张表,别离是shopproduct_baseproduct_description,因而须要配置三个数据节点,如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    # 分片的配置
    sharding:
      # 表的分片策略
      tables:
          ## product_base 是逻辑表的名称
        product_base:
          # 数据节点配置,采纳 Groovy 表达式,切分之后的实在表所在的节点
          actual-data-nodes: ds$->{1}.product_base
        product_description:
          # 数据节点配置,采纳 Groovy 表达式
          actual-data-nodes: ds$->{1}.product_description
        shop:
          # 数据节点配置,采纳 Groovy 表达式
          actual-data-nodes: ds$->{2}.shop

ds$->{1} 采纳的是 Groovy 表达式,示意 ds1

数据节点要具体到指定的数据库、表名。

spring.shardingsphere.sharding.default-data-source-name=ds1能够指定默认的数据源

3、主键生成策略

Sharding-JDBC 反对配置主键生成策略,比方应用雪花算法或者 UUID 形式,配置如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    # 分片的配置
    sharding:
      # 表的分片策略
      tables:
          ## product_base 是逻辑表的名称
        product_base:
          # 主键生成策略
          key-generator:
            # 主键
            column: product_id
            # 生成算法
            type: SNOWFLAKE
        shop:
          # 主键生成策略
          key-generator:
            # 主键
            column: shop_id
            # 生成算法
            type: SNOWFLAKE

SNOWFLAKE:雪花算法

配置实现后,向 product_base 插入一条数据,将会直接插入到 ds1 这个数据库中,demo 如下:

@Test
public void test1(){Product product = Product.builder()
     .name("Spring Cloud Alibaba 实战课程")
     .price(159L)
     .originAddress("码猿技术专栏")
     .shopId(1L)
     .build();
    productMapper.insertProductBase(product);
}

源码曾经上传 GitHub,关注公号:码猿技术专栏,回复关键词:9532 获取!

3. 程度分库

问题来了,当初有很多商家入驻,product_db 单库存储数据曾经超出预估,商品资源属于拜访十分频繁的资源,单台服务器曾经无奈撑持,此时就须要对其进行程度分库,将商品库拆分成两个数据库:product_db1、product_db2,如下:

product_db1+product_db2 数据合并则为残缺的商品数据

Sharding-JDBC 配置也很简略,只须要配置一下数据库的切分规定,配置规定如下:

# 分库策略,如何将一个逻辑表映射到多个数据源 
spring.shardingsphere.sharding.tables.< 逻辑表名称 >.database‐strategy.< 分片策略 >.< 分片策略属性名 >=

那么此时须要对 product_base、product_description 进行数据源切分,依照 对 2 取模 的形式,配置如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    # 分片的配置
    sharding:
      # 表的分片策略
      tables:
        product_base:
          database‐strategy:
            inline:
            ## 分片键
              sharding‐column: product_id
             ## 分片算法,内置的准确算法
              algorithm‐expression: ds$->{product_id%2+1}
        product_description:
          database‐strategy:
            inline:
            ## 分片键
              sharding‐column: product_id
              ## 分片算法,内置的准确算法
              algorithm‐expression: ds$->{product_id%2+1}

上述配置什么意思?

product_id 是偶数的将会存储在 product_db1 库中,奇数的存储在 product_db2

测试也很简略,咱们循环往数据库中插入 10 条商品数据,因为是雪花算法,因而应该有 5 条在 db1 库中,另外 5 条在 db2 中,单元测试如下:

@Test
public void test3(){for (int i = 0; i < 10; i++) {Product product = Product.builder()
        .name("Spring Cloud Alibaba 实战课程")
        .price(159L)
        .originAddress("码猿技术专栏")
        .shopId(1L)
        .build();
    productMapper.insertProductBase(product);
    productMapper.insertProductDescribe(product.getProductId(),"内容",product.getShopId());
  }
}

察看下控制台的 SQL,能够发现是不停的往 ds1 和 ds2 中进行插入数据,如下图:

数据库中的数据如下:

源码曾经上传 GitHub,关注公号:码猿技术专栏,回复关键词:9532 获取!

4. 程度分表

通过程度分库后,性能失去了晋升,然而通过一段时间后,商品的单表数据量急剧增长,查问十分慢,那么此时就须要对单表进行程度拆分了,如下图:

同样须要在 Sharding-JDBC 中配置分表的规定,如下:

# 分表策略,如何将一个逻辑表映射为多个理论表 
spring.shardingsphere.sharding.tables.< 逻辑表名称 >.table‐strategy.< 分片策略 >.< 分片策略属性名 >=

理论的配置如下:

spring:
  # Sharding-JDBC 的配置
  shardingsphere:
    # 分片的配置
    sharding:
      # 表的分片策略
      tables:
        product_base:
          # 数据节点配置,采纳 Groovy 表达式,分库分表策略,这样是 4 个节点,ds1.product_base_$_1/ds1.product_base_$_2/ds2.product_base_$_1/ds2.product_base_$_2
          actual-data-nodes: ds$->{1..2}.product_base_$->{1..2}
          # 分表策略
          table‐strategy:
            inline:
              # 分片键为店铺 ID
              sharding‐column: shop_id
              # 分片策略取模
              algorithm‐expression: product_base_$->{shop_id%2+1}
        product_description:
          # 数据节点配置,采纳 Groovy 表达式
          actual-data-nodes: ds$->{1..2}.product_description_$->{1..2}
          table‐strategy:
            inline:
              sharding‐column: shop_id
              algorithm‐expression: product_description_$->{shop_id%2+1}

这里须要留神的是:因为这里用了分库分表,那么数据节点肯定要配置对,比方 ds$->{1..2}.product_base_$->{1..2} 这里的表达式别离对应的是 4 个数据节点,如下:

  • ds1.product_base_$_1
  • ds1.product_base_$_2
  • ds2.product_base_$_1
  • ds2.product_base_$_2

因为篇幅无限,残缺的配置看源码

单元测试如下:

@Test
public void test4(){for (int i = 0; i < 10; i++) {Product product = Product.builder()
            .name("Spring Cloud Alibaba 实战课程")
            .price(159L)
            .originAddress("码猿技术专栏")
            .shopId((long)(new Random().nextInt(100)+1))
            .build();
            productMapper.insertProductBase(product);
            productMapper.insertProductDescribe(product.getProductId(),"内容",product.getShopId());
        }
    }

源码曾经上传 GitHub,关注公号:码猿技术专栏,回复关键词:9532 获取!

总结

本篇文章次要介绍了 Sharding-JDBC 分库分表实战根底内容,通过具体的案例和代码演示心愿能帮忙大家更好的了解。

最初说一句(别白嫖,求关注)

陈某每一篇文章都是精心输入,曾经写了 3 个专栏,整顿成PDF,获取形式如下:

  1. 《Spring Cloud 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Cloud 进阶 获取!
  2. 《Spring Boot 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Boot 进阶 获取!
  3. 《Mybatis 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Mybatis 进阶 获取!
退出移动版