分布式事务解决方案-seata
什么是MySQL XA方案?
MySQL从5.7开始加入了分布式事务的支持。MySQL XA中拥有两种角色:RM,TM。
RM(Resource Manager):用于直接执行本地事务的提交和回滚。在分布式集群中,一台MySQL服务器就是一个RM。
TM(Transaction Manager):TM是分布式事务的核心管理者。事务管理器与每个RM进行通信,协调并完成分布式事务的处理。
发起一个分布式事务的MySQL客户端就是一个TM。
XA的两阶段提交分为Prepare阶段和Commit阶段,过程如下:
阶段一为准备(prepare)阶段:即所有的RM(Resource Manager)锁住需要的资源,在本地执行这个事务(执行sql,写redo/undo log等),但不提交,然后向TM(Transaction Manager)报告已准备就绪
阶段二为提交阶段(commit):当TM(Transaction Manager)确认所有参与者都ready后,向所有参与者发送commit命令。
什么是seata?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
MySQL XA与Seata(Fescar) AT的区别?
使用Seata解决分布式异常
-
模拟分布式事务异常
-
使用Seata解决
完成父工程:
1.新建springboot项目,删除src文件夹
2.导入pom依赖
<properties>
<java.version>1.8</java.version>
<!--spring cloud 版本-->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<!--引入Spring Cloud 依赖-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<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>
</dependencies>
完成子工程order(maven项目):
1.新建数据库pay,表名pay
2.导入maven依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> </dependencies>
3.编写application.yml
server: port: 8020 spring: application: name: pay datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/pay?serverTimezone=GMT%2B8
4.编写启动类
@SpringBootApplication @EnableDiscoveryClient public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
}
5.完成service
OrderService.java
public interface OrderService { void save(); }
OrderServiceImpl.java
@Service public class OrderServiceImpl implements OrderService {
@Autowired private JdbcTemplate jdbcTemplate; public void save(){ this.jdbcTemplate.update(" INSERT INTO `order` (`username`) VALUES ('zhangsan') "); } }
6.编写OrderController.java
@RestController public class OrderController { @Autowired private OrderService orderService; @Autowired private RestTemplate restTemplate; @GetMapping("/save") public String save(){ //订单 this.orderService.save(); int i = 10/0; //支付 //控制器 Order 通过 RestTemplate 调用 Pay 的服务 this.restTemplate.getForObject("http://localhost:8020/save",String.class); return "success"; } }
完成子工程pay(maven项目):
1.新建数据库pay,表名pay
2.导入maven依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> </dependencies>
3.编写application.yml
server: port: 8020 spring: application: name: pay datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/pay?serverTimezone=GMT%2B8
4.编写启动类
@SpringBootApplication @EnableDiscoveryClient public class PayApplication { public static void main(String[] args) { SpringApplication.run(PayApplication.class, args); }
}
5.完成service
PayService.java
public interface PayService { void save(); }
PayServiceImpl.java
@Service public class PayServiceImpl implements PayService { @Autowired private JdbcTemplate jdbcTemplate; public void save(){ this.jdbcTemplate.update(" INSERT INTO `pay` (`username`) VALUES ('张三') "); } }
6.编写PayController.java
@RestController public class PayController { @Autowired private PayService payService; @GetMapping("/save") public String save(){ this.payService.save(); return "success"; } }
使用Seate解决分布式问题
1.下载Seate https://seata.io/zh-cn/blog/download.html
我用的版本0.9.0
2.解压后分别修改nacos-config.txt和registry.conf
regisry.conf
注意serverAddr地址如果是linux中,不在本机,需要写uri
registry { type = "nacos" nacos { serverAddr = "localhost" namespace = "public" cluster = "default" } } config { type = "nacos" nacos { serverAddr = "localhost" namespace = "public" cluster = "default" } }
nacos-config.txt
名字与工程中application.yml中的name属性对应
transport.type=TCP transport.server=NIO transport.heartbeat=true transport.thread-factory.boss-thread-prefix=NettyBoss transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler transport.thread-factory.share-boss-worker=false transport.thread-factory.client-selector-thread-prefix=NettyClientSelector transport.thread-factory.client-selector-thread-size=1 transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread transport.thread-factory.boss-thread-size=1 transport.thread-factory.worker-thread-size=8 transport.shutdown.wait=3 service.vgroup_mapping.pay=default service.vgroup_mapping.order=default service.enableDegrade=false service.disable=false service.max.commit.retry.timeout=-1 service.max.rollback.retry.timeout=-1 client.async.commit.buffer.limit=10000 client.lock.retry.internal=10 client.lock.retry.times=30 client.lock.retry.policy.branch-rollback-on-conflict=true client.table.meta.check.enable=true client.report.retry.count=5 client.tm.commit.retry.count=1 client.tm.rollback.retry.count=1 store.mode=file store.file.dir=file_store/data store.file.max-branch-session-size=16384 store.file.max-global-session-size=512 store.file.file-write-buffer-cache-size=16384 store.file.flush-disk-mode=async store.file.session.reload.read_size=100 store.db.datasource=dbcp store.db.db-type=mysql store.db.driver-class-name=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true store.db.user=mysql store.db.password=mysql store.db.min-conn=1 store.db.max-conn=3 store.db.global.table=global_table store.db.branch.table=branch_table store.db.query-limit=100 store.db.lock-table=lock_table recovery.committing-retry-period=1000 recovery.asyn-committing-retry-period=1000 recovery.rollbacking-retry-period=1000 recovery.timeout-retry-period=1000 transaction.undo.data.validation=true transaction.undo.log.serialization=jackson transaction.undo.log.save.days=7 transaction.undo.log.delete.period=86400000 transaction.undo.log.table=undo_log transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registry-type=compact metrics.exporter-list=prometheus metrics.exporter-prometheus-port=9898 support.spring.datasource.autoproxy=false
在路劲下的地址栏输入cmd,在cmd界面输入startup/startup.cmd
运行 nacos-config.sh 将 Seata 配置导入 Nacos
注意:windows中不能直接运行sh文件,可用git启动
# 进入conf目录,git hash中执行: sh nacos-config.sh 127.0.0.1
# 进入bin目录 ,地址栏输入cmd seata-server.bat -p 8090 -m file
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
OrderApplication.java
@SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ return new JdbcTemplate(new DataSourceProxy(dataSource)); } }
PayApplication.java
@SpringBootApplication public class PayApplication { public static void main(String[] args) { SpringApplication.run(PayApplication.class, args); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource){ return new JdbcTemplate(new DataSourceProxy(dataSource)); } }
spring: application: name: order cloud: nacos: config: server-addr: localhost:8848 namespace: public group: SEATA_GROUP alibaba: seata: tx-service-group: ${spring.application.name}
spring: application: name: pay cloud: nacos: config: server-addr: localhost:8848 namespace: public group: SEATA_GROUP alibaba: seata: tx-service-group: ${spring.application.name}
@RestController public class OrderController { @Autowired private OrderService orderService; @Autowired private RestTemplate restTemplate; @GetMapping("/save") @GlobalTransactional public String save(){ //订单 this.orderService.save(); int i = 10/0; //支付 this.restTemplate.getForObject("http://localhost:8020/save",String.class); return "success"; } }
运行
在OrderController.java打断点
Debug运行Order
运行Pay
访问http://localhost:8010/save
查看order数据库
order表
undo_log表
直接运行完
order表
undo_log表
进行了数据的回滚,如果去掉模拟异常int i=10/0,两个数据库能够存入数据