ShardingSphere |
算法刷题专栏 | 面试必备算法 | 面试高频算法
越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨
作者简介:硕风和炜,CSDN-Java领域优质创作者,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享
恭喜你发现一枚宝藏博主,赶快收入囊中吧
人生如棋,我愿为卒,行动虽慢,可谁曾见我后退一步?
ShardingSphere |
数据库事务需要满足 ACID(原子性、一致性、隔离性、持久性)四个特性:
在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为本地事务。 几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 但是在基于微服务的分布式应用环境下,越来越多的应用场景要求对多个服务的访问及其相对应的多个数据库资源能纳入到同一个事务当中,分布式事务应运而生。
关系型数据库虽然对本地事务提供了完美的 ACID 原生支持。 但在分布式的场景下,它却成为系统性能的桎梏。 如何让数据库在分布式场景下满足 ACID 的特性或找寻相应的替代方案,是分布式事务的重点工作。
Sharding-JDBC分布式事务详解官方指导手册
ShardingSphere 对外提供 begin/commit/rollback 传统事务接口,通过 LOCAL,XA,BASE 三种模式提供了分布式事务的能力。
在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。 通过放宽对强一致性要求,来换取系统吞吐量的提升。
基于 ACID 的强一致性事务
和基于 BASE 的最终一致性事务
都不是银弹
,只有在最适合的场景中才能发挥它们的最大长处。 Apache ShardingSphere 集成了 SEATA 作为柔性事务的使用方案。 可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。
在191.168.10.132
服务器上创建ljw_position_db0
数据库,然后在数据库中创建dept
职位表和dept_detail
职位描述表 ;
在191.168.10.133
服务器上创建ljw_position_db1
数据库,然后在数据库中创建dept
职位表和dept_detail
职位描述表 。
-- 职位表
CREATE TABLE `dept` (
`Id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(256) DEFAULT NULL,
`salary` varchar(50) DEFAULT NULL,
`city` varchar(256) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 职位描述表
CREATE TABLE `dept_detail` (
`Id` bigint(11) NOT NULL AUTO_INCREMENT,
`pid` bigint(11) NOT NULL DEFAULT '0',
`description` text,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
引入依赖 (注意: 在这里使用ShardingSphere4.1版本为案例
进行分布式事务的实战)
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.3version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-jdbc-spring-boot-starterartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-core-commonartifactId>
<version>4.1.0version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.10version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.5.8version>
dependency>
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-boot-starterartifactId>
<version>2.0.5version>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>20.0version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-transaction-xa-coreartifactId>
<version>4.1.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
与数据库表中对应的实体类
@TableName("dept")
@Data
@ToString
public class Dept{
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String salary;
private String city;
}
@TableName("dept_detail")
@Data
@ToString
public class DeptDetail {
@TableId(type = IdType.AUTO)
private Long id;
private Long pid;
private String description;
}
编写对应的数据库持久层UserMapper接口
@Repository
public interface DeptMapper extends BaseMapper<Dept> {
}
@Repository
public interface DeptDetailMapper extends BaseMapper<DeptDetail> {
}
@Repository注解的类需要交给我们的Spring容器进行管理,因此需要我们在主启动类加上扫描接口的注解。
@SpringBootApplication
@MapperScan("com.ljw.mapper")
public class ShardingSphereApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereApplication.class, args);
}
}
配置读写分离相关配置的信息
# 应用名称
spring.application.name=sharding-jdbc-transaction
# 打印SQl
spring.shardingsphere.props.sql-show=true
# 端口
server.port=8888
# 配置数据源
spring.shardingsphere.datasource.names=db0,db1
# 数据源1
spring.shardingsphere.datasource.db0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db0.jdbc-url=jdbc:mysql://192.168.10.132:3306/ljw_position_db0?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.db0.username=root
spring.shardingsphere.datasource.db0.password=root
# 数据源2
spring.shardingsphere.datasource.db1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db1.jdbc-url=jdbc:mysql://192.168.10.133:3306/ljw_position_db1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.db1.username=root
spring.shardingsphere.datasource.db1.password=root
# 分库策略
spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=db$->{id % 2}
spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=pid
spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=db$->{pid % 2}
# 分布式主键生成
spring.shardingsphere.sharding.tables.position.key-generator.column=id
spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.position_detail.key-generator.column=id
spring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptMapper deptMapper;
@Autowired
private DeptDetailMapper deptDetailMapper;
@RequestMapping("/save")
public String saveDept(){
for(int i=0;i<6;i++){
Dept dept = new Dept();
dept.setName("Java高级开发工程师" + i);
dept.setSalary("40K");
dept.setCity("北京");
deptMapper.insert(dept);
// 注意 : 模拟出现 BUG -> 然后去数据库中验证数据是否插入!
if(i == 4){
throw new RuntimeException("模拟出现 BUG -> 然后去数据库中验证数据是否插入!");
}
DeptDetail deptDetail = new DeptDetail();
deptDetail.setPid(dept.getId());
deptDetail.setDescription("这是Java高级开发工程师具体的职位描述" + i);
deptDetailMapper.insert(deptDetail);
}
return "Save Successfully!";
}
}
启动项目打开浏览器,访问接口http://localhost:8888/dept/save
,到数据库中验证数据!
程序出现异常,检查数据库, 发现数据库的数据插入了,但是数据是不完整。
主启动类添加@EnableTransactionManagement注解,开启声明式事务
@EnableTransactionManagement
@SpringBootApplication
@MapperScan("com.ljw.mapper")
public class ShardingSphereApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereApplication.class, args);
}
}
方法上添加@Transactional注解
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptMapper deptMapper;
@Autowired
private DeptDetailMapper deptDetailMapper;
@Transactional
@RequestMapping("/save")
public String saveDept(){
for(int i=0;i<6;i++){
Dept dept = new Dept();
dept.setName("Java高级开发工程师" + i);
dept.setSalary("40K");
dept.setCity("北京");
deptMapper.insert(dept);
// 注意 : 模拟出现 BUG -> 然后去数据库中验证数据是否插入!
if(i == 4){
throw new RuntimeException("模拟出现 BUG -> 然后去数据库中验证数据是否插入!");
}
DeptDetail deptDetail = new DeptDetail();
deptDetail.setPid(dept.getId());
deptDetail.setDescription("这是Java高级开发工程师具体的职位描述" + i);
deptDetailMapper.insert(deptDetail);
}
return "Save Successfully!";
}
}
启动项目打开浏览器,访问接口http://localhost:8888/dept/save
,到数据库中验证数据(项目启动之前要把数据库中原来的数据删除!)!
程序出现错误,检查数据库, 发现数据库中没有任何数据,说明数据发生了回滚的操作。
问题来了:
问题1: 为什么会出现回滚操作呢?此时是分布式环境呀? 难道@Transactional注解可以解决分布式事务吗?(@Transactional不能解决分布式事务
)
Sharding-JDBC中的本地事务在以下两种情况是完全支持的:
本地事务不支持的情况:
不支持因网络、硬件异常导致的跨库事务;例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交。
对于因网络、硬件异常导致的跨库事务无法支持很好理解,在分布式事务中无论是两阶段还是三阶段提交都是直接或者间接满足以下两个条件:
1.有一个事务协调者 2.事务日志记录
本地事务并未满足上述条件,自然是无法支持
问题2:为什么逻辑异常导致的跨库事务能够支持?
导入sharding-transaction-xa-core依赖
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-transaction-xa-coreartifactId>
<version>4.1.0version>
dependency>
方法上添加@ShardingTransactionType(TransactionType.XA)注解
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptMapper deptMapper;
@Autowired
private DeptDetailMapper deptDetailMapper;
@Transactional
@ShardingTransactionType(TransactionType.XA)
@RequestMapping("/save")
public String saveDept(){
for(int i=0;i<6;i++){
Dept dept = new Dept();
dept.setName("Java高级开发工程师" + i);
dept.setSalary("40K");
dept.setCity("北京");
deptMapper.insert(dept);
// 注意 : 模拟出现 BUG -> 然后去数据库中验证数据是否插入!
if(i == 4){
throw new RuntimeException("模拟出现 BUG -> 然后去数据库中验证数据是否插入!");
}
DeptDetail deptDetail = new DeptDetail();
deptDetail.setPid(dept.getId());
deptDetail.setDescription("这是Java高级开发工程师具体的职位描述" + i);
deptDetailMapper.insert(deptDetail);
}
return "Save Successfully!";
}
}
启动项目打开浏览器,访问接口http://localhost:8888/dept/save
,到数据库中验证数据!(项目启动之前记得清除数据!)
程序出现错误,检查数据库, 发现数据库中没有任何数据,说明数据发生了回滚的操作。
本篇文章主要讲解了Sharding-JDBC分布式事务详解与实战,实操过程非常重要,大家一定要动手亲自实践一下,必须掌握。下节预告,ShardingProxy实战,大家敬请期待呦!!!。
最后,我想和大家分享一句一直激励我的座右铭,希望可以与大家共勉! |