ShardingSphere
1.1分库分表是啥?
简单来说。就是指通过某种特定的条件,将我们存放在同一个数据库中的数据分散到多个数据库(主机)上面,以达到分散单台设备负载的效果
-
分库分表解决的问题
分库分表的目的是为了结局由于数据库量过于大而导致数据库性能降低的问题,将原来的单体服务数据库进行拆分,将数据大表拆成若干份数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的 -
什么情况下需要分库分表
- 单体存储容量遇到瓶颈
- 连接数,处理能力到达上限
注意:
分库分表之前,需要根据项目的实际情况确定我们的数据量是不是够大。
并发量是不是够大。来决定是否分库分表;
数据量不够就不要分表,
单表数据量超过1000万或100G的时候,速度就会慢下来(官方测试),
1.2分库分表的方式
分库分表包括:垂直分库,垂直分表、水平分库、水平分表 四种方式
1.2.1 垂直分库
-
数据库中不同的表对应着不同的业务,垂直切分是指按照业务的不同将表进行分类,分布到不同的数据库上面
- 将数据库部署在不同的服务器上,从而达到服务器共同分摊压力的效果
- 将数据库部署在不同的服务器上,从而达到服务器共同分摊压力的效果
1.2.2 垂直分表
表中的字段太多却包含大字段的时候,在查询的时候对数据库IO、内存会受到影响,同时更新数据时,产生的binlog文件很大、MYSQL在主从同步时也会延迟的风险
-
将一个表按照字段拆成多个表,每个表存储其中一部分字段
-
对职位表进行垂直拆分,将职位基本信息放在一张表、将职位描述信息存放在另一张表
-
垂直拆分带来的一些提升
- 解决业务层面的耦合、业务清晰
- 能对不同业务的数据进行分级管理、维护、监控、扩展等
- 高并发场景下,垂直分库一定程度的提高访问性能
-
垂直拆分没有彻底解决单表数据量过大的问题
1.2.3 水平分库
- 将单表的数据切分到多个服务器上去,每个服务器具有相对库与表,只是表中数据结合不同。水平分库分表能够有效的解决单机和单库的性能瓶颈和压力、突破IO、连接数、硬件资源等瓶颈
- 简单讲就是根据表中的数据的逻辑关系、将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,例如将订单表 按照id是基数还是偶数,分别存储在不同的库中
1.2.4 水平分表
-
针对数据量巨大的单张表(比如订单表),按照规则把一张表的数据切分到多张表里面去。但是这些表还是在同一个数据库中,所有库级别的数据库操作还是有IO瓶颈
-
总结
- 垂直分布 :将一个表按照字段分成多表,每个表存储其中一部分字段
- 垂直分库:根据表的业务不同,分别存放在不同库中,这些表分别不部署在不同服务器
- 水平分库:把一张表的数据按照一定规则,分配到不同的数据库,每一个库只有这张表达部分字段
- 水平分表 :把一表的数据按照一定规则,分配到同一个数据库的多种表中,每个表只有这个表的部分数据
1.2.5 分库分表的规则
1) 水平分库规则
- 不跨库、不跨表,保证同一类的数据都放在同一个服务器上面
- 数据在切分之前,需要考虑如何高效的进行数据获取,如果每次查询都要跨越多个节点、就需要谨慎使用
2)水平分表规则
-
RANGE
- 时间:按照年、月、日去切分。例order_2020,order_00202009、order_20200901
- 地域:按照省或市切分,例如 order_beijing,order_shanghai
- 大小:从0到1000000一个表,例如10000001-20000000,每100万放到一个表
-
HASH
- 用户ID取模
3)不同的业务使用的切分规则不一样,就上面提到的切分规则,举例
-
用户表
- 范围法:以用户ID为划分依据,将数据水平切分到连个数据库实例,如:1到1000W在一张表,1000W-2000W 在一张表,这种情况出现单表的负载较高
- 按照用户ID HASH尽量保证用户数据均衡分到数据库中
如果在登录场景下,用户输入手机号和验证码进行登录,
这种情况,登陆时是不是需要扫描所有分库的信息?
最终方案:用户信息采用ID做切分处理,
同时存储用户ID和手机号的映射的关联表(新增一个关系表)
官表采用手机号进行切分。可以通过关系表根据手机号查询到对应的ID,在定位用户信息
-
流水表
- 时间维度:可以根据每天新增的流水来判断,选择按照年份分库,还是按照月份分库,甚至也可以按照日期分库
1.2.6 分库分表带来的问题及解决方案
关系型数据库在单机单库的情况下,比较容易出现性能瓶颈问题,分库分表可以有效解决这方面的问题,但是同时也会产生一些,比较棘手的问题
1)事务一致性问题
- 当我们需要更新的内容同时分布在不同的库时,不可避免的会产生跨库的事务问题,原来在一个数据库操作,本地事务就可以进行控制,分库之后,一个请求可能要访问多个数据库,如果保证事务的一致性,目前还没有简单的解决方案
2)跨节点关联问题
- 在分库之后,原来在一个库中的一些表,被分散到多个库,并且这些数据库可能还不在一台服务器,无法关联查询,解决这种关系查询,需要我们在代码层面进行控制,将关联查询拆分执行,然后再获取到的结果进行拼装
3)分页排序查询的问题
- 分库并行查询时,如果用到了分页每个库返回的结果集本身是无序的,只是将多个库中的数据先查出来,然后在根据排序字段在内存中进行排序,如果查询结果过大也是十分消耗资源的
4)主键避重问题
- 在分库分表的环境中,表中的数据存储在不同的数据库,主键自增无法保证ID不重复,需要单独设计全局主键
5)公共表的问题
- 不同的数据库,都需要崇公共表中获取数据,某一个数据库更新看公共表其他数据库的公共表需要进行同步
上面我们说了分库分表后可能会遇到一些问题,接下来带着这些问题,我们就来一起学习一下 Apache ShardingSphere
2.SharedingSphere
2.1 什么是ShardingSphere
Apache ShardingSphere 是一款分布式的数据库生态系统,可以将任意的数据库转换为分布式数据库,并通过数据分片、弹性伸缩,加密等功能对原来数据进行增强
官网:https://shardingsphere.apache.org/
Apache ShardingSphere 设计哲学为 Database Plus,皆在异构结构数据库上层的标准和生态。他关注如何充分合理的利用数据库的计算和存储能力,而并非实现一个全新的数据库,他站在数据库的上层角度,关注他们之间的协作多于数据库自身
Apache ShardingSphere它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独立的产品组成,他们均提供标准化的数据分配、分布式事务和数据库治理功能,可使用于如Java同构,异构语言,容器、云原生等各种多样化的应用场景
- Sharding-JDBC:被定位轻量级JAVA框架,在Java的 JDBC层提供的额外服务,以jar方式使用
- Sharding-Proxy:被定为透明化的数据库代理,向应用程序完全透明,可以直接当多Mysql客户端使用
- Sharding-Sidecar:被定为Kubernetes的云原生数据库代理,以守护进程的形式代理所有对数据库的访问(规划中)
Sharding-JDBC、Sharding-Proxy之间的区别如下:
Sharding-JDBC | Sharding-Proxy | |
---|---|---|
数据库 | 任意 | MySQL/PostgreSQL |
连接消耗数 | 高 | 低 |
异构语言 | 仅Java | 任意 |
性能 | 损耗低 | 损耗略高 |
无中心化 | 是 | 否 |
静态入口 | 无 | 有 |
异构式继面向对象编程思想又一种较新的编程思想,
面向服务编程,不用顾虑语言的差别,
提供规范的服务接口,我们无论使用什么语言,
就都可以访问使用了,大大提供了程序的复用率
Sharding-Proxy的优势在于对异构语言的支持,
以及为DBA提供可操作入口。
它可以屏蔽分库分表的复杂度,运维及开发人员仅面向proxy操作
很显然Sharding-JDBC只是客户端的一个工具包,可以理解为一个特殊的JDBC驱动包,所有分库分表逻辑均有业务方自己控制,所以他的功能相对灵活,支持的数据库非常多,但是对业务入侵大,需要业务方自己定义所以的分库分表逻辑
而Sharding-proxy是一个独立部署的服务,对业务方无入侵,业务方可以像用一个普通的MYSQL服务一样进行数据交互,基本上感觉不到后端分库分表逻辑的存在,到那时这也意味着功能会比较固定,能够支持的数据库比较少,两者各有优势
ShardingSphere项目状况如下:
ShardingSphere定位为关系型数据库的中间件,皆在充分合理的分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库
2.2 Sharding-JDBC介绍
Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务,它使用客户端之恋数据库,以jar包的方式提供服务,无需额外的配置和一来,可以理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架使用。
- 适用于任何基于Java的ORM框架, 比例:JPA、Hibernate、Mybatis,Spring JDBC Template,或直接使用JDBC
- 基于任何第三方的数据库连接池,如:DBCP,c3p0,Druid,HikariCP等
- 支持任意实现JDBC规范的数据库,目前支持MYSQL,Oracle,SQL Server,PostgreSQL
Sharding-JDBC主要功能
-
数据分片
- 分库,分表
- 读写分离
- 分片策略
- 分布式主键
-
分布式事务
- 标准化的事务接口
- XA强一致性事务
- 柔性数据
-
数据库治理
- 配置动态化
- 编排和治理
- 数据脱敏
- 可视化链路追踪
Sharding-JDBC 内部结构
- 图中黄色部分表示是Sharding-JDBC的入口API,采用了工厂方法的形式提供,目前有 ShardingDataSourecFactory 和 MasterSlaveDataSourceFactory两个工厂类
- ShardingDataSourecFactory 支持分库分表,读写分离操作
- MasterSlaveDataSourceFactory 支持读写分离操作
- 图中蓝色部分表示的是Sharding-JDBC的配置对象,提供灵活多变的配置方式
SharedingRuleConfiguration是分库分表配置的核心和入口,它可以包含多个TableRuleConfiguration和MasterSlaveRuleConfiguration。- TableRuleConfiguration 封装的是表的分片配置信息,有5中配置形式对应不同的Configuration类型
- MasterSlaveRuleConfiguration 封装了读写分离配置信息
- 图中红色部分表示的是内部对象,由Sharding-JDBC内部使用,应用开发者无需关注。Sharding-JDBC通过ShardingRuleConfiguration和MasterSlaveRuleConfiguration生成真正供ShardingDataSource和MasterSlaveDataSource使用的规则对象。ShardingDataSource和MasterSlaveDataSource实现了DataSource接口,是JDBC的完整实现方案。
2.3 数据分片
2.3.1 概念
对于数据库的垂直拆分一般都是在数据库设计初期就会完成,因为垂直拆分与业务直接相关,而我们提到的分库分表一般都是水平拆分,数据分片就是将原本一张数据量较大的表t_order拆分生成数个表结构完全一致的小数据量表t_order_0、t_order_1…,每张表指保存原表的部分数据
2.3.2 表的概念
-
逻辑板
水平拆分的数据库(表)的相同逻辑和数据结构表的总称,比如我们将订单表t_order 拆分成 t_order_0 … t_order_9等10张表。此时我们会发现分库分表以后数据库中已不在有t_order 这张表,取而代之的是t_order_n,但我们在代码中写SQL依然按t_order来写,此时t_order就是拆分出的逻辑表 -
真实表
数据库中真实存在的物理表,例如:t_order_0,t_order_1 -
数据节点
在分片之后,由数据源和数据表组成。例如t_order_db1.t_order_0 -
绑定表
指的是分片规则一致的关系表(主表,子表),例如t_order和t_order_item,均按照order_id分片,此两个表互为绑定表关系,绑定表之间的多表关联查询不会出现笛卡尔积关联,可以提升关联查询效率# t_order: t_order_0、t_order_1 # t_order_item: t_order_item_1、t_order_item_2 select * from t_order o join t_order_item i on (o.order_id = i.order_id) where o.order_id in (10,11)
由于分库分表以后这些表被拆分成N多个子表,如果不配置绑定表关系,会出现笛卡尔积查询,将产生如下四条sql
select * from t_order0 o join t_order_item0 i on o.order_id=i.order_id where o.order_id in (10,11); select * from t_order0 o join t_order_item1 i on o.order_id=i.order_id where o.order_id in (10,11); select * from t_order1 o join t_order_item0 i on o.order_id=i.order_id where o.order_id in (10,11); select * from t_order1 o join t_order_item1 i on o.order_id=i.order_id where o.order_id in (10,11);
如果配置绑定表关系后在进行关联查询时候,只要对应表分片规则一致产生的数据就会落到同一个数据库中,那么只需要 t_order_0 和 t_order_itme_0 表关联即可select * from b_order0 o join b_order_item0 i on(o.order_id=i.order_id) where o.order_id in (10,11); select * from b_order1 o join b_order_item1 i on(o.order_id=i.order_id) where o.order_id in (10,11);
注意:在关联查询时 t_order 它作为整个联合查询的主表。
所有相关的路由计算都只使用主表的策略,
t_order_item 表的分片相关的计算也会使用 t_order 的条件,
所以要保证绑定表之间的分片键要完全相同,当保证这些一样之后,
根据sql去查询时会统一的路由到0表或者1表,自然就没有笛卡尔积问题了。
-
广播表
在使用中,有些表没必要做分片,例如字典表、省份信息等,因为他们数据量不大,而且这种表可能需要与海量数据的表进行关联查询,广播表会在不同的数据节点上进行存储,存储的表接结构和数据完全相同 -
单表
指所有的分片数据源中指存在唯一一张的表,适用于数据量不大且不需要做任何操作的场景
2.3.2 分片键
用于分片的数据库字段,是将数据库(表)水平拆分的关键字段
例:将订单表中的订单主键的位数取模分片,则订单主键为分片字段,SQL中如果无分片字段,将执行全路由(去查询所有的真实表),性能较差,除了对单片字段的支持,Apache ShardingSphere也支持多个字段进行分片
2.3.4 分片算法
由于分片算法(ShardingAlgorithm)和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。目前提供4中分片算法
- 精确分片算法
用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景 - 范围分片算法
用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<= 进行分片场景 - 复合分片算法
用于处理使用多键作为分片键进行分片的场景,多个分片键的逻辑比较复杂,需要应用开发者自行处理其中的复杂读 - hint分片算法
用于处理使用Hint行分片的场景。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分片,而数据中并无此字段。SQL Hint支持通过JAVA API 和 SQL 注释两种方式使用
2.3.5 分片策略
分片策略(ShardingStrategy)包含分片键和分片算法,真正可用于分片操作的是分片键+分片算法,也就是分片策略, 目前提供5中分片策略
-
标准分片策略 StandardShardingStrategy
只支持单片分片键,提供对SQL语句中的=,>,<,<=,>=,IN,BETWEEN AND的分片操作支持。提供 PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法
PreciseShardingAlgorithm是必选的,RangeShardingAlgorithm是可选的。但是SQL中使用了范围操作,如果不配置RangeShardingAlgorithm会采用全库路由扫描,效率低。 -
复合分片策略 ComplexShardingStrategy
支持多分片键,提供对SQL语句中的=,>,<,<=,>=,IN,BETWEEN AND的分片操作支持。由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活读 -
行表达式分片策略 InlineShardingStrategy
只支持单分片键。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如:t_user_&->{uid % 8}表示t_user表根据uid%8,而分成8张表,表名为 t_user_0 到 t_user_7 -
Hint 分片策略HintShardingStrategy
通过Hint指定分片值而非从SQL中提取分片值得方式进行分片得策略 -
不分片策略NoneShardingStrategy
不分片得策略
分片策略配置
对于分片策略存有数据源分片策略和表分片策略两种维度,两种策略得API完全相同
-
数据源分片策略
用于配置数据被分配得目标数据源 -
表分配策略
用于配置数据被分配的目标表,由于表存在于数据源内,所有表分配策略是依赖数据源分片略结果的
2.3.6 分布式主键
数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题,同一个逻辑表(t_order)内的不同真实表(t_order_n)之间的自增键由于无法相互感知而产生重复主键
尽管可通过设置自增主键设置初始值和步长的方式避免ID碰撞,但这样会使维护成本加大,缺乏完整性和可扩展性。如果后去需要增加分片表的数量,要逐一修改分片表的步长,运维成本非常高,所以不建议这种方式。
ShardingSphere不仅提供了内置的分布式主键生成器,例如UUID、SNOWFLAKE,还抽离出分布式主键生成器接口,方便用户自行实现自定义的自增主键生成器
内置主键生成器:
-
UUID
采用UUID.randomUUID()的方式产生分布主键 -
SNOWFLAKE
在分布规则配置模块可配置每个表的主键生成策略,默认使用雪花算法,生成64bt的长整型数据
自定义主键生成器
- 自定义主键类,实现ShardingKeyGenerator接口
- 按SPI规范配置自定义主键类
在Apache ShardingSphere中,很多功能实现类的加载方式式通过SPI注入的方式完成的。注意:在resource目录下新建META-INF文件夹,在新建services文件夹,然后新建文件的名字为org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator
,打开文件,复制自定义主键类全路径到文件中保存。 - 自定义主键类应用配置
#对应主键字段名 spring.shardingsphere.sharding.tables.t_book.key-generator.column=id #对应主键类getType返回内容 spring.shardingsphere.sharding.tables.t_book.key-generator.type=LAGOUKEY
2.3.7 环境搭建
1.JDK:要求jdk必须式1.8版本以上
2.MYSQL:推荐使用5.7版本
3.需要两台Mysql服务器(使用docker)
# mysql01 192.168.220.10:3306
docker run -d -p 3306:3306 --name mysql01
-v /docker/shardingSphere/mysql01/data:/var/lib/mysql
-v /docker/shardingSphere/mysql01/conf:/etc/mysql/conf.d
-v /docker/shardingSphere/mysql01/log:/var/log/mysql
-e MYSQL_ROOT_PASSWORD=root mysql:5.7
# mysql02 192.168.220.10:3308
docker run -d -p 3308:3306 --name mysql02
-v /docker/shardingSphere/mysql02/data:/var/lib/mysql
-v /docker/shardingSphere/mysql02/conf:/etc/mysql/conf.d
-v /docker/shardingSphere/mysql02/log:/var/log/mysql
-e MYSQL_ROOT_PASSWORD=root mysql:5.7
2.3.8 创建数据库和表
1.在mysql01服务器上,创建数据库sharding_payorder_db,并创建par_order表
CREATE DATABASE sharding_payorder_db CHARACTER SET 'utf8';
CREATE TABLE `pay_order` (
`order_id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`product_name` varchar(128) DEFAULT NULL,
`COUNT` int(11) DEFAULT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12345679 DEFAULT CHARSET=utf8
2.在mysql02服务器上,创建数据库sharding_user_db,并创建users表
CREATE DATABASE sharding_user_db CHARACTER SET 'utf8';
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`username` varchar(255) NOT NULL COMMENT '用户昵称',
`phone` varchar(255) NOT NULL COMMENT '注册手机',
`PASSWORD` varchar(255) DEFAULT NULL COMMENT '用户密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'
2.3.9 创建springboot项目
环境说明:springboot2.3.7,MyBatisPlus,ShardingSphere-JDBC 5.1,Hikari,Mysql 5.7
1)创建项目
。。。
2)引入依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</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>
</plugins>
</build>
3)创建实体类
- PayOrder类
@TableName("pay_order") //逻辑表名 @Data @ToString public class PayOrder { @TableId private long orderId; private long userId; private String productName; private int count; }
- User类
@TableName("users") // 逻辑表名字 @Data @ToString public class User { @TableId private long id; private String username; private String phone; private String password; }
4)创建mapper
- PayOrderMapper类
@Mapper public interface PayOrderMapper extends BaseMapper<PayOrder>{ }
- UserMapper类
@Mapper public interface UserMapperextends BaseMapper<User>{ }
2.3.9 实现垂直分库
2.3.9.1 配置文件
使用sharding-jdbc 对数据库中垂直拆分的表进行操作,通过sharding-jdbc对分库分表规则进行配置,配置内容包括:数据源,主键生成策略,分片策略
application.yaml
- 配置信息
# 应用名称配置 spring: application: name: sharding-jdbc # 配置数据图 shardingsphere: datasource: # 定义多个数据源 names: db1,db2 # 数据源 db1的连接信息 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.220.10:3306/sharding_payorder_db?characterEncoding=UTF-8&useSSL=false username: root password: root # 数据源 db2的连接信息 db2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.220.10:3308/sharding_user_db?characterEncoding=UTF-8&useSSL=false username: root password: root rules: sharding: # 配置标准分片表配置 数据源.表名字,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。 tables: pay_order: actual-data-nodes: db1.pay_order users: actual-data-nodes: db2.users mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.3.9.2 配测试类
@SpringBootTest
public class ShardingJdbcApplicationTests {
@Autowired
private UserMapper userMapper;
@Autowired
private PayOrderMapper payOrderMapper;
@Test
public void testInsert(){
User user = new User();
user.setId(1000);
user.setUsername("阿爸阿爸");
user.setPhone("234234");
user.setPassword("23424");
userMapper.insert(user);
PayOrder payOrder = new PayOrder();
payOrder.setOrderId(124);
payOrder.setProductName("娃娃哈");
payOrder.setOrderId(user.getId());
payOrder.setCount(2);
payOrderMapper.insert(payOrder);
}
@Test
public void testSelect(){
User user = userMapper.selectById(1000);
System.out.println(user);
PayOrder payOrder = payOrderMapper.selectById(1231312);
System.out.println(payOrder);
}
}
2.3.10 实现水平分表
需求说明:
- 在mysql01服务器上,创建sharding_course_db
- 创建 t_course_1、t_course_2
- 约定规则:如果添加课程id为偶数添加到t_course_1中,奇数添加到t_course_2中
水平分片的id需要在业务层实现,不能依赖数据库的主键自增
CREATE TABLE t_course_1 (
`cid` BIGINT(20) NOT NULL,
`user_id` BIGINT(20) DEFAULT NULL,
`cname` VARCHAR(50) DEFAULT NULL,
`brief` VARCHAR(50) DEFAULT NULL,
`price` DOUBLE DEFAULT NULL,
`status` INT(11) DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE t_course_2 (
`cid` BIGINT(20) NOT NULL,
`user_id` BIGINT(20) DEFAULT NULL,
`cname` VARCHAR(50) DEFAULT NULL,
`brief` VARCHAR(50) DEFAULT NULL,
`price` DOUBLE DEFAULT NULL,
`status` INT(11) DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
2.3.9.2 实体类
@TableName("t_course")
@Data
@ToString
public class Course implements Serializable {
@TableId
//@TableId(value = "cid",type = IdType.ASSIGN_ID)
private Long cid;
private Long userId;
private String cname;
private String brief;
private double price;
private int status;
}
2.3.9.3 Mapper
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}
2.3.9.4 行表达式
下面配置操作会使用 inline表达式,灵活配置数据节点
行表达式的使用-> 行表达式
actual-data-nodes: db1.t_course_$->{1..2}
表达式 db1.t_course_$->{1…2}
$会被 大括号中的{1…2}被替 ${begin…end}表示范围区间
表达式 db1.t_course_$->{1…2}
最终会有替换为 db1.t_course_1 和 db1.t_course_2
2.3.9.4 分布式序列算法
雪花算法 -> 雪花算法链接
水平分片需要关注全局序列,因为不能简单的使用基于数据库的主键自增。
这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。
- 基于MyBatisPlus的id策略:将Course类的id设置成如下形式
2.3.9.5 配置文件
# 应用名称配置
spring:
application:
name: sharding-jdbc
# 配置数据图
shardingsphere:
datasource:
# 定义多个数据源
names: db1
# 数据源 db1的连接信息
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3306/sharding_course_db?characterEncoding=UTF-8&useSSL=false
username: root
password: root
rules:
sharding:
# 配置标准分片表配置 数据源.表名字,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
tables:
# 逻辑表的名称
t_course:
actual-data-nodes: db1.t_course_$->{1..2} # 支持 inline 表达式。
# 配置分片策略(分片策略报错分片键和分片算法)
table-strategy:
standard:
# 分片键 cid字段
sharding-column: cid
# 分片算法名称
sharding-algorithm-name: tabel-inline
# 配置分布式序列
key-generate-strategy:
# 序列字段
column: cid
# 序列算法名称
key-generator-name: alg_snowflake
# 分片算法名称类型:行表达式分片算法
sharding-algorithms:
tabel-inline:
type: INLINE
props:
# 取模分表落去那个表中去
algorithm-expression: t_course_$->{cid % 2 +1}
# 分布式序列-算法类型
key-generators:
# 序列算法名称
alg_snowflake:
# 雪花算法
type: SNOWFLAKE
# 打印sql
props:
sql-show: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.3.10 实现水平分库
水平分库是把同一个表的数据按一定的规则拆到不同的数据库中,每个库可以放在不同的服务器上。接下来看一下如何使用Sharding-JDBC实现水平分库
2.3.10.1 数据准备
1.创建数据库
在mysql01的服务器上,创建数据库sharding_course_db0,在mysql02服务器上,创建数据库sharding_course_db1
2.创建表
CREATE TABLE `t_course_0` (
`cid` bigint(20) NOT NULL,
`user_id` bigint(20) DEFAULT NULL,
`corder_no` bigint(20) DEFAULT NULL,
`cname` varchar(50) DEFAULT NULL,
`brief` varchar(50) DEFAULT NULL,
`price` double DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_course_1` (
`cid` bigint(20) NOT NULL,
`user_id` bigint(20) DEFAULT NULL,
`corder_no` bigint(20) DEFAULT NULL,
`cname` varchar(50) DEFAULT NULL,
`brief` varchar(50) DEFAULT NULL,
`price` double DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 实体类
@TableName("t_course")
@Data
public class Course implements Serializable {
@TableId
private Long cid;
private Long userId;
private Long corderNo;
private String cname;
private String brief;
private double price;
private int status;
}
2.3.10.1 配置文件
# 应用名称配置
spring:
application:
name: sharding-jdbc
# 配置数据图
shardingsphere:
datasource:
# 定义多个数据源
names: db0,db1
# 数据源 db0的连接信息
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3306/sharding_course_db0?characterEncoding=UTF-8&useSSL=false
username: root
password: root
# 数据源 db1的连接信息
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3308/sharding_course_db1?characterEncoding=UTF-8&useSSL=false
username: root
password: root
rules:
sharding:
# 配置标准分片表配置 数据源.表名字,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
tables:
t_course:
# 分布式序列配置
key-generate-strategy:
# 分布式序列- 字段名称
column: cid
# 分布式序列- 算法名称
key-generator-name: alg_snowflake
actual-data-nodes: db$->{0..1}.t_course_$->{0..1}
# 设置分库策略
database-strategy:
standard:
# 分库:字段名称
sharding-column: user_id
# 分片算法名称 -> 行表达式分片suanf
sharding-algorithm-name: table-inline
# 分表策略
table-strategy:
standard:
# 分表字段
sharding-column: cid
# 分表算法名称
sharding-algorithm-name: inline-hash-mod
sharding-algorithms:
# table-inline -> 自定义分片算法名称 分库
table-inline:
# 分片算命类型
type: INLINE
# 分片算法属性配置
props:
# user_id % 2, user_id为偶数操作欸db0数据库,否则操作db1数据库
algorithm-expression: db$->{user_id % 2}
# inline-hash-mod -> 自定义分片算法名称 分表
inline-hash-mod:
# 分片算命类型
type: INLINE
# 分片算法属性配置
props:
# cid的哈希值 % 2, cid为偶数操作欸t_course_0,否则操作 t_course_1
algorithm-expression: t_course_$->{Math.abs(cid.hashCode()) % 2}
# 分布式序列算法-类型
key-generators:
# 自定义分布式算法名称
alg_snowflake:
# 自定义分布式算法类型
type: SNOWFLAKE
# 打印sql
props:
sql-show: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
官方提供分片算法配置
https://shardingsphere.apache.org/document/current/cn/dev-manual/sharding/
2.3.11 绑定表
2.3.11.1 数据准备
1.创建表
在mysql01
服务器上的 sharding_course_db0
数据库 和 mysql02
服务器上的 sharding_course_db0
数据库分别创建 t_course_section_0
和 t_course_section_1
表 ,表结构如下:
CREATE TABLE `t_course_section_0` (
`id` bigint(11) NOT NULL,
`cid` bigint(11) DEFAULT NULL,
`corder_no` bigint(20) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL,
`section_name` varchar(50) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `t_course_section_1` (
`id` bigint(11) NOT NULL,
`cid` bigint(11) DEFAULT NULL,
`corder_no` bigint(20) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL,
`section_name` varchar(50) DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 创建实体类
@TableName("t_course_section")
@Data
@ToString
public class CourseSection {
@TableId(type = IdType.AUTO)
private Long id;
private Long cid;
private Long userId;
private String sectionName;
private int status;
}
3.创建mapper
@Mapper
public interface CourseSectionMapper extends BaseMapper<CourseSection> {
}
2.3.11.2 配置
# 应用名称配置
spring:
application:
name: sharding-jdbc
# 配置数据图
shardingsphere:
datasource:
# 定义多个数据源
names: db0,db1
# 数据源 db0的连接信息
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3306/sharding_course_db0?characterEncoding=UTF-8&useSSL=false
username: root
password: root
# 数据源 db1的连接信息
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3308/sharding_course_db1?characterEncoding=UTF-8&useSSL=false
username: root
password: root
rules:
sharding:
# 配置标准分片表配置 数据源.表名字,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
tables:
t_course:
# 分布式序列配置
key-generate-strategy:
# 分布式序列- 字段名称
column: cid
# 分布式序列- 算法名称
key-generator-name: snowflake
# 物理表
actual-data-nodes: db$->{0..1}.t_course_$->{0..1}
# 设置分库策略
database-strategy:
standard:
# 分库:字段名称
sharding-column: user_id
# 分片算法名称 -> 行表达式分片suanf
sharding-algorithm-name: table-mod
# 分表策略
table-strategy:
standard:
# 分表字段
sharding-column: corder_no
# 分表算法名称
sharding-algorithm-name: table-hash-mod
t_course_section:
# 分布式序列配置
key-generate-strategy:
# 分布式序列- 字段名称
column: id
# 分布式序列- 算法名称
key-generator-name: snowflake
# 物理表
actual-data-nodes: db$->{0..1}.t_course_section_$->{0..1}
# 设置分库策略
database-strategy:
standard:
# 分库:字段名称
sharding-column: user_id
# 分片算法名称 -> 行表达式分片suanf
sharding-algorithm-name: table-mod
# 分表策略
table-strategy:
standard:
# 分表字段
sharding-column: corder_no
# 分表算法名称
sharding-algorithm-name: table-hash-mod
sharding-algorithms:
# table-mod -> 自定义分片算法名称 分库
table-mod:
# 分片算命类型
type: MOD
# 分片算法属性配置
props:
# 取模2
sharding-count: 2
# table-hash-mod -> 自定义分片算法名称 分表
table-hash-mod:
# 分片算命类型 hashCode 取模
type: HASH_MOD
# 分片算法属性配置
props:
# 取模2
sharding-count: 2
# 分布式序列算法-类型
key-generators:
# 自定义分布式算法名称
snowflake:
# 自定义分布式算法类型
type: SNOWFLAKE
# 绑定表配置,避免笛卡尔积查询
binding-tables[0]: t_course,t_course_section
# 打印sql
props:
sql-show: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
如果不配置绑定表,测试的结果8个SQL,多表关联查询会出现笛卡尔积关联
spring.shardingsphere.rules.sharding.binding-tables[0]=t_course,t_course_section
如果配置绑定表:测试的结果为4个SQL。 多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
2.3.12 广播表(公共表)
2.3.12 公共表介绍
公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表,参数表,数据字典等属于此类型
可以将这类表在每个数据库保存一份,所以更新操作同时发送到所以分库执行,接下来看一下如何使用sharding-JDBC实现公共表的数据维护
- 数据准备
创建表,分表在sharding_course_db0,sharding_course_db1,sharding_user_db库进行创建t_district表
CREATE TABLE t_district (
id BIGINT(20) PRIMARY KEY COMMENT '区域ID',
district_name VARCHAR(100) COMMENT '区域名称',
LEVEL INT COMMENT '等级'
);
2.创建实体类
@TableName("t_district")
@Data
public class District {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String districtName;
private int level;
}
3.创建mapper
@Mapper
public interface DistrictMapper extends BaseMapper<District> {
}
4.配置
# 应用名称配置
spring:
application:
name: sharding-jdbc
# 配置数据图
shardingsphere:
datasource:
# 定义多个数据源
names: db0,db1,user_db
# 数据源 db0的连接信息
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3306/sharding_course_db0?characterEncoding=UTF-8&useSSL=false
username: root
password: root
# 数据源 db1的连接信息
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3308/sharding_course_db1?characterEncoding=UTF-8&useSSL=false
username: root
password: root
# 数据源 user_db的连接信息
user_db:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.220.10:3308/sharding_user_db?characterEncoding=UTF-8&useSSL=false
username: root
password: root
rules:
sharding:
tables:
# 逻辑表
t_district:
# 数据节点配置
actual-data-nodes: db$->{0..1}.t_district,user_db.t_district
# 公共表配置
broadcast-tables:
- t_district
props:
sql-show: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl