sharding-jbdc入门

1.简介

1.1 分库分表是什么

​ 电商平台,在开发中涉及店铺、商品的相关业务,设计如下数据库

sharding-jbdc入门_第1张图片

通过以下SQL能够获取到商品相关的店铺信息、地理区域信息:

SELECT p.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [店铺信息] r ON p.[产地] = r.[地理区域编码]
LEFT JOIN [店铺信息] s ON p.id  = s.[所属店铺]
WHERE p.id = ?

形成类似一下展示:

sharding-jbdc入门_第2张图片

随着系统商品的发展,数据库中的数据量猛增,访问性能也变慢了,优化迫在眉睫。分析一下问题出现在哪?关系型数据库本身就比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引、做很多操作时性能仍然下降严重。

​ 方案1:

​ 通过提升服务器硬件能力来体高处理能力,比增加存储容量、CPU等,这种方案成本很高,并且如果瓶颈在mysql本身那么提高硬件也是有限的。

方案2:

​ 把数据分散到不同的数据库中,使得单一数据库的数据量变小来缓解数据库的性能问题, 从而达到提升数据库性能的目的。如下图:将电商数据库拆分为若干独立的数据库,并且对于大表也拆分为若干小表,通过这种数据库拆分的方法来解决数据库的性能问题。

sharding-jbdc入门_第3张图片

分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题,将原来独立的数据库拆分成若干数据库组成,将数据大表拆分成若干数据组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的。

1.2 分库分表的方式

​ 分库分表包括分库和分表两种,在生产中通常包括:垂直分库水平分库垂直分表水平分表四种方式。

1.2.1.垂直分表

​ 下边通过一个商品查询案例讲解垂直分表:

​ 通常在商品列表中是不显示山商品详情信息的,如下图:

sharding-jbdc入门_第4张图片

用户在浏览器列表时,只有对某商品感兴趣时才会查看该商品的详细描述。因此,商品信息中的商品名称、商品图片、商品价格等其他字段数据访问频次较高。

​ 由于这两种数据的特性不一样,因此他考虑将商品信息拆分如下:

将访问频次低的商品描述信息单独存放在一张表中,访问频次较高的商品基本信息单独放在一张表中。

sharding-jbdc入门_第5张图片

商品列表可采用以下sql:

SELECT P.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [地理区域] ON p.[产地] = r.[地理区域编码]
LEFT JOIN [店铺信息] ON p.id = s.[所属店铺]
WHERE ... ORDER BY ... LIMIT

需要获取商品描述时,再通过以下sql获取:

SELECT *
FROM [商品描述]
WHERE [商品ID] = ?

这就叫做垂直分表

垂直分表定义:将一个表按照字段分成多表,每个表存储其中一部分字段。

它带来的提升是:

1.为了避免IO争抢并减少锁表的几率,查看详情的用户与商品信息浏览互不影响

2.充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累。

​ 一般来说,某业务实体中的各个数据项的访问频次是不一样的,部分数据项可能占用存储空间比较大的BLOB或是TEXT。例如上列中的商品描述。所以,当表数据量很大时,可以将表按字段切开,将热门字段,冷门字段分开放置再不同库中,这些库可以放在不同的存储设备上,避免IO争抢。垂直切分带来的性能提升主要集中在热门数据的操作效率上,而磁盘争用情况减少。

通常我们按照以下原则进行垂直拆分:

​ 1.把不常用的字段单独放入一张表;

​ 2.把text,blog等大字段拆分出来放在附表中;

1.2.2.垂直分库

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

​ 经过思考,他把原有SELLER_DB(卖家库),分为了PRODUCT_DB(商品库)和STORE_DB(店铺库),并把这两个库分散到不同的服务器,如下图:

sharding-jbdc入门_第6张图片

由于商品信息商品描述业务耦合度较高,因此一起被存放在PRODUCT_DB(商品库);而店铺信息相对独立,因此单独被存放在STORE_DB(店铺库)。

​ 这一步优化就叫做垂直分库

垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。

它带来的提升是:

  • 解决业务层面的耦合,业务清晰
  • 能对不同业务的数据进行分级管理、维护、监控、拓展等
  • 高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈
  • 垂直分库通过将表按业务分类,但是依然没有解决单数据量过大的问题。

1.2.3.水平分库

​ 经过垂直分库后,数据库性能问题得到了一定层度的解决,但是随着业务量的增长,PRODUCT_DB(商品库)单库存储已经超出预估。粗略估计,目前有假设有8w店铺,每个店铺平均150个不同规格的商品,在算上增长,那商品数量的往1500w+上预估,并且PRODUCT_DB(商品库)属于访问非常频繁的资源,单台服务器已经无法支撑。此时此刻如何优化?

​ 再次分库?但是从业务角度分析,目前情况已经无法再次垂直分库。

​ 尝试水平分库,将店铺ID单数的和店铺ID为双数的商品信息分别在放在两个库中。

sharding-jbdc入门_第7张图片

​ 也就是说,要操作某条数据,先分析这条数据所属的店铺ID。如果店铺ID为双数 ,将此操作映射到PRODUCT_DB1(商品库1);如果店铺ID为单数,将操作映射到PRODUCT_DB2(商品库2)。此操作要访问数据库名称的表达式PRODUCT_DB[商品库ID%2 +1]

​ 这进一步优化叫做水平分库

水平分库是把同一个表是数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。

它带来的提升是:

  • 解决了单库大数据,高并发的性能的瓶颈
  • 提高了系统的稳定性及可用性。

1.2.4.水平分表

按照水平分表的思路对他把PRODUCT_DB_X(商品库)内的表也可以进行水平拆分,其目的也是为解决单表数据量大的问题,如下图:

sharding-jbdc入门_第8张图片

与水平分库的思路类似,不过这次操作的目标是表,商品信息及商品描述被拆分了两层。如果商品ID为双数,将此操作映射至商品信息1表;如果商品ID为单数,将此操作映射至商品信息2表。此操作要访问名称的表达式为商品信息[商品ID%2+1]

​ 这进一步优化叫做水平分表

水平分表是在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。

​ 它带来的提升是:

  • 优化单一表数据量过大而产生的性能问题
  • 避免IO争抢并减少锁表的几率

​ 库内的水平分表,解决了单一表数据量过大的问题,分出来的小表中只包含一部分数据,从而进行使得单个表的数据量变小,提高检索性能。

1.2.5.小结

本章介绍了分库分表的各种方式,他们分别是垂直分表、垂直分库、水平分库和水平分表:

垂直分表:可以把一个宽表的字段访问频次、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将的得不偿失。

垂直分库:可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而是访问压力被多服务器负载,大大提升性能,同时能提高整体叫否的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。

水平分库:可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题。

水平分表:可以把一个表的数据(按数据行)分到多个同一个数据库中的多个表中,每个表只有这个表的部分数据,这样做能小幅度提升性能,它仅仅作为水平库的一个补充优化。

一般来说,在系统设计阶段就应该根据业务耦合松紧原确定垂直分库,垂直分表,方案,在数据量及访问压力不是特别大的情况,首先考虑缓存,读写分离、索引技术等方案,若数据量极大,且持续增长,在考虑水平分库,水平分表。

1.3 分库分表带来的问题

​ 分库分表能有效的缓解了单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。

1.3.1.事务一致性问题

​ 由于分库分表把数据分布在不同库甚至不同服务器,不可避免带来分布式事务问题。

1.3.2.跨节点关联查询

在没有分库前,我们索引商品时可以通过以下SQL对比店铺信息进行关联查询;

SELECT p.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [地理区域] r ON p.[产地] = r.[地理区域编码]
LEFT JOIN [店铺信息] s ON p.id = s.[所属店铺]
WHERE ... ORDER BY ... LIMIT ...

但垂直分库后**[商品信息][店铺信息]**不在一个数据库中,甚至不在一台服务器,无法进行关联查询。可将原关联查询分为两次查询,第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据,最后将获得到的数据进行拼接。

1.3.3.跨节点分页、排序函数

​ 跨节点多库进行查询时,limit分页、order by 排序等问题,就变得比较复杂了。需要现在不同的分片节点中将数据进行排序并返回,然后将返回的结果集进行汇总和再次排序。

​ 如,进行水平分库后的商品库,按ID倒序排序分页,取第一页:

sharding-jbdc入门_第9张图片

sharding-jbdc入门_第10张图片

​ 以上流程是取第一页的数据,性能影响不大,但是由于商品信息的分布在各数据库的数据可能是随机的,如果是取第N页数据都取出来合并,再进行整体的排序,操作效率可想而知。所以请求页数越大,系统的性能也会越差。

​ 在使用Max、Min、Sum、Count之类的函数进行计算的时候,与排序分页同理,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。

1.3.4.主键避重

​ 在分库分表环境中,由于表中数据同时存在不同的数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。

sharding-jbdc入门_第11张图片

1.3.5.公共表

​ 实际的应用场景中,参数表,数据字典等都是数据量较小,变动少,而且属于高频联合查询的依赖表。列子中地理区域表也属于此类型。

​ 可以将分库分表后之后,数据被分散在不同的数据库,服务器。因此,对数据的操作也就无法通过常规方式完成,并且它还带来了一系列问题。好在,这些问题不是所有都需要我们在应用层面上解决,市面上有很多中间件可供我们选择,其中Sharding-JDBC使用流行度较高,我们来了解一下它。

1.4 Sharding-JDBC介绍

1.4.1.Sharding-JDBC介绍

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

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

官方地址:https://shardingsphere.apache.org

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

​ Sharding-JDBC的核心功能数据分片读写分离,通过Sharding-JDBC,应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源,而不用关心数据源的数量以及数据如何分布。

适用于任何基于java的ORM框架,如:Hibernate,Mybatis,SpringJDBC Template或者直接使用JDBC。

基于任何第三方的数据库连接池,如:DBCP,C3P0,BoneCP,Druid,HikariCP等。

支持任意实现JDBC规范的数据库。目前支持MYSQL,Oracle,SQLServer和PostgreSQL。

sharding-jbdc入门_第12张图片

​ 上图展示了Sharding-jdbc的工作方式,使用Sharding-jdbc前需要人工对数据库进行分库分表,在应用程序中加入Sharding-jdbc的jar包,应用程序通过Sharding-jdbc操作分库分表后的数据库和数据表,由于Sharding-jdbc是对jdbc驱动的增强,使用Sharding-jdbc就像使用jdbc驱动一样,在应用程序中事无需指定具体要操作的分库和分表的。

1.4.2.与jdbc性能对比

1.性能损耗测试:服务器资源充足、并发数相同,比较JDBC和Sharding-jdbc性能损耗,Sharding-jdbc相对JDBC损耗不超过7%。

基准测试性能比

业务场景 JDBC Sharding-JDBC1.5.2 Sharding-JDBC1.5.2/JDBC损耗
单库单表查询 493 470 4.7%
单库单表更新 6682 6303 5.7%
单库单表插入 6855 6375 7%
业务场景 业务平响应时间(ms) 业务TPS
JDBC单库单表查询 7 493
Sharding-JDBC单库单表查询 8 470

2.性能对比测试:服务器资源使用到极限,相同的场景JDBC与Sharding-JDBC的吞吐量相当。

3.性能测对比:服务器资源使用到极限,Sharding-JDBC采用分库分表后,Sharding-JDBC吞吐量较JDBC不分库分表有接近2倍的提升。

JDBC单库两库表与Sharding-JDBC两库各两表对比

业务场景 JDBC单库两表 Sharding-JDBC两库各两表 性能提升至
查询 1736 3331 192%
更新 9170 17997 196%
插入 11574 23043 199%

JDBC单库单表与Sharding-JDBC两库各一表对比

业务场景 JDBC单库单表 Sharding-JDBC两库各一表 性能提升至
查询 1586 2944 185%
更新 9548 18561 194%
插入 11182 21414 192%

2.Sharding-JDBC快速入门

2.1 需求说明

本章节使用Sharding-JDBC完成对订单表的水平分表,通过快速入门程序开发,快速体验Sharding-JDBC的使用方法。

人工创建两张表,t_order_1和t_order_2,这两张表事订单表拆分后的表,通过Sharding-JDBC向订单表插入数据,按照一定的分片规则,主键为偶数的进入t_order_1,另一部分数据进入t_order_2,通过Sharding-JDBC查询数据,根据SQL语句的内容从t_order_1或t_order_2查询数据。

2.2 环境配置

2.2.1.环境说明

  • 操作系统: win10
  • 数据库:MySQL-8.0.27
  • JDK:64位 jdk1.8
  • 应用框架:spring-boot-2.3.5.RELEASE mybatis-plus-3.5.1
  • Sharding-JDBC: Sharding-jdbc-spring-boot-start-4.1.1

2.2.2.创建数库

创建订单order_db

CREATE DATABASE order_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci

在order_db中创建t_order_1和t_order_2表

DROP TABLE IF EXISTS 't_order_1'
CREATE TABLE `t_order_1` (
  `order_id` bigint NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint NOT NULL COMMENT '下单用用户id',
  `status` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

DROP TABLE IF EXISTS 't_order_2'
CREATE TABLE `t_order_2` (
  `order_id` bigint NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint NOT NULL COMMENT '下单用用户id',
  `status` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

2.2.3.引入依赖

引入Sharding-jdbc和SpringBoot整合的jar包:

<dependency>
    <groupId>org.apache.shardingspheregroupId>
    <artifactId>sharding-jdbc-spring-boot-starterartifactId>
    <version>4.1.1version>
dependency>

2.3 编写程序

2.3.1.分片规则配置

具体springboot相关依赖及配置参考资料中dbsharding/sharding-jdbc-simple工程,本指引只说明与sharding-jdbc相关的内容。

#配置数据源
spring:
 shardingsphere:
   datasource:
     names: m1  #数据库
     m1:  
       type: com.alibaba.druid.pool.DruidDataSource
       driver-class-name: com.mysql.cj.jdbc.Driver
       username: root
       data-password: 123456
       url: jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
       
#指定t_order表的数据分布情况,配置数据节点
sharding:
     tables:
       t.order:   #逻辑表名
         key-generator:
           column: order_id  #主键
           type: SNOWFLAKE  #主键策略 雪花算法
         actual-data-nodes: m1.t_order_$->(1..2)  #表分布情况
         table-strategy: # 分表策略
           inline:
             sharding-column: order_id   # 分片路由标准
             algorithm-expression: t_order_$->{order_id % 2 +1} # 分片算法表达式



props:
     sql:
       show: true  #打印sql
  1. 首先定义数据源m1,并对m1进行实际的参数配置。
  2. 指定t_order表的数据分布情况,他分布在m1.t_order_1和m1.t_order_2
  3. 指定指定t_order表的主键生成策略为SNOWFLAKE,SNOWFLAKE是一种分布式自增算法,保证id全局唯一。
  4. 定义t_order分表策略,order_id为偶数的数据落在t_order_1,为基数落在t_order_2,分表策略的表达式为t_order_$->{order_id % 2 +1}

2.3.2.数据操作

    @Resource
    private IOrderMapper orderMapper;
    @Override
    public int addOrder(Order order) {
       return orderMapper.insert(order);
    }

2.4 流程分析

通过日志分析,Sharding-JDBC在拿到用户要执行的sql之后干了哪些事儿;

(1)解析sql,获取片键值,在本例中是order_id

(2)Sharding-JDBC通过规则配置t_order_$->{order_id % 2 +1} ,知道了当order_id为偶数时,应该往t_order_1插入数据,为奇数时,往t_order_2插入数据。

(3)于是Sharding-JDBC根据order_id的值改写成sql语句,改写后的sql语句是真实所要执行的sql语句。

(4)执行改写后的真实sql语句

(5)将所有真正执行sql的结果进行汇总并返回。

3.Sharding-JDBC执行原理

3.1 基本概念

在了解Sharding-JDBC的执行原理前,需要了解以下概念:

逻辑表

水平拆分的数据表的总数。例:订单数据表主键尾数拆分为10张表,分别是t_order_0、t_order_1到t_order_9,他们的逻辑表名为t_order。

真实表

在分片的数据表中真实存在的物理表。即上个实例中的t_order_0到t_order_9。

数据节点

数据分片的最小物理单元。由数据源名称和数据表组成,例:ds_0_order_0。

绑定表

指分片规则一致的主表和子表。例: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); 

广播表

指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

分片键

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

分片算法

通过分片算法数据分片,支持通过=BETEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。包括精确分片算法、范围分片算法、复合分片算法,例如:where order_id =?将采用精确分片算法,where order_id in(?,?,?)将采用精确分片算法,where order_id BETEEN ? and ? 将采用范围分片算法,复合算法用于分片键多复杂情况。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分键+分片算法,也就是分片策略。内置的分片策略大致可分为尾数取模。哈希、范围、标签、时间等。由用户方配置的分片策略规则更加灵活,常用的使用行表达式配置分片策略,它采用Groovy表达式表示,如: t_user_$->(u-id % 8)表示 t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7.

自增主键生成策略

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

3.2 SQL解析

​ 当Sharing-JDBC接受到一条SQL语句时,会陆续执行SQL解析=>查询优化=>SQL路由=>SQL改写=>SQL执行=>结果归并,最终返回执行结果。

sharding-jbdc入门_第13张图片

​ SQL解析过程分为语法解析语法分析。词法解析器用于将SQL拆解为不可再分的原子符号,称为Token。并根据不同数据库方言所提供数据字典,将其归类为关键字,表达式,字面量和操作符。再使用语法解析器将SQL转为抽象语法树。

例如,以下SQL:

SELECT id,name FROM t_user WHERE status = 'ACTIVE' AND age > 18

解析之后的为抽象语法树见下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0KtlhMA-1655709179926)(https://i0.hdslb.com/bfs/album/d362c6abd6de4c548f46709d2756fdc1956b7092.png)]

3.3 SQL路由

​ SQL路由就是针对逻辑表的数据操作到对数据节点操作的过程。

​ 根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的SQL,根据分片键操作符不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片的操作符是IN)和范围路由(分片键的操作符是BETWEEN),不携带分片键的SQL则采用广播路由。根据分片键进行路由场景可分为直接路由、标准路由、笛卡尔路由等。

标准路由

标准路由是Sharding-JDBC最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅

当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落在唯一的库(表),因此一条逻辑SQL最终可能拆分为多条用于执行的真实SQL。举例说明,如果按照order_id的奇数和偶数进行数据分片,一个单表查询的SQL如下:

SELECT * FROM t_order WHERE order_id IN(1,2);

那么路由的结果为:

SELECT * FROM t_order_0 WHERE order_id IN(1,2);
SELECT * FROM t_order_1 WHERE order_id IN(1,2);

绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的SQL如下:

SELECT * FROM t_order o JOIN t_order_item i  ON o.order_id = i.order_id  WHERE order_id IN(1,2); 

可以看到,SQL拆分的数目与单表是一致的。

笛卡尔路由

笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询所需要拆解为笛卡尔积组合执行。如果上个实例中的SQL并为配置绑定表关系,那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i  ON o.order_id = i.order_id  WHERE order_id IN(1,2); 

SELECT * FROM t_order_0 o JOIN t_order_item_1 i  ON o.order_id = i.order_id  WHERE order_id IN(1,2); 

SELECT * FROM t_order_1 o JOIN t_order_item_0 i  ON o.order_id = i.order_id  WHERE order_id IN(1,2); 

SELECT * FROM t_order_1 o JOIN t_order_item_1 i  ON o.order_id = i.order_id  WHERE order_id IN(1,2); 

笛卡尔路由查询性能较低,需谨慎使用。

全库表路由

对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型又可以划分为全库表路由。全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。其中全库表路由用于处理对数据库中与逻辑表相关的所有真实表的操作,主要包括不带分片的DQL(数据查询)和DML(数据操纵),以及DDL(数据定义)等。例如:

SELECT * FROM t_order WHERE good_prority IN(1,10);

则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能够匹配得上则执行。路由后成为

SELECT * FROM t_order_0 WHERE good_prority IN(1,10);
SELECT * FROM t_order_1 WHERE good_prority IN(1,10);
SELECT * FROM t_order_2 WHERE good_prority IN(1,10);
SELECT * FROM t_order_3 WHERE good_prority IN(1,10);

3.4 SQL改写

​ Sharding-JDBC采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。它不是简单地将SQL通过JDBC直接发送到数据源执行;也并非直接将执行请求放入线程池去并发执行。它更关注平衡数据源连接创建以及内存占用所产生的消耗,以及最大限度地合理利并发等问题。执行引擎的目标是自动化的平衡资源控制与执行效率,他能在以下两种模式自适应切换:

内存限制模式

​ 使用此模式的前提是,Sharding-JDBC对一次操作所消耗的数据库连接数量不做限制。如果实际执行的SQL需要对某数据库实例中的200张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。

连接限制模式

​ 使用此模式的前提是,Sharding-jdbc严格控制一次操作所耗费的数据库连接数量。如果实际执行的SQL需要对某数据库实例中的200张表做操作,那么只会创建唯一的数据库连接,并对其200张表串行处理。如果一次操作中的分片散落在不同的数据库,仍然采用多线程处理不同库的操作,但每个库的每次操作仍然只创建一个唯一的数据库连接。

​ 内存限制模式适用于OLAP操作,可以通过放宽对数据连接的限制提升系统吞吐量;连接限制模式适用于OLTP操作,OLTP通常带有分片键,会路由到单一的分片,因此严格控制数据库连接,以保证在线系统数据库资源能够被更多的应用所使用,是明智的选择。

3.6 结果归并

​ 将从各个数据节点获取多个数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。

​ Sharding-JDBC支持的结果归并从功能上可以遍。历排序分组分页聚合5种类型,她们是组合而非互斥的关系。

​ 归并引擎的整体结构划分如下图。

sharding-jbdc入门_第14张图片

​ 结果归并从结构划分可分为流式归并装饰归并。流式归并和内存归并是互斥的,装饰者归并并可以在流式归并和内存归并之上做进一步的处理。

内存归并很容易理解,他是将所有的分片结果集的数据都遍历并存储在内存中,再通过统一的分组,排序以及聚合等计算之后,再将其封装成为逐条访问的数据结果集返回。

​ 流式归并是指每一次从数据库结果集中获取到的数据,都能够通过游标逐条的方式返回的正确的单条数据,它与数据库原生的返回结果集的方式最为契合。

​ 下边举例说明排序归并的过程,如下图是一个通过分数进行排序的示例图,它采用流式归并方式。图中展示了3张表返回的数据结果集,每个数据集已经根据分数排序完毕,但3个数据结果集之间是无序的。将3个数据集的当前游标指向的数据值进行排序,并放入优先级队列,t_score_0的第一个数据值最大,t_score_1的方式排列队列。

sharding-jbdc入门_第15张图片

下图则展现了进行next调用的时候,排序归并是如何进行的。通过图中我们可以看到,当进行第一次next调用时,排在队列首位的t_score_0将会被弹出队列,并且将当前的游标指向的数据值(也就是1000) 返回值查询用户端,并且将游标下移一位之后,重新放入优先级队列。而优先级队列也会根据t_score_0的当前数据结果集指向游标数值(这里是90)进行排序,根据当前的数值,t_score_0排列的最后一位。之前队列中排名第二的t_score_1的数据结果集则自动排在了队列首位。

在进行第二次next时,只需要将目前排列在队列首位的t_score_1弹出队列,并且将其数据结果集游标指向的值返回客户端,并下移游标,继续加入队列排队,以此类推。当一个结果集中已经没有数据了,则无需再次加入队列。

可以看到,对于每个数据结果集中的数据有序,而多数据结果集整体无序的情况下,Sharing-JDBC无需将所有的数据都加载至内存即可排序。它使用的是流式归并的方式,每次next仅获取唯一正确的一条数据,极大的节省了内存的消耗。

装饰者归并是对所有的结果集归并进行统一的功能增强,比如归并时间需要聚合SUM前,在进行聚合计算前,都会通过内存归并或流式归并查询结果集。因此,聚合归并是在之前介绍的归并类型之上追加的归并能力,即装饰者模式。

3.7 总结

通过以上内容介绍,我们已经了解到Sharding-JDBC基础概念、核心功能以及执行原理。

基础概念:逻辑表、真实表、数据节点、绑定表、广播表、分片键、分片算法、分片策略、主键生成策略

核心功能: 数据分片、读写分离

执行流程:SQL解析 => 查询优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并

4.水平分表

​ 前面已经介绍过,水平分表是在同一个数据库内,把同一个数据按照一定规则拆到多个表中,在快速上手里,我们已经对水平分表进行实现,这里不再重复介绍。

5.水平分库

​ 前面已经介绍过,水平分库是把同一个表的数据按照一定的规则拆到不同的数据库中,每个库可以放在不同的服务器上。接下来看一下如何使用Sharding-JDBC实现水平分库,咱们继续对快速入门中的例子进行完善。

(1)将原有的order_db库拆分为order_db_1和order_db_2

(2)分片规则修改

由于数据库拆分两个,这里需要配置两个数据源。

分库需要配置分库的策略,和分表策略类似,通过分库策略实现数据操作针对分库的数据库进行操作。

#定义多数据源
spring:
  shardingsphere:
    datasource:
      names: m1,m2 #数据库
      m1:  
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        data-password: 123456
        url: jdbc:mysql://127.0.0.1:3306/order_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
      m2:  
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        data-password: 123456
        url: jdbc:mysql://127.0.0.1:3306/order_db_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true    
        
#分库策略,以user_id为分片键,分片策略为user_id % 2 +1,user_id为偶数操作m1数据源,否则操作m2。
 sharding:
      tables:
        t.order:   #逻辑表名
          table-strategy: # 分表策略
            inline:
              sharding-column: user_id   # 分片路由标准
              algorithm-expression: m$->{user_id % 2 +1} # 分片算法表达式
            key-generator: # 主键⽣成策略
              type: SNOWFLAKE
              column: order_id

        

分库策略定义方式如下:

 #分片策略,如何将一个逻辑表映射到多个数据源
 sharding:
      tables:
        <逻辑表名>:   
          database-strategy: 
            <分片策略>:
              <分片策略属性名>: 分片策略属性值

6.垂直分库

​ 前面介绍过,垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。下来看一下如何使用sharding-JDBC实现垂直分库。

(1)创建数据库

创建数据库user_db

CREATE DATABASE user_db CHARACTER SET utf8 COLLATE utf8_general_ci;

在user_db中创建t_user表

DROP TABLE IF EXITSTS t_user;
CREATE TABLE t_user (
    user_id bigint(20) NOT NULL COMMENT '用户id',
    fullname varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名',
    user_type char(1) DEFAULT NULL COMMENT '用户类型',
    PRIMARY KEY ('user_id') USING BTREE
)ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2) 在sharding-JDBC规则中修改

shardingsphere:
  datasource:
    names: m1,m2                                      # 数据源,这里为了方便直接使用库名的名称
    m1:
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/user_db_1?setUnicode=true&characterEncoding=utf8
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource   # Druid连接池
    m2:
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3307/user_db_2?setUnicode=true&characterEncoding=utf8
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource   # Druid连接池

  sharding:
      tables:
        t_user:   
          actual-data-nodes: m$->{1..2}.t_user_$->{1..2}        # 表的名字
          database-strategy:                                # 分库策略
            inline:
              sharding-column: user_id
              algorithm-expression: m$->{user_id % 2 +1}
           table-strategy:                                # 分库策略
            inline:
              sharding-column: user_id
              algorithm-expression: t_user_$->{user_id % 2 +1} #分表策略

7.公共表

​ 公共表属于系统中数据量较小,变动少,而且属于频繁联合查询的依赖表。参数表、数据字典表等属于此类型。可以将这类型表每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。接下来一下如何使用Sharding-JDBC实现公共类。

(1) 创建数据库

分别在user_db、order_db_1、order_db2中创建t_dict表:

DROP TABLE IF EXITSTS t_dict;
CREATE TABLE t_user (
    dict_id bigint(20) NOT NULL COMMENT '字典id',
    type varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典类型',
    code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典编码',
    value varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值',
    PRIMARY KEY ('dict_id') USING BTREE
)ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2) 在sharding-JDBC规则中修改

# 指定t_dict为公共表
    sharding:
      broadcast-tables: t_dict #公共表 广播表

8.读写分离

8.1 理解读写分离

​ 面对日益的系统访问量,数据库的吞吐量面临巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性质的增删改操作,从库负责处理查询操作,能够有效的避免由数据库更新导致的行锁,使得整个系统的查询性能得到极大的改善。

sharding-jbdc入门_第16张图片

​ 通过一主多从的配置方式,可以将查询请求均匀的分散到都多个数据副本,能够进一步的提升系统的处理能力。使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。

sharding-jbdc入门_第17张图片

​ 读写分离的数据节点的数据内容是一致的,而水平分片的每个数据节点的 数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统的性能。

ShardingJDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。它提供透明化读写分离,让使用方法尽量像使用一个数据库一样使用主从数据库集群。

sharding-jbdc入门_第18张图片

Sharding-JDBC提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用,同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性。Sharding-JDBC不提供主从数据库的数据同步功能,需要采用其他机制支持。

sharding-jbdc入门_第19张图片

​ 接下来需要,对上面例子中user_db进行读写分离实现。为了实现Sharding-JDBC的读写分离,首先,要进行mysql的主从同步配置。

8.2 mysql主从同步(windows)

主库:

[mysqld]
#开启日志
log-bin= mydql-bin
#设置服务id,主从不能一致
server-id= 1
#设置需要同步的数据库
binlog-do-db= user_db
#屏蔽系统同步
binlog-ignore-db= mysql
binlog-ignore-db= information_schema
binlog-ignore-db= performance_schema

从库:

[mysqld]
#开启日志
log-bin= mydql-bin
#设置服务id,主从不能一致
server-id= 2
#设置需要同步的数据库
binlog-do-db= user_db.%
#屏蔽系统同步
binlog-ignore-db= mysql.%
binlog-ignore-db= information_schema.%
binlog-ignore-db= performance_schema.%

重启主库和从库

net start [主服务器名]
net start [从服务器名mysqls1]

请注意,主从Mysql下的数据(data)目录下有个auto.cnf,文件中定义了uuid,要保证主从数据库实例的uuid不一样,建议直接删掉,重启服务后将重新生成

三、授权主从复制专用账号

#登录主库
mysql -h localhost -uroot -p123456
#授权主备复制专用账号
GARNT REPLICATION ON *.* TO 'db_sync'@'%' IDENTIFIED BY 'db_sync';
#刷新权限
FLUSH PRIVILEGES;
#确认位点,记录下文件名以及位点
show master status;

四、设置从库向主库同步数据、并检查链路

#登录从库
mysql -h localhost -uroot -p123456
#先停止同步
STOP SLAVE;
#修改从库指向主库,使用上一步记录的文件名以及位点
CHANGE MASTER TO
 master_host = 'localhsot',
 master_user = 'db_sync',
 master_password = 'db_sync',
 master_log_file = 'mysql-bin.000002',
  master_log_pos = '154';
#启动同步
START SLAVE;
#查看从库状态Slave_IO_Runing和Slave_SQL_Runing都为Yes说明同步成功,如果不为Yes,请检查error_log,然后排查相关异常。
show slave status\G

#注意 如果之前此备库已有主库指向 需要先执行以下命令清空
STOP SLAVE TO_THREAD FOR CHANNEL '';
reset slave all;

8.3 实现sharding-JDBC读写分离

(1) 在Sharding-JDBC规则中修改

#配置主从数据库
shardingsphere:
    datasource:
      names: m1,s1        # 主库:m1,从库:s1
      m1:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/user_db?setUnicode=true&characterEncoding=utf8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource   # Druid连接池
      s1:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/user_db?setUnicode=true&characterEncoding=utf8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource   # Druid连接池
        
    sharding:
      master-slave-rules:
        ds0:  # 主从逻辑数据源 定义 ds0 为uesr_db
          master-data-source-name: m1
          slave-data-source-name: s1
          			
      tables:  #分表策略,固定分配到ds0的真实表
        t_user:   
          actual-data-nodes: ds0.t_user        # 表的名字

你可能感兴趣的:(数据库,java,服务器)