ShardingJDBC
ShardingJdbc准备-Mysql完成主从复制
概述
主从复制(也称AB复制) 允许将来自一个Mysql数据库服务器(主)的数据复制到一个或多个Mysql数据库服务器(从服务器)
复制是异步的从站不需要永久连接以接受来自主站的更新
Mysql中复制的优点
- 横向扩展解决方案-在多个从站之间分配负载以提高性能。在此环境中,所有写入和更新都必须在主服务器上进行。但是,读取可以在一个或多个从设备上进行。该模型可以提高写入性能,同时显著提高了越来越多的从设备的读取速度。
- 数据安全性-因为数据被复制到从站可以暂停复制过程,所以可以在从站上运行备份服务而不会破坏相应的主数据。
- 分析 - 可以在主服务器上创建实时数据,而信息分析可以在从服务器上进行,而不会影响主服务器的性能。
- 远程数据分发 -您可以使用复制为远程站点创建数据的本地副本,而无需永久访问主服务器。
Replication原理
主服务器角色的数据库服务器必须是开启二进制日志
主服务器上面的任何修改都会通过直接的I/O tread(I/O线程)保存在二进制日志 Binary log里面。
- 从服务器上面也启动一个I/O thread,通过配置好的用户名和密码,连接到主服务器上面请求读取二进制日志,然后把读取到二进制日志写到本地的Realy log里面。
- 从服务器上面同时开启一个SQL thread定时检查Realy log,如果发现有更新立即把更新的内容在本机的数据库上执行一遍。每个从服务器都会收到主服务器二进制日志的全部内容的副本。
- 从服务器设备负责决定应该执行二进制日志中的哪些语句。除非另外指定,否则主从二进制日志中的所有事件都在从站执行。如果需要,您可以将从服务器配置为仅处理一些特定数据库或表的事件。
具体配置
Master节点配置 /etc/my.cnf
vim /etc/my.cnf
[mysqld]
##同一局域网内注意要唯一
server-id=100
##开启二进制日志功能
log-bin=mysql-bin
##复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
##为每一个session 分配内存,在事务过程中用二进制日志的缓存
binlog_cache_size=1M
##主从复制的格式(mixed,statement,row,默认是statement)
binlog_format=mixed
#进入mysql查询通过命令查看是否配置成功
mysql -u root -p
show variables like '%server_id%';
Slave节点配置 /etc/my.cnf
vim /etc/my.cnf
[mysqld]
##同一局域网内注意要唯一
server-id=101
##开启二进制日志功能,以备Slave作为其他Slave的Master时使用
log-bin=mysql-slave-bin
##realy_log 配置中继日志
relay_log=edu-mysql-relay-bin
##复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
##如果需要同步函数或者存储过程设置为true
log_bin_trust_function_creators=true
##为每一个session 分配内存,在事务过程中用二进制日志的缓存
binlog_cache_size=1M
##主从复制的格式(mixed,statement,row,默认是statement)
binlog_format=mixed
##跳过主从复制中遇到的所有错误或者指定类型错误避免slave端复制终端
##如1062错误是指一些主键复制,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
#进入mysql查询通过命令查看是否配置成功
show variables like '%server_id%';
在master服务器授权slave服务器可以同步权限
mysql -u -root -p master的密码
#查看master的二进制文件名称和复制节点位置
show master status;
#授予slave服务器可以同步master服务
grant replication slave,replication client on *.* to 'root'@'slave服务的ip' identified by 'slave服务器的密码'
##更新
flush privileges;
#查看mysql现在有哪些用户及对应的IP权限(可以不执行,只是一个查看)
select user,host from mysql.user;
##配置从服务
change master to master_host='192.168.0.177', master_user='root', master_password='123456',master_port=3307,master_log_file='mysql-bin.000006',master_log_pos=704;
##启动主从复制
start slave;
##查看是否启动成功
show slave status\G;
使用springBoot操作
配置pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
</dependency>
<!--集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<!--MyBatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<!--MyBatis-Plus自动生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
<!--sharding-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>4.0.0-RC1</version>
</dependency>
</dependencies>
配置yml文件
server:
port: 8204
servlet:
context-path: /api
spring:
shardingsphere:
#参数配置显示sql
props:
sql:
show: true
#配置数据源
datasource:
#给每个数据源取别名,下面ds1,ds2,ds3任意取
names: ds1,ds2
#给master-ds1每个数据源配置数据库连接信息
ds1:
#配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3307/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
maxPoolSize: 100
minPoolSize: 5
#给slave-ds2每个数据源配置数据库连接信息
ds2:
#配置druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3308/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
maxPoolSize: 100
minPoolSize: 5
#配置默认数据源默认为ds1
sharding:
#默认数据源主要用于写操作,注意一定要配置读写分离,如果不配置就会吧所有节点当中slave节点,新增就会报错
default-data-source-name: ds1
#配置数据源的读写分离,但是数据库一定要做主从复制
masterslave:
name: ms
#配置主库master,负责数据的写入
master-data-source-name: ds1
#配置从库slave节点
slave-data-source-names: ds2
#配置slave节点的负载均衡策略,采用轮询 默认是random_robin
load-balance-algorithm-type: round_robin
#配置主从名称,可以任意取名称
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Mysql分库分表原理
01、为什么要分库分表
一般机器为(4核16G),单库的Mysql并发超过了2K,系统基本就完蛋了。最好是并发量控制在1k左右。这里就引出了一个问题为什么要分库分表?
分库分表的目的:解决高并发,和数据量大的问题。
1.高并发情况下,会造成IO读写频繁,自然就造成读写缓慢,甚至是宕机。一般单库不要超过2K并发,NB的机器除外。
2.数据量大的问题。主要由于底层索引实现导致,Mysql的索引实现为B+TREE,数据量大会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制是64TB。
要解决上诉问题。最常见做法,就是分库分表。分库分表的目的是将一个表拆成N个表,就是让每个表的数据量控制在一定范围内,保证sql的性能。一个表数据建议不要超过500W。
02、分库分表
又分为垂直拆分和水平拆分
水平拆分: 统一个表的数据拆到不同的库不同的表中。可以根据时间,地区,或者某个业务建维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致。
**垂直拆分:**就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都要包含部分字段。一般来说,可以根据业务维度进行拆分为订单,订单支持,订单地址,订单商品,订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表。
03、不停机分库分表数据迁移
一般数据库的拆分也是有个过程的,一开始是单表,后面慢慢拆分成多表。那么我们就看下如何平滑的从Mysql单表过度到Mysql的分库分表架构。
1.利用mysql+canal做增量数据同步,利用分库分表中间件,将数据路由到对应的新表中。
2.利用分库分表中间件,全量数据导入到对应的新表中。
3.通过单表数据和分库分表数据两两比较,更新不匹配的数据到新表中。
4.数据稳定后,将单表的配置切换到分库分表配置上。
04、总结
垂直拆分:业务模块拆分,商品库,用户库,订单库
水平拆分:对表进行水平拆分
表进行垂直拆分:表的字段过多,字段使用频率不一。
shardingJdbc的分库和分表
01、逻辑表
逻辑表是指:水平拆分的数据库或者数据库表的相同路基和数据库结构表的总称。比如用户数据根据用户id%2拆分成为2个表,分别是:user0和user1。他们的逻辑表名是:user。
在shardingjdbc中的定义方式如下:
spring:
shardingsphere:
sharding:
tables:
#逻辑表名称
user:
02、分库分表数据节点-actual-data-nodes
tables:
#逻辑表名称
user:
#数据节点:多数据源$->{0..N}.逻辑表名称$->{0..N} 相同表
actual-data-nodes: ds$->{0..2}.user$->{0..1}
#数据节点:多数据源$->{0..N}.逻辑表名称$->{0..N} 不同表
actual-data-nodes:ds0.user$->{0..1},ds1.user$->{2..4}
#指定单数据源的配置方式
actual-data-nodes:ds0.user$->{0..1}
#全部手动指定
actual-data-nodes:ds0.user0,ds0.user1,ds1.user0,ds1.user1,
数据分片是最小的单元,由数据源名称和数据表组成,比如:ds0.ksd_user0。
寻找规则:
03、分库分表5种分片策略
数据源分片分为两种:
- 数据源分片
- 表分片
这两个是不同维度的分片规则,但是它们能用的分片策略和规则是一样的。他们由两部分构成:
- 分片建
- 分片算法
第一种: none
对应NoneShardingStragey,不分片策略,SQL会被发给所有节点去执行,这个规则没有子项目可以配置
第二种:inline 行表达时分片策略(核心,必须掌握)
对应lnlineShardingStragey。使用Groovy的表达式,提供对SQL语句中的=和in的分片操作支持,只支持单分片建。
对于简单的分片算法,可以通过简单的配置使用,避免繁琐的java代码开放,如user$(分片键(数据表字段)userid%5)表示根据user表的某一个字段(userid)模5从而分为5张表,表名为:user0到user4。库也是如此
tables: #逻辑表名称 user: #数据节点:多数据源$->{0..N}.逻辑表名称$->{0..N} 相同表 actual-data-nodes: ds$->{0..2}.user$->{0..1} #拆分库策略,也就是什么样子的数据放入到哪个数据库中 database-strategy: inline: sharding-column: userid #分片字段 algorithm-expression: ds$->{userid%3} #分片算法表达式 #拆分表策略,也就是什么样子的数据放入哪个数据表中。 table-strategy: inline: sharding-column: age #分片字段 algorithm-expression: user$->{age%2} #分片算法表达式
algorithm-expression行表达式:
${begin…end}表示区间范围
${[unit1,unit2,…,unitn]}表示枚举值
行表达式如果出现连续多个
e
x
p
r
e
s
s
s
i
o
n
或
{expresssion}或
expresssion或->{expression}表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。
第三种:根据时间日期-按照标准规则分库分表
标准分片-Standard
- 对应StrandardShardingStrategy.提供对SQL语句中=,in和between and 的分片操作支持。
- StrandardShardingStrategy只支持单片分表键。提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。
- PreciseShardingAlgorithm是可选的,是用于吃力Between and分片,如果不配置和RangeShardingAlgorithm,SQL的Between And 将按照全库路由处理
#配置默认数据源默认为ds1 sharding: #默认数据源主要用于写操作,注意一定要配置读写分离,如果不配置就会吧所有节点当中slave节点,新增就会报错 default-data-source-name: ds0 #配置分表规则 tables: # 逻辑表名称 db_user: actual-data-nodes: ds$->{0..1}.db_user$->{0..1} database-strategy: standard: sharding-column: birthday preciseAlgorithmClassName: com.gf.shardingjdbc.config.BirthdayAlgorithm table-strategy: inline: sharding-column: age algorithm-expression: db_user$->{age%2}
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.time.LocalDate;import java.time.LocalDateTime;import java.util.*;public class BirthdayAlgorithm implements PreciseShardingAlgorithm<LocalDate> { List<LocalDate> dateTimes = new ArrayList<>(); { dateTimes.add(LocalDate.of(2020, 1, 1)); dateTimes.add(LocalDate.of(2021, 1, 1)); } @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<LocalDate> shardingValue) { //获取数据库真实的值 LocalDate value = shardingValue.getValue(); //获取数据源的名称和信息列表 Iterator<String> iterator = availableTargetNames.iterator(); String target = null; for (LocalDate dateTime : dateTimes) { target = iterator.next(); if (value.isBefore(dateTime)) { break; } } //返回具体的数据源 return target; }}
分片日期规则配置
第四种:ShardingSphere - 符合分片策略
- 对应接口:HintShardingStrategy。通过Hint而非SQL解析的方式分片策略
- 对于分片字段非SQL决定,而是由其他外置条件决定的场景,使用SQL hint 灵活的注入分片字段。列如:按照用户登录的时间,主键等进行分库,而数据库没有此字段。SQL hint支持通过javaAPI和SQL注解两种方式使用。让分库分表更加灵活。
第五种:ShardingSphere -hint分片策略(了解)
- 对应ComplexShardingStrategy。符合分片策略提供对应SQL语句中-,in和between and的分片操作支持。
- ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键组合以及分片操作符传至分片算法,完全由开发者自己实现提供最大的灵活度。
完整案例和配置如下
准备两个数据库sharding-db。名字相同,两个数据源ds0和ds1。
每个数据库下方test_user0和test_user1即可。
数据库规则,性别为偶数的放入ds0库,奇数的放入ds1库。
数据库规则,年龄为偶数的放入test_user0,奇数放入test_user1库中。
server: port: 8204 servlet: context-path: /apispring: shardingsphere: #参数配置显示sql props: sql: show: true #配置数据源 datasource: #给每个数据源取别名,下面ds1,ds2,ds3任意取 names: ds0,ds1 #给master-ds1每个数据源配置数据库连接信息 ds0: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3307/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #给slave-ds2每个数据源配置数据库连接信息 ds1: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3308/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #配置默认数据源默认为ds1 sharding: #默认数据源主要用于写操作,注意一定要配置读写分离,如果不配置就会吧所有节点当中slave节点,新增就会报错 default-data-source-name: ds0 #配置分表规则 tables: # 逻辑表名称 db_user: actual-data-nodes: ds$->{0..1}.db_user$->{0..1} database-strategy: inline: sharding-column: age algorithm-expression: ds$->{age%2} table-strategy: inline: sharding-column: age algorithm-expression: db_user$->{age%2}# #配置数据源的读写分离,但是数据库一定要做主从复制# masterslave:# name: ms# #配置主库master,负责数据的写入# master-data-source-name: ds1# #配置从库slave节点# slave-data-source-names: ds2# #配置slave节点的负载均衡策略,采用轮询 默认是random_robin# load-balance-algorithm-type: round_robin servlet: multipart: max-file-size: 100MB max-request-size: 100MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
shardingJdbc分布式主键配置
shardingJdbc提供灵活的配置分布式主键生成策略方式。在分片规则配置模块每个表的主键生成策略。默认使用学花算法。(snowflake)生成bit的长整形数据。支持两种配置。
- SNOWFLAKE
- UUID
切记:主键列不能自增长数据类型是:bigint(20)
spring: shardingsphere: sharding: tables: test_user: key-generator: column: id type: SNOWFLAKE
根据年月来进行分片
inline 方式yml
server: port: 8204 servlet: context-path: /apispring: shardingsphere: #参数配置显示sql props: sql: show: true #配置数据源 datasource: #给每个数据源取别名,下面ds1,ds2,ds3任意取 names: ds0,ds1 #给master-ds1每个数据源配置数据库连接信息 ds0: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3307/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #给slave-ds2每个数据源配置数据库连接信息 ds1: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3308/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #配置默认数据源默认为ds1 sharding: #默认数据源主要用于写操作,注意一定要配置读写分离,如果不配置就会吧所有节点当中slave节点,新增就会报错 default-data-source-name: ds0 #配置分表规则 tables: # 逻辑表名称 db_user: key-generator: column: id type: SNOWFLAKE actual-data-nodes: ds$->{0..1}.db_user_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')} database-strategy: inline: sharding-column: id algorithm-expression: ds$->{id%2} table-strategy: #使用inline的方法 inline: sharding-column: birthday algorithm-expression: db_user_$->{birthday}# #配置数据源的读写分离,但是数据库一定要做主从复制# masterslave:# name: ms# #配置主库master,负责数据的写入# master-data-source-name: ds1# #配置从库slave节点# slave-data-source-names: ds2# #配置slave节点的负载均衡策略,采用轮询 默认是random_robin# load-balance-algorithm-type: round_robin servlet: multipart: max-file-size: 100MB max-request-size: 100MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
standard 方式yml
server: port: 8204 servlet: context-path: /apispring: shardingsphere: #参数配置显示sql props: sql: show: true #配置数据源 datasource: #给每个数据源取别名,下面ds1,ds2,ds3任意取 names: ds0,ds1 #给master-ds1每个数据源配置数据库连接信息 ds0: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3307/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #给slave-ds2每个数据源配置数据库连接信息 ds1: #配置druid数据源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3308/test_user?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true username: root password: 123456 maxPoolSize: 100 minPoolSize: 5 #配置默认数据源默认为ds1 sharding: #默认数据源主要用于写操作,注意一定要配置读写分离,如果不配置就会吧所有节点当中slave节点,新增就会报错 default-data-source-name: ds0 #配置分表规则 tables: # 逻辑表名称 db_user: key-generator: column: id type: SNOWFLAKE actual-data-nodes: ds$->{0..1}.db_user_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')} database-strategy: inline: sharding-column: id algorithm-expression: ds$->{id%2} table-strategy: #使用inline的方法# inline:# sharding-column: birthday# algorithm-expression: db_user_$->{birthday} #使用standard的方法 standard: sharding-column: birthday preciseAlgorithmClassName: com.gf.shardingjdbc.config.YearMonthShardingAlgorithm# #配置数据源的读写分离,但是数据库一定要做主从复制# masterslave:# name: ms# #配置主库master,负责数据的写入# master-data-source-name: ds1# #配置从库slave节点# slave-data-source-names: ds2# #配置slave节点的负载均衡策略,采用轮询 默认是random_robin# load-balance-algorithm-type: round_robin servlet: multipart: max-file-size: 100MB max-request-size: 100MB jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
策略类
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import java.util.Collection;/** * <p> * * </p> * * @author gf * @date 2021/10/1415:34 */public class YearMonthShardingAlgorithm implements PreciseShardingAlgorithm<String> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) { return shardingValue.getLogicTableName() + "_" + shardingValue.getValue(); }}
ShardingJdbc的事务管理
数据库事务需要满足ACID(原子性,一致性,隔离性,持久性)四个特性。
- 原子性(Atomicity)指事务作为整体来执行,要么全部执行要么全部不执行。
- 一致性(Consistency)指事务应确保数据从一个一致的状态转变为另一个一致的状态。
- 隔离性(Isolation) 指多个事务并发执行时,一个事务的执行不影响其他事务执行。
- 持久性(Durability)指已提交的事务修改数据会被持久保存。
在单一数据节点中,事务权限对于单一数据库资源的访问控制,称之为本地事务。几乎所有的成熟的关系型数据库提供了对本地事务的原生支持。但是在基于微服务的分布式应用环境下,越来越多的应用场景要求对于多个服务的访问及其相对应的多个数据库资源能纳入到同一个事务当中,分布式事务应运而生。
关系型数据库虽然对本地事务提供了完整的ACID原生支持。但在分布式场景下,它却成为系统性能的桎梏。如何让数据库在分布式场景下满足ACID的特性或找寻相应的代替方案,是分布式事务的重点工作。
本地事务
在不开启任何分布式事务管理器的前提下,让每个数据节点各自管理自己的事务。它们之间没有协调以及通信的能力,也并不互相知晓其他数据节点事务的成功与否。本地事务在性能方面无任何损耗,但在强一致性方面则力不从心。
- 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中。
- 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能回滚。
- 不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交。
两阶段提交
XA协议最早的分布式事务模型是由X/Open Distributed Transaction Processing(DTP)模型,简称XA协议。
基于XA协议实现的分布式事务对业务侵入很小,它最大的优点就是对使用方透明,用户可以像使用本地事务一样基于XA协议分布式事务,XA协议能够严格保障事务ACID特性。
严格保障事务ACID特性是一种双刃剑,事务执行过程中需要资源全部锁定,他更加适用于执行时间确定的短事务。对于事务来说,整个事务进行期间对数据的独占,将导致热点数据依赖业务系统并发性能衰退明显。因此,在高并发的性能至上场景中,基于XA协议的分布式事务并不是最佳选择。
- 支持数据分片后的跨库XA事务
- 两阶段提交保证操作的原子性和数据的强一致性
- 服务宕机重启后,提交/回滚中的事务可自动恢复
- SPI机制整合主流的XA事务管理器,默认Atomikos,可以选择使用Narayana和Bitronix
- 同时支持XA和非XA的连接池
- 提供spring-boot和namespace的接入端
柔性事务
如果将实现ACID的事务要素的事务称之为刚性事务,那么基于BASE事务要素则称之为柔性事务,BASE是基本可用,柔性状态和最终一致性这三个要素的缩写。
基本可用(Basically Availabel) 保证分布式事务参与方不一定同时在线。
柔性状态(Solt state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能察觉。
最终一致性(Eventually consistent) 通常是通过消息传递的方式保证系统的最终一致性。
在ACID事务中对隔离性的要求很高,在事务执行过程中,必须所有的资源锁定。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面,通过放宽对强一致性要求,来换取系统吞吐量的提升。
柔性事务-Saga
- 完全支持跨库事务
- 支持失败SQL重试及最大努力送达
- 支持反向SQL、自动生成更新快照以及自动补偿
- 默认使用关系型数据库进行快照及事务日志的持久化,支持使用SPI的方式加载其他类型的持久化
柔性事务-SEATA
- 完全支持跨库分布式事务
- 支持RC隔离级别
- 通过undo快照进行事务回滚
- 支持服务宕机后的,自动恢复提交中的事务
导入shardingjdbc包
<!--sharding 事务--> <dependency> <groupId>io.shardingsphere</groupId> <artifactId>sharding-transaction-spring-boot-starter</artifactId> <version>3.1.0</version> </dependency>
在业务代码中加上注解
@Transactional(rollbackFor = Exception.class) @ShardingTransactionType(TransactionType.XA)