分布式事务解决方案-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 事务模式,为用户打造一站式的分布式解决方案。

 

seata的分布式事务解决方案是业务层面的解决方案,只依赖于单台数据库的事务能力。Seata框架中一个分布式事务包含3中角色:TC,TM,RM。

事务协调器TC(Transaction Coordinator ):维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

TM(Transaction Manager):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

RM(Resource Manager):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运

MySQL XA与Seata(Fescar) AT的区别?

XA 方案的 RM 实际上是在数据库层RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。

Seata 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖与数据库本身对协议的支持。

使用Seata解决分布式异常

  1. 模拟分布式事务异常

  2. 使用Seata解决

1.模拟分布式事务异常

完成父工程:
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
复制代码

启动Nacos

在路劲下的地址栏输入cmd,在cmd界面输入startup/startup.cmd

运行 nacos-config.sh 将 Seata 配置导入 Nacos

注意:windows中不能直接运行sh文件,可用git启动

# 进入conf目录,git hash中执行:
sh nacos-config.sh 127.0.0.1

启动Seata Server ( JDK 8 以上环境无法启动)

# 进入bin目录 ,地址栏输入cmd
seata-server.bat -p 8090 -m file

初始化数据库

两个数据库中执行此sql

两个子工程的pom中添加seata依赖

<dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

给JDBCTemplate添加代理数据源(我用的是JDBC,如果使用的是MyBatis 或 MP处理不一样)

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));
    }

}
复制代码

将registry.conf复制到两个工程的resources下

给两个工程添加bootstrap.yml读取Nacos配置

tx-service-group 需要和 Nacos 配置中的名称一致

复制代码
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}
复制代码

Order调用Pay处添加注解@GlobalTransactional

复制代码
@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,两个数据库能够存入数据

posted @ 2022-05-06 11:28  满Sir  阅读(301)  评论(0编辑  收藏  举报