1.简介
- 1.开源的分布式数据生态项目
ShardingSphere-JDBC
:轻量级Java框架ShardingSphere-Proxy
:数据库代理ShardingSphere-Sidecar
(规划中):Kubernetes
的云原生数据库代理- 2.使用版本:
ShardingSphere5.1.1
- 3.定位:
关系型数据库中间件
1.数据库集群架构
- 1.
出现原因
:单个数据库服务器无法满足海量数据加海量用户的业务需求- 2.
解决方式
:采用数据库集群架构提高应用程序的性能- 3.
具体实现
- 1.
读写分离
- 2.
数据分片
1.读写分离
- 1.
原理
:将数据库读写操作分散到不同的节点上,分散了数据库读写的压力- 2.
主机
:负责事务性的增删改操作;从机
:负责查询操作- 3.
读写分离
机制建立在数据库主从复制
的基础上,可参考MySQL
文章中主从复制- 4.
优点
:通过读写分离可以有效避免数据更新导致的行锁,也可将查询负载均衡的分散到多个从机中,从而提高查询性能- 5.
问题
- 1.主机向从机复制数据的过程中会有时间的延迟,如果将数据写入主机,立即从从机读取数据,这时数据可能还未从主机完全复制到从机,所以会出现
数据不一致
问题,即CAP
定理- 2.
读写分离
分散了数据库读写压力
,但没有分散存储压力
,为了业务数据存储的需求,需将存储分散到多台数据库服务器
1.CAP定理
- 1.
定义
:分布式系统中,当涉及读写操作时,只能保证一致性(Consistence)
,可用性(Availability)
,分区容错性(Partition Tolerance)
三者中的两个
- 1.
C
:一致性
,对某个指定的客户端,读操作保证能够返回最新的写操作结果,即读写结果一致- 2.
A
:可用性
,非故障的节点在合理的时间内返回合理的响应(非错误和超时的响应)- 3.
P
:分区容错性
,当出现网络分区(多个服务器数据交换)后,系统能够继续履行职责(继续执行,可返回超时或历史数据)- 2.
特点
:实际设计过程中,每个系统不可能只处理一种数据,有的数据必须选择CP
,有的数据必须选择AP
,分布式系统理论上不可能选择CA
- 3.
CAP
理论中的C
在实践中不可能完美实现,数据复制的过程中,节点N1
和节点N2
的数据并不一致(强一致性);即使无法做到强一致性
,但应用可以采用合适的方式达到最终一致性
- 1.
基本可用
(Basically Available
):分布式系统在出现故障时,允许损失部分可用性,保证核心可用- 2.
软状态
(Soft State
):允许系统存在中间状态,该中间状态不会影响系统的整体可用性,这里的中间状态就是CAP
理论中的数据不一致
- 3.
最终一致性
(Eventual Consisitency
),系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
1.CP
- 1.如图所示,为了保证
一致性
,当发生分区现象(丢包)后,N1
节点上数据已经更新到y
,但由于N1
和N2
之间的复制通道中断,数据y
无法同步到N2
,N2
节点上的数据还是x
- 2.此时客户端
c
访问N2
时,N2
需要返回Error
,提示客户端C
系统现在发生了错误,该处理方式违背了可用性的要求,此时CAP
三者只能满足CP
2.AP
- 1.如图所示,为了保证
可用性
,当发生分区现象后,N1
节点上的数据已经更新到y
,但由于N1
和N2
之间的复制通道中断,数据y
无法同步到N2
,N2
节点上的数据还是x
- 2.此时客户端
C
访问N2
时,N2
将当前自己拥有的数据x
返回给客户端C
,而实际当前最新的数据是y
,不满足一致性
的要求,此时CAP
只能满足AP.
2.数据分片(分库分表)
- 1.
读写分离问题
:读写分离
分散了数据库读写压力
,从而缓解了单台服务器的访问压力,但没有分散存储压力
,为了满足业务数据存储需求,需将存储分散到多台数据库服务器- 2.
数据分片定义
:将存放在单一数据库或表中的数据分散地存放至多个数据库或表中,以达到提升性能瓶颈以及可用性的效果- 3.数据分片的
拆分方式
- 1.
垂直分片
- 2.
水平分片
- 4.
适用场景
:《阿里开发手册
》建议单表超过500万
条记录或单表大小超过2GB
需要考虑分库分表
1.垂直分片
- 1.
定义
:按照业务拆分
的方式称为垂直分片
/纵向拆分
- 2.
垂直分片
可细分为
- 1.
垂直分库
- 2.
垂直分表
1.垂直分库
- 1.
定义
:核心理念是专库专用
- 1.
拆分之前
:一个数据库由多个数据表构成,每个表对应着不同的业务- 2.
拆分之后
:按照业务将表进行归类,分布到不同的专用数据库,从而将压力分散至不同的数据库- 2.
问题
:垂直分库可以缓解数据库数据量和访问量的问题,即缓解对IO
性能;但无法处理单表数据量过大问题,此时需要水平分片
进一步处理
2.垂直分表
- 1.
定义
:核心理念是专表专用
- 1.
拆分之前
:一个数据表由多个字段构成,每个字段对应着不同的业务- 2.
拆分之后
:按照业务将字段进行归类,分布到不同的专用数据表,从而将压力分散至不同的数据表- 2.
问题
- 1.垂直分表可以缓解数据表访问量和占用量的问题,但无法处理单表数据量过大问题,此时需要
水平分片
进一步处理- 2.垂直分表会引入额外的复杂度,原来只要一次查询,现在需要两次查询
- 3.
适用范围
:垂直分表适合将表中不常用的列或是占了大量空间的列拆分出去- 4.
注意
:垂直分表对应的主键id
应该保持一致
2.水平分片
- 1.
定义
:通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中的方式称为水平分片/横向拆分
- 2.
水平分片
可细分为
- 1.
水平分库
- 2.
水平分表
1.水平分库
- 1.
定义
:相对于垂直分库,不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库中,每个数据库的分片仅包含数据的一分部(例:根据主键分片,偶数主键的记录放入0
库,奇数主键的记录放入1
库)- 2.单表进行切分后,是否将多个表分散在不同的数据库服务器中,可以根据实际的切分效果来确定
- 3.
适用场景
:如果单表拆分为多表后,单台服务器依然无法满足性能要求,那就需要将多个表分散在不同的数据库服务器中
2.水平分表
- 1.
定义
:相对于垂直分表,不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个表中,每个数据表的分片仅包含数据的一分部(例:根据主键分片,偶数主键的记录放入0
表,奇数主键的记录放入1
表)- 2.
适用范围
:水平分表适合表行数特别大的表
- 3.
注意
- 1.单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升;如果性能能够满足业务要求,可不拆分到多台数据库服务器,因为业务分库也会引入更多复杂性
- 2.将拆分的表引入多台数据库会更复杂(例:分布式事务,跨库关联,数据库成本)
3.产生原因
- 1.
分表
- 1.随着表数据的不断增大,查询变得缓慢
- 2.如果添加过多索引,会影响到新增和删除的性能
- 3.将数据分散到不同的表上,使单表的索引大小得到控制
- 2.
分库
- 1.数据库实例的吞吐量达到性能的瓶颈,需要扩展数据库实例,让每个数据库实例分担一部分请求,分解总体大请求量的压力
- 2.数据库扩容时需要对配置改变最少则需要在每个数据库实例中预留足够的数据库数量
4.适用范围及问题
1.适用范围
- 1.数据库设计时考虑垂直分库和垂直分表
- 2.随着数据库数据量增加,首先考虑
缓存处理
,读写分离
,索引
等方式,如果这些方式不能根本解决问题,再考虑水平分库和水平分表
2.问题
- 1.跨节点连接查询问题(分页,排序)
- 2.多数据源管理问题
- 3.分布式主键
- 4.分布式事务
3.读写分离和数据分片
- 1.上图展示将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系
1.实现方式
- 1.读写分离和数据分片具体的实现方式一般有两种
- 1.
程序代码封装
- 2.
中间件封装
1.程序代码封装
- 1.定义:指在代码中抽象一个
数据访问层
(或中间层封装),对程序进行一个封装,让数据访问层直接去访问不同数据库集群中的数据源,实现读写分离
操作和数据库服务器连接
的管理
2.中间件封装
- 1.中间件封装是指的是独立一套系统出来提供数据访问层的功能,从而实现读写操作分离和数据库服务器连接的管理
- 2.对于程序服务器而言,访问中间件和访问数据库没有区别
- 3.常用的中间件有
ShardingSphere
和MyCat
,其中已经封装好了数据访问层供程序使用- 4.
Apache ShardingSphere
既提供了程序级别也提供了中间件级别两种解决方案;MyCat
只提供了中间件解决方案
2.ShardingSphere JDBC
1.简介
- 1.
ShardingSphere JDBC
定位为轻量级Java
框架,在Java
的JDBC
层提供的额外服务,类似生成一个数据访问层
,使用客户端直连数据库,以jar包
形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动
,完全兼容JDBC和各种ORM
框架
- 1.适用于任何基于
JDBC
的ORM
框架(JPA
,Hibetnate
,mybatis
,Spring JDBC Template
或直接使用JDBC
)- 2.支持任何第三方的数据库连接池(
DBCP
,C3P0
,BoneCP
,HikariCP
等)- 3.支持任意实现
JDBC
规范的数据库(MySQL
,PostgreSQL
,Oracle
,SQLServer
以及任何可使用JDBC
访问的数据库)- 2.如图所示
ShardingSphere JDBC
充当一个数据访问层
,如果应用程序是集群模式可采用服务治理组件(zookeeper
)
2.读写分离搭建步骤
1.搭建Mysql主从复制
- 参考
数据库-MySQL
文章中的主从复制
2.创建Maven项目
3.导入架包
<?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> <groupId>com.demp</groupId> <artifactId>ShardingSphere</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ShardingSphere</name> <description>Demo project for ShardingSphere</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <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>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </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> </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> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.atguigu.shargingjdbcdemo.ShargingJdbcDemoApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
4.配置文件
1.application.properties
# 应用名称 spring.application.name=ShardingSphere # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 配置真实数据源 spring.shardingsphere.datasource.names=master,slave1,slave2 # 配置第 1 个数据源 spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.master.username=root spring.shardingsphere.datasource.master.password=root # 配置第 2 个数据源 spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.slave1.username=root spring.shardingsphere.datasource.slave1.password=root # 配置第 3 个数据源 spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave2.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.slave2.username=root spring.shardingsphere.datasource.slave2.password=root # 读写分离类型,如: Static,Dynamic spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static # 写数据源名称 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master # 读数据源名称,多个从数据源用逗号分隔 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2 # 负载均衡算法名称 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_weight # 负载均衡算法配置 # 负载均衡算法类型 spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1 spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=2 # 打印SQl spring.shardingsphere.props.sql-show=true
2.application.yml
版本1
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: master,slave1,slave2 master: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root slave1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://169.254.73.102:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root slave2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://169.254.73.103:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: readwrite-splitting: data-sources: myds: type: Static load-balancer-name: alg_round props: write-data-source-name: master read-data-source-names: slave1,slave2 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: slave1: 1 slave2: 2 props: sql-show: true
- 注意:
- 1.上述配置使用
ShardingSphere
的5.0.0-alpha
和5.0.0
版本会报错- 2.使用
ShardingSphere
的5.1.0
和5.1.1
版本不会报错
版本2
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: common: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver names: master,slave1,slave2 master: jdbc-url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root slave1: jdbc-url: jdbc:mysql://169.254.73.102:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root slave2: jdbc-url: jdbc:mysql://169.254.73.103:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: readwrite-splitting: data-sources: myds: type: Static load-balancer-name: alg_round props: write-data-source-name: master read-data-source-names: slave1,slave2 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: slave1: 1 slave2: 2 props: sql-show: true
- 注意:
- 1.上述
数据源
配置使用ShardingSphere
的和5.0.0
,5.1.0
和5.1.1
版本会报错- 2.使用
ShardingSphere
的5.0.0-alpha
版本不会报错- 3.所以使用
5.0.0-alpha
版本必须使用common
配置数据源
,5.1.0
和5.1.1
版本不能使用common
配置数据源
,5.0.0
版本数据源
无法配置成功- 4.当前使用
ShardingSphere5.1.1
版本
5.通过MybatisX快速生成
6.创建启动类
package com.demp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.demp.mapper") public class ShardingSphereStartApplication { public static void main(String[] args) { SpringApplication.run(ShardingSphereStartApplication.class,args); System.out.println("---------------------项目启动成功!---------------------"); } }
7.读写分离测试
package com.demp; import com.demp.domain.NyEnergyData; import com.demp.mapper.NyEnergyDataMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.util.Date; import java.util.List; @SpringBootTest public class ShardingSphereStartApplicationTest { @Resource NyEnergyDataMapper nyEnergyDataMapper; @Test public void insert(){ NyEnergyData nyEnergyData = new NyEnergyData(null,111L,new Date(),1,1L,1L,"A111","D111","111",new Date(),null,null,0,"111",0,"测试"); nyEnergyDataMapper.insert(nyEnergyData); } @Test public void select(){ List<NyEnergyData> nyEnergyDataList = nyEnergyDataMapper.selectList(null); nyEnergyDataList.forEach(nyEnergyData -> System.out.println(nyEnergyData)); } }
2023-03-03 15:16:30.436 INFO 41736 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) 2023-03-03 15:16:30.436 INFO 41736 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2023-03-03 15:16:30.436 INFO 41736 --- [main] ShardingSphere-SQL : Actual SQL: master ::: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) ::: [1631554118550081537, 222, 2023-03-03 15:16:29.275, 2, 2, 2, A222, D222, 222, 2023-03-03 15:16:29.275, 0, 222, 0, 测试]
2023-03-03 15:25:42.902 INFO 19944 --- [main] ShardingSphere-SQL: Logic SQL: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data 2023-03-03 15:25:42.903 INFO 19944 --- [main] ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2023-03-03 15:25:42.903 INFO 19944 --- [main] ShardingSphere-SQL: Actual SQL: slave1 ::: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data
8.读写分离+事务测试
1.未加事务
@Test public void testTransactional(){ NyEnergyData nyEnergyData = new NyEnergyData(null,222L,new Date(),2,2L,2L,"A222","D222","222",new Date(),null,null,0,"222",0,"测试"); nyEnergyDataMapper.insert(nyEnergyData); List<NyEnergyData> nyEnergyDataList = nyEnergyDataMapper.selectList(null); nyEnergyDataList.forEach(data -> System.out.println(data)); }
2023-03-03 15:50:40.875 INFO 31528 --- [main] ShardingSphere-SQL: Logic SQL: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) 2023-03-03 15:50:40.876 INFO 31528 --- [main] ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2023-03-03 15:50:40.876 INFO 31528 --- [main] ShardingSphere-SQL: Actual SQL: master ::: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) ::: [1631562716718092289, 222, 2023-03-03 15:50:39.194, 2, 2, 2, A222, D222, 222, 2023-03-03 15:50:39.194, 0, 222, 0, 测试] 2023-03-03 15:50:41.153 INFO 31528 --- [main] ShardingSphere-SQL: Logic SQL: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data 2023-03-03 15:50:41.153 INFO 31528 --- [main] ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2023-03-03 15:50:41.153 INFO 31528 --- [main] ShardingSphere-SQL: Actual SQL: slave1 ::: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data
2.加事务
@Test @Transactional public void testTransactional(){ NyEnergyData nyEnergyData = new NyEnergyData(null,222L,new Date(),2,2L,2L,"A222","D222","222",new Date(),null,null,0,"222",0,"测试"); nyEnergyDataMapper.insert(nyEnergyData); List<NyEnergyData> nyEnergyDataList = nyEnergyDataMapper.selectList(null); nyEnergyDataList.forEach(data -> System.out.println(data)); }
2023-03-03 16:03:37.964 INFO 34092 --- [main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@446a1e84 testClass = ShardingSphereStartApplicationTest, testInstance = com.demp.ShardingSphereStartApplicationTest@188bf4d8, testMethod = testTransactional@ShardingSphereStartApplicationTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@4f0f2942 testClass = ShardingSphereStartApplicationTest, locations = '{}', classes = '{class com.demp.ShardingSphereStartApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7b98f307, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@59474f18, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@5b7a5baa, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@1573f9fc, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@4cb2c100], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@7dd7ec56]; rollback [true] 2023-03-03 16:03:40.070 INFO 34092 --- [main] ShardingSphere-SQL: Logic SQL: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) 2023-03-03 16:03:40.071 INFO 34092 --- [main] ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2023-03-03 16:03:40.071 INFO 34092 --- [main] ShardingSphere-SQL: Actual SQL: master ::: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,is_delete,row_id,is_statistics,remark ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,?,? ) ::: [1631565984982728705, 222, 2023-03-03 16:03:38.43, 2, 2, 2, A222, D222, 222, 2023-03-03 16:03:38.43, 0, 222, 0, 测试] 2023-03-03 16:03:40.403 INFO 34092 --- [main] ShardingSphere-SQL: Logic SQL: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data 2023-03-03 16:03:40.404 INFO 34092 --- [main] ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2023-03-03 16:03:40.404 INFO 34092 --- [main] ShardingSphere-SQL: Actual SQL: master ::: SELECT id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,create_time,update_time,delete_time,is_delete,row_id,is_statistics,remark FROM ny_energy_data 。。。。。。 2023-03-03 16:03:40.807 INFO 34092 --- [main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@446a1e84 testClass = ShardingSphereStartApplicationTest, testInstance = com.demp.ShardingSphereStartApplicationTest@188bf4d8, testMethod = testTransactional@ShardingSphereStartApplicationTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@4f0f2942 testClass = ShardingSphereStartApplicationTest, locations = '{}', classes = '{class com.demp.ShardingSphereStartApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7b98f307, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@59474f18, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@5b7a5baa, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@1573f9fc, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@4cb2c100], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
- 1.为了保证主从库间的
事务一致性
,避免跨服务的分布式事务,ShardingSphere-JDBC
的主从模型中,事务中
的数据读写均用主库
- 1.不添加
@Transactional
:写操作
对主库操作,读操作
对从库操作- 2.添加
@Transactional
:读写操作
均对主库操作- 2.注意
JUnit
环境下的@Transactional
注解,默认情况下就会对事务进行回滚(即使在没加注解@Rollback
,也会对事务回滚)
9.负载均衡算法及测试
类型 名称 参数 参数类型 ROUND_ROBIN 轮询 transaction-read-query-strategy String RANDOM 随机 transaction-read-query-strategy String WEIGHT 权重 读库名称 Double # 配置规则 rules: # 读写分离 readwrite-splitting: # 数据源 data-sources: # 自定义数据源名称 myds: # 数据源类型:静态,动态 type: Static # 采用的负载均衡算法名称(对应下面) load-balancer-name: alg_round props: # 写数据源名称 write-data-source-name: master # 读数据源名称 read-data-source-names: slave1,slave2 # 负载均衡算法 load-balancers: # 算法名称 alg_round: # 算法类型:轮询 type: ROUND_ROBIN # 算法名称 alg_random: # 算法类型:随机 type: RANDOM # 算法名称 alg_weight: # 算法类型:权重 type: WEIGHT props: # 数据源权重 slave1: 1 slave2: 2
- 1.经过测试以上算法均成功,注意
权重
只是概率设置,并非绝对比
3.数据分片搭建步骤
1.创建Maven项目
- 同上
2.导入架包
- 同上
3.配置文件
1.行表达式
- 1.作用:行表达式是为了有效地简化数据节点配置
- 2.使用方法
- 1.
$->{begin..end}
:表示范围区间
$->{begin..end} 例: spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=localhost-energy.t_order$->{0..1} 表示: localhost-energy.t_order0,localhost_energy.t_order1
- 2.
$->{[unit1,unit2,unit_x]}
:表示枚举值
$->{[unit1,unit2,unit_x]} 例: spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=localhost-energy.t_order$->{[0,1,3]} 表示: localhost-energy.t_order0,localhost_energy.t_order1,localhost_energy.t_order3
- 3.
行表达式
中如果出现连续多个$->{ expression }
表达式,整个表达式最终的结果将会根据每个子表达式
的结果进行笛卡尔组合
例:spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=localhost-energy$->{0..1}.t_order$->{0..1} 表示: localhost-energy0.t_order0,localhost_energy0.t_order1,localhost-energy1.t_order0,localhost_energy1.t_order1 例:spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=$->{[localhost-energy,virtual-energy]}.t_order$->{0..1} 表示: localhost-energy.t_order0,localhost_energy.t_order1,virtual-energy.t_order0,virtual-energy.t_order1
- 4.
非均匀数据节点
需要单独配置每个数据节点的对应表例:spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=localhost-energy.t_order$->{0..1},virtual-energy.t_order$->{[1,3,4]} 表示: localhost-energy.t_order0,localhost_energy.t_order1,virtual-energy.t_order1,virtual-energy.t_order3,virtual-energy.t_order4
- 5.
有前缀数据节点
,可以将公共部分提取例:spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=localhost-energy.t_order_0$->{0..1},virtual-energy.t_order_0$->{[1,3,4]} 表示: localhost-energy.t_order_00,localhost_energy.t_order_01,virtual-energy.t_order_01,virtual-energy.t_order_03,virtual-energy.t_order_04
2.垂直分库
1.application.properties
# 应用名称 spring.application.name=ShardingSphere # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 配置真实数据源 spring.shardingsphere.datasource.names=localhost-energy,virtual-energy # 配置第 1 个数据源 spring.shardingsphere.datasource.localhost-energy.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.localhost-energy.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.localhost-energy.jdbc-url=jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.localhost-energy.username=root spring.shardingsphere.datasource.localhost-energy.password=root # 配置第 2 个数据源 spring.shardingsphere.datasource.virtual-energy.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.virtual-energy.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.virtual-energy.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.virtual-energy.username=root spring.shardingsphere.datasource.virtual-energy.password=root # 标准分片表配置 # 由数据源名 + 表名组成,以小数点分隔。 # 多个表以逗号分隔,支持 inline 表达式。 # 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况 spring.shardingsphere.rules.sharding.tables.area_account.actual-data-nodes=localhost-energy.area_account spring.shardingsphere.rules.sharding.tables.ny_energy_data.actual-data-nodes=virtual-energy.ny_energy_data # 打印SQl spring.shardingsphere.props.sql-show=true
2.application.yml
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: localhost-energy,virtual-energy localhost-energy: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root virtual-energy: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: sharding: tables: arar_account: actual-data-nodes: localhost-energy.area_account ny_energy_data: actual-data-nodes: virtual-energy.ny_energy_data props: sql-show: true
3.垂直分表
- 1.垂直分表等同于按业务分为不同的表,即
无需使用ShardingSphere
4.水平分库
- 1.水平分片的
id
需要在业务层实现,不能依赖数据库的主键自增- 2.因为使用数据库本身的主键自增,不同的表会有各自独立的自增序列,拆分多张表的主键会重复
- 3.需要在业务代码中生成主键,而非依赖主键自增
- 4.水平分片的实体类对应未拆分的逻辑表,而真实数据节点则对应数据库中的真实表
- 5.修改时无法修改分片键,否则会报错
- 6.实体类
id
一般使用Long
类型,且表id使用bigint类型,否则分布式id
的值过大会报错- 7.表
varchar
需要设置字符类型
和字符排序类型
,一般设置为utf8mb4
和utf8mb4_general_ci
,否则根据该字段查询时会报错
1.application.properties
#------------------------基本配置 # 应用名称 spring.application.name=ShardingSphere # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 打印SQl spring.shardingsphere.props.sql-show=true #------------------------数据源配置 # 配置真实数据源 spring.shardingsphere.datasource.names=energy0,energy1 # 配置第 1 个数据源 spring.shardingsphere.datasource.energy0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy0.jdbc-url=jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy0.username=root spring.shardingsphere.datasource.energy0.password=root # 配置第 2 个数据源 spring.shardingsphere.datasource.energy1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy1.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy1.username=root spring.shardingsphere.datasource.energy1.password=root #------------------------标准分片表配置(数据节点配置) # 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。 # 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况 spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=energy$->{0..1}.t_order0 #------------------------分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一 # 用于单分片键的标准分片场景 # 分片列名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=type # 分片算法名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_type #------------------------分片算法配置 # 行表达式分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.type=INLINE # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.props.algorithm-expression=energy$->{type % 2} # 取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2 # 哈希取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2
2.application.yml
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: energy0,energy1 energy0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root energy1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: sharding: tables: t_order: actual-data-nodes: energy$->{0..1}.t_order0 database-strategy: standard: sharding-column: type sharding-algorithm-name: alg_inline_type sharding-algorithms: alg_inline_type: type: INLINE props: algorithm-expression: energy$->{type % 2} alg_mod: type: MOD props: sharding-count: 2 alg_hash_mod: type: HASH_MOD props: sharding-count: 2 props: sql-show: true
5.水平分表
- 1.水平分片的
id
需要在业务层实现,不能依赖数据库的主键自增- 2.因为使用数据库本身的主键自增,不同的表会有各自独立的自增序列,拆分多张表的主键会重复
- 3.需要在业务代码中生成主键,而非依赖主键自增
- 4.水平分片的实体类对应未拆分的逻辑表,而真实数据节点则对应数据库中的真实表
- 5.配置文件类似水平分库,修改对应的数据节点即可
1.application.properties
#------------------------基本配置 # 应用名称 spring.application.name=ShardingSphere # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 打印SQl spring.shardingsphere.props.sql-show=true #------------------------数据源配置 # 配置真实数据源 spring.shardingsphere.datasource.names=energy0,energy1 # 配置第 1 个数据源 spring.shardingsphere.datasource.energy0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy0.jdbc-url=jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy0.username=root spring.shardingsphere.datasource.energy0.password=root # 配置第 2 个数据源 spring.shardingsphere.datasource.energy1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy1.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy1.username=root spring.shardingsphere.datasource.energy1.password=root #------------------------标准分片表配置(数据节点配置) # 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。 # 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况 spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=energy$->{0..1}.t_order$->{0..1} #------------------------分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一 # 用于单分片键的标准分片场景 # 分片列名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=type # 分片算法名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_type #------------------------分表策略 # 用于单分片键的标准分片场景 # 分片列名称 spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=code # 分片算法名称 spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod #------------------------分片算法配置 # 行表达式分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.type=INLINE # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.props.algorithm-expression=energy$->{type % 2} # 取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2 # 哈希取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2
2.application.yml
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: energy0,energy1 energy0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root energy1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: sharding: tables: t_order: actual-data-nodes: energy$->{0..1}.t_order$->{0..1} database-strategy: standard: sharding-column: type sharding-algorithm-name: alg_inline_type table-strategy: standard: sharding-column: code sharding-algorithm-name: alg_hash_mod sharding-algorithms: alg_inline_type: type: INLINE props: algorithm-expression: energy$->{type % 2} alg_mod: type: MOD props: sharding-count: 2 alg_hash_mod: type: HASH_MOD props: sharding-count: 2 props: sql-show: true
5.通过MybatisX快速生成
6.创建启动类
- 同上
7.测试
1.垂直分库
@Test public void insert(){ NyEnergyData nyEnergyData = new NyEnergyData(); nyEnergyData.setEnterpriseId(111L); nyEnergyData.setUseTime(new Date()); nyEnergyData.setDateType(1); nyEnergyData.setAttributeId(1L); nyEnergyData.setFrequencyId(1L); nyEnergyData.setAttributeCode("A111"); nyEnergyData.setDataCode("D111"); nyEnergyData.setDataValue("2525"); nyEnergyData.setRowId("test"); nyEnergyDataMapper.insert(nyEnergyData); AreaAccount areaAccount = new AreaAccount(); areaAccount.setCityName("测试"); areaAccountMapper.insert(areaAccount); }
2023-03-03 19:22:01.235 INFO 36836 --- [main] ShardingSphere-SQL: Logic SQL: INSERT INTO ny_energy_data (id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,row_id ) VALUES ( ?,?,?,?,?,?,?,?,?,? ) 2023-03-03 19:22:01.235 INFO 36836 --- [main] ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2023-03-03 19:22:01.235 INFO 36836 --- [main] ShardingSphere-SQL: Actual SQL: virtual-energy ::: INSERT INTO ny_energy_data ( id,enterprise_id,use_time,date_type,attribute_id,frequency_id,attribute_code,data_code,data_value,row_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ::: [1631615903340724225, 111, 2023-03-03 19:21:59.893, 1, 1, 1, A111, D111, 2525, test] 2023-03-03 19:22:04.859 INFO 36836 --- [main] ShardingSphere-SQL: Logic SQL: INSERT INTO area_account ( city_name ) VALUES ( ? ) 2023-03-03 19:22:04.859 INFO 36836 --- [main] ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2023-03-03 19:22:04.859 INFO 36836 --- [main] ShardingSphere-SQL: Actual SQL: localhost-energy ::: INSERT INTO area_account ( city_name ) VALUES (?) ::: [测试]
- 1.经测试,不同数据库的表的
读写操作
会自动路由到对应的数据库中
2.垂直分表
- 无需测试
3.水平分库
@Test public void testShardingSelect(){ List<Order> orders = orderMapper.selectList(null); orders.forEach(order -> System.out.println(order)); } @Test public void testShardingInsertAndUpdate(){ Order order = new Order(); order.setName("洛基亚"); order.setCode("L444444"); order.setNumber(3000L); order.setCreateTime(new Date()); Order oldOrder = orderMapper.selectOne(new QueryWrapper<Order>().eq("name","洛基亚")); if(Objects.isNull(oldOrder)){ order.setVersion(0); if(orderMapper.insert(order) > 0){ System.out.println("添加成功!"); }else { System.out.println("添加失败"); } }else { Integer num = oldOrder.getVersion(); order.setVersion(++num); order.setUpdateTime(new Date()); if(orderMapper.update(order,new QueryWrapper<Order>().eq("name","洛基亚")) > 0){ System.out.println("更新成功!"); }else { System.out.println("更新失败"); } } }
- 1.实体类的
id
主键需要设置为分布式生成,且类型为Long
,对应的表id类型应该为bigint
,且varchar
类型需按如下设置
- 2.修改时无法修改分片键
- 3.插入时根据
分库分片键
自动将数据插入对应的库中,查询时自动根据分片键去指定的库中查询,然后ShardingSphere
会自动将结果组合到一起
4.水平分表
- 1.
测试内容
同水平分库- 2.插入时根据
分表分片键
自动将数据插入到对应的表中,查询时将同一个库中的多个表的数据使用UNION ALL
连接到一起,然后再将不同库的数据组装到一起
8.分片算法
1.优缺点
- 1.
取模分片
- 1.优点:数据存放比较均匀
- 2.缺点:扩容需要大量数据迁移(例:两个片不够则需再加片,之前分好的数据需要迁移)
- 2.
范围分片
(时间,id
前缀,地区等)
- 优点:扩容不需要迁移数据
- 缺点:数据存放不均匀,容易产生数据倾斜(例:
双11
活动,可能11月
分片很多,无法分担压力)- 3.
业务场景分片
4.雪花算法
- 1.
传统数据库
中,主键自动生成技术是基本需求(主键自增
)- 2.数据分片后,不同数据节点生成
全局唯一主键
无法通过传统主键自增完成- 3.因为同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而会产生重复主键
- 4.可通过
约束自增主键初始值和步长
的方式避免碰撞,但需要引入额外的规则,使解决方案缺乏完整性和可扩展性- 5.通过第三方解决方案,如:
UUID
,SNOWFLAKE
(雪花算法)
1.实现原理
- 1.雪花算法生成
64bit
的长整型数据- 2.同一个进程中,首先通过
时间位
保证不重复,如果时间相同则通过序列位
保证- 3.由于时间位是
单调递增
的,且各个服务器如果做了时间同步,那生成的主键在分布式环境可认为是总体有序
的,保证了对索引字段插入的高效性- 4.
雪花算法
生成的主键,二进制
表示形式包含4部分
,从高位到低位分别表示为:1bit符号位
,41bit的时间戳位
,10bit工作进程位
以及12bit的序列号位
- 1.
符号位(1bit)
- 预留的符号位,恒为零(因为生成的是正数)
- 2.
时间戳位(41bit)
41位
的时间戳可以容纳的毫秒数是2的41次幂
,一年所使用的毫秒数是365*24*60*60*100
,通过计算可知结果约等于69.73
年,Apache ShardingSphere
的雪花算法时间纪元从2016年11月1日零点
开始,可使用到2086
年,可满足绝大部分系统的要求- 3.
工作进程位(10bit)
- 该标志在
Java
进程内是唯一的,如果是分布式应用部署应保证每个工作进程的id
是不同的,该值默认为0
,可通过属性设置- 4.
序列号位(12bit)
- 该序列用来在
同一个毫秒
内生成不同的id
,如果在这个毫秒内生成的数量超过4096(2的12次幂)
,那么生成器会等待到下个毫秒继续生成- 5.雪花算法主键的详细结构图
- 6.雪花算法和
UUID
比较:雪花算法生成的主键唯一有序,而UUID
生成的主键唯一无序,所以一般使用雪花算法作为主键生成策略
5.分布式序列配置
- 1.使用
ShardingSphere
生成分布式主键id
需要将Mybatis-Plus
的主键自增策略设置为自增
- 2.因为
Mybatis-Plus
默认为雪花算法生成主键id
,其优先级比ShardingSphere
高
1.application.properties
#------------------------基本配置 # 应用名称 spring.application.name=ShardingSphere # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 打印SQl spring.shardingsphere.props.sql-show=true #------------------------数据源配置 # 配置真实数据源 spring.shardingsphere.datasource.names=energy0,energy1 # 配置第 1 个数据源 spring.shardingsphere.datasource.energy0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy0.jdbc-url=jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy0.username=root spring.shardingsphere.datasource.energy0.password=root # 配置第 2 个数据源 spring.shardingsphere.datasource.energy1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.energy1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.energy1.jdbc-url=jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true spring.shardingsphere.datasource.energy1.username=root spring.shardingsphere.datasource.energy1.password=root #------------------------标准分片表配置(数据节点配置) # 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。 # 缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况 spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=energy$->{0..1}.t_order$->{0..1} #------------------------分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一 # 用于单分片键的标准分片场景 # 分片列名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=type # 分片算法名称 spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_type #------------------------分表策略 # 用于单分片键的标准分片场景 # 分片列名称 spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=code # 分片算法名称 spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod #------------------------分片算法配置 # 行表达式分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.type=INLINE # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_type.props.algorithm-expression=energy$->{type % 2} # 取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2 # 哈希取模分片算法 # 分片算法类型 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD # 分片算法属性配置 spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2 #------------------------分布式序列策略配置 # 分布式序列列名称 spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id # 分布式序列算法名称 spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake #------------------------分布式序列算法配置 # 分布式序列算法类型 spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE # 分布式序列算法属性配置(如果分布式序列作为分片键则需配置) # spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.xxx=
2.application.yml
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: energy0,energy1 energy0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root energy1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: sharding: tables: t_order: actual-data-nodes: energy$->{0..1}.t_order0 database-strategy: standard: sharding-column: type sharding-algorithm-name: alg_inline_type key-generate-strategy: column: id key-generator-name: alg_snowflake sharding-algorithms: alg_inline_type: type: INLINE props: algorithm-expression: energy$->{type % 2} alg_mod: type: MOD props: sharding-count: 2 alg_hash_mod: type: HASH_MOD props: sharding-count: 2 key-generators: alg_snowflake: type: SNOWFLAKE props: sql-show: true
6.切换数据源
- 1.
ShardingSphere5.1.1
默认使用com.zaxxer.hikari.HikariDataSource
数据源- 2.如果想切换为
durid
数据源,则需要添加durid
依赖即可
1.配置依赖
<?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> <groupId>com.demp</groupId> <artifactId>ShardingSphere</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ShardingSphere</name> <description>Demo project for ShardingSphere</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <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.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.9</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </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> </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> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.atguigu.shargingjdbcdemo.ShargingJdbcDemoApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2.配置文件
server: port: 8091 spring: profiles: active: test shardingsphere: mode: type: Memory datasource: names: energy0,energy1 energy0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root energy1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://169.254.73.100:3306/energy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true&rewriteBatchedStatements=true username: root password: root rules: sharding: tables: t_order: actual-data-nodes: energy$->{0..1}.t_order0 database-strategy: standard: sharding-column: type sharding-algorithm-name: alg_inline_type key-generate-strategy: column: id key-generator-name: alg_snowflake sharding-algorithms: alg_inline_type: type: INLINE props: algorithm-expression: energy$->{type % 2} alg_mod: type: MOD props: sharding-count: 2 alg_hash_mod: type: HASH_MOD props: sharding-count: 2 key-generators: alg_snowflake: type: SNOWFLAKE props: sql-show: true
3.ShardingSphere Proxy
1.简介
1.
ShardingSphere Proxy
定位为透明化
的数据库代理器
,即只是一个中间件
并不提供程序代码,其提供了封装了数据库二进制协议的服务端版本,用于完成对异构语言(即非Java
的项目)的支持
- 1.目前提供
Mysql
和PostgreSQL
版本,可使用任何兼容MySQL/PostgreSQL
协议的访问客户端(如(MySQL Command Client
,MySQL Workbench
,Navicat
等)操作数据- 2.对应用程序完全透明,可直接当做
MySQL/PostgreSQL
使用
3.如何设置数据分片方案
- 如何设计零迁移数据扩容分片方案
- 问题核心:如何不迁移数据,实现集群动态扩缩容?同时又能保证数据分布相对均匀?
- 关键点:整体按范围分片,保证扩容时老数据不需要迁移,范围内,按照取模分片,让每个范围内的数据分布大致均匀
4.分库分表需要解决哪些问题
- 主键唯一性
- 单机,MySQL索引可以保证唯一性。
- 分布式场景呢(每个数据库通过索引只能保证自己的唯一标识不会重复,无法简单的递增方式交给数据库自己处理,需要构建第三方的服务来保证分布式的主键唯一,还需要注意高并发的场景)
- 保证数据唯一性最常用的是UUID,用UUID生成业务主键,UUID产生的是字符串主键,排序性能很低,MySQL的innodb引擎的采用的是B+树,需要对主键进行排序,然后排成一颗b+树,UUID的主键排序性能消耗很大,UUID也不是单调递增的趋势,单调递增的好处可以直接插入尾部,所以UUID需要插入中间,对B+树结构也产生影响
- 分布式事务
- 保证多个节点的数据一致性
- SQL路由(in,大于,小于)
- 每个分片都查
- 根据分片策略去查一个分片
- 结果归并
- 每个节点只包括一部分结果
- 数据库是一个重状态的服务,从单机扩展到集群需要时刻考虑状态如何转移,阿里提出尽量不要分库分表
5.什么时候需要分库分表
6.分库分表与多数据源切换
- 分库分表
- 分库分表不光是管理多个数据源,他是对SQL的优化,改写,归并等一系列的解决方案,关注点是SQL语句
- 多数据源切换
- 如果只是需要简单的切换多个数据源,而对于SQL逻辑没有任何的限制,这时就不要选择分库分表了,直接选用多数据源切换的方案更简单
- 以ShardingSphere为例,虽然他也支持用跟SQL无关的Hint策略提供路由功能,但是在SQL改写以及归并的过程中,依然对SQL有限制
- 两种典型的动态数据源切换方案
- 根据前端请求参数动态切换数据源
- 方式2动态数据源
- 总结
- 分库分表是传统数据库在面临互联网三高压力时,最为轻量级的一种解决方案,但是分库分表并不是简单的将数据分散而已,包含了分布式唯一主键,分布式事务,分布式结果归并等一系列问题
- 在决定使用分库分表之前,要仔细分析项目的业务场景,尽量将方案设计周全,一旦开始使用分库分表,临时改变方案必然要进行大量的数据迁移,对系统的影响会非常大
1.了解ShardingSphere
- Apache ShardingSphere是一款开源分布式数据库生态项目,由JDBC(工具包,嵌入到Java应用中),Proxy(独立部署成一个服务)和Sidecar(规划中)3款产品构成
- 其中的$->{ }代表变量
3.标准分片之InIine策略
- 根据单一分片键进行精确分片(不适合范围查询)
4.标准分片之Standard策略
- 根据单一分片键进行精确或者范围分片
5.标准分片之Complex策略
- 根据多个分片键进行精确或者范围分片
6.标准分片之Hint策略
- 使用与SQL无关的方式进行分片
总结:
- ShardingSphere是目前分库分表领域影响力最大的一个框架,它提供了四种基础的分片策略,Inline,Standard,Complex,Hint.从这四种策略中可以看到任何一种数据分片策略都是针对特定的SQL语句设计的,没有万能的分片策略,只有合适的分片策略
- 这四种分片策略也是我们去构建真实分片策略的基础,未来ShardingSphere提出的新的分片策略以及真实企业的复杂分片策略都可以通过这四种分片策略组合形成
新版本分片规则
ShardingSphere扩展接口
零迁移数据扩容方案
- 实现效果:灵活规划下一个分片容量,老的数据不需要迁移
- 关键点:将范围分片和取模分片的优缺点结合起来,综合考虑
基因法多分片查询
读写分离实现方案
分库分表思想总结
navicate出现中文乱码,需要使用MySQL字符串后问题解决