1. 程式人生 > 其它 >分散式事務解決方案-seata

分散式事務解決方案-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: order
  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,兩個資料庫能夠存入資料