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