SpringCloudAlibaba隨筆目錄
二、SpringCloudAlibaba項目之Nacos搭建及服務注冊
三、SpringCloudAlibaba項目之生產者與消費者
四、SpringCloudAlibaba項目之Ribbon負載均衡
五、SpringCloudAlibaba項目之OpenFeign遠程調用
六、SpringCloudAlibaba項目之Nacos-config配置中心
七、SpringCloudAlibaba項目之Sentinel流量控制
八、SpringCloudAlibaba項目之Seata分布式事務
九、SpringCloudAlibaba項目之GateWay網關
十、SpringCloudAlibaba項目之SkyWalking鏈路追蹤
SpringCloudAlibaba項目之Seata分布式事務
1、分布式事務
事務是數據庫的概念,數據庫事務(ACID:原子性、一致性、隔離性和持久性);
分布式事務的產生,是由於數據庫的拆分和分布式架構(微服務)帶來的,在常規情況下,我們在一個進程中操作一個數據庫,這屬於本地事務,如果在一個進程中操作多個數據庫,或者在多個進程中操作一個或多個數據庫,就產生了分布式事務;
(1)數據庫分庫分表就產生了分布式事務;
(2)項目拆分服務化也產生了分布式事務;
2、Seata簡介
Seata 是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。
Seata為用戶提供了AT、TCC、SAGA和XA事務模式,為用戶打造一站式的分布式解決方案;
四種事務模式中,XA模式正在開發中...,其他事務模式已經實現;
目前使用的流行度情況是:AT > TCC > Saga;
我們可以參看seata各公司使用列表:
https://github.com/seata/seata/issues/1246 大部分公司都采用的AT事務模式;
Seata已經在國內很多團隊開始落地,其中不乏有大公司;
Github:https://github.com/seata/seata/releases
官網文檔:https://seata.io/zh-cn/docs/overview/what-is-seata.html
在Seata的架構中,一共有三個角色:
TC (Transaction Coordinator) - 事務協調者
維護全局和分支事務的狀態,驅動全局事務提交或回滾;
TM (Transaction Manager) - 事務管理器
定義全局事務的范圍:開始全局事務、提交或回滾全局事務;
RM (Resource Manager) - 資源管理器
管理分支事務處理的資源,與TC交互以注冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾;
其中TC為單獨部署的 Server 服務端,TM和RM為嵌入到應用中的 Client 客戶端;
在Seata中,一個分布式事務的生命周期如下:
TM請求TC開啟一個全局事務,TC會生成一個XID作為該全局事務的編號,XID會在微服務的調用鏈路中傳播,保證將多個微服務的子事務關聯在一起;
RM請求TC將本地事務注冊為全局事務的分支事務,通過全局事務的XID進行關聯;
TM請求TC告訴XID對應的全局事務是進行提交還是回滾;
TC驅動RM將XID對應的自己的本地事務進行提交還是回滾;
3、Seata服務端部署(TC事務協調者)
因為TC需要進行全局事務和分支事務的記錄,所以需要對應的存儲,目前,TC有三種存儲模式( store.mode ):
file模式:適合單機模式,全局事務會話信息在內存中讀寫,並持久化本地文件 root.data,性能較高;
db模式:(mysql 5.7+)適合集群模式,全局事務會話信息通過 db 共享,相對性能差點;
redis模式:解決db存儲的性能問題;
這里使用windows系統演示db模式;
(1)下載安裝包
seata下載地址:https://github.com/seata/seata/releases
(2)修改配置文件
(3)創建數據庫表
官網已經為我們創建好了腳本,只需要引入使用即可:https://github.com/seata/seata/tree/1.4.0/script/server/db
運行mysql.sql文
-- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(96), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
總共三張表,global_table(存儲全局會話數據)、branch_table(存儲分支會話數據)、lock_table(存儲鎖數據)
4、Seata服務端搭建-nacos
(1)修改注冊中心
(2)修改配置中心
(3)啟動seata
5、Seata客戶端搭建
(1)pom.xml文件引入依賴
<!-- nacos 服務注冊發現(客戶端)依賴 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- nacos-config 配置中心依賴 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- seata 分布式事務依賴 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
接口調用需要openfeign依賴
<!-- openfeign 遠程調用依賴 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
(2)各微服務對應的數據庫中添加undo_log表
CREATE TABLE `undo_log`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY(`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
(3)將配置注冊到nacos注冊中心
到https://github.com/seata/seata 下,先將整個文件下載下來,將內部的script腳本文件復制一份到seata的根目錄下
修改內部script\config\config.txt文件內容
關於config.txt文件中vgroupMapping.default_tx_group和在項目里配置的要一致
執行script\config-center\nacos\nacos-config.sh把config.txt文件導入到nacos配置中心
注意:執行出現找不到config.txt的話修改一下nacos-config.sh里面關於config.txt的路徑指向你本地的即可。如果電腦無法運行.sh文件,安裝git即可。
注冊成功
(3)各微服務添加配置
application.properties
#配置seata的注冊中心
seata.enabled=true
seata.application-id=${spring.application.name}
#配置事務分組
seata.tx-service-group=default_tx_group
seata.registry.type=nacos
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.registry.nacos.application=seata-server
seata.registry.nacos.namespace=
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
seata.registry.nacos.group=SEATA_GROUP
#配置seata的配置中心
seata.config.type=nacos
seata.config.nacos.server-addr=127.0.0.1:8848
seata.config.nacos.namespace=
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
seata.config.nacos.group=SEATA_GROUP
消費者
OrderController
/** * 訂單服務 */ @RestController @RequestMapping("/order") public class OrderController { @Resource private StockOpenFeignService stockOpenFeignService; @Resource private OrderService orderService; /** * 新增訂單 * @return */ @RequestMapping("/addOrder") @GlobalTransactional //分布式事務注解,這個一般放在業務層,這里圖方便 public String addOrder(){ Order order = new Order("襪子"); int id = orderService.addOrder(order); System.out.println("訂單新增成功-id:" + id); //調用庫存扣減 String result = stockOpenFeignService.subStock(id); return new Result(200,"訂單服務-訂單新增成功",result).toString(); } }
StockOpenFeignService
/** * 庫存服務接口 * name:指定調用rest接口所對應的服務名 * path:指定調用rest接口所在的StockController指定的@RequestMapping */ @FeignClient(name = "service-seata-stock",path = "stock") public interface StockOpenFeignService { //聲明需要調用的rest接口對應的方法 /** * 庫存扣減 * @return */ @PostMapping("/subStock") String subStock(int id); }
生產者
StockController
/** * 庫存服務 */ @RestController @RequestMapping("/stock") public class StockController { @Value("${server.port}") private String port; @Resource private StockService stockService;/** * 庫存扣減 * @return */ @PostMapping("/subStock") public String subStock(@RequestBody int id) { System.out.println("進入庫存扣減接口");int num = stockService.subStock(id); Result result = null; if(num > 0){ System.out.println("庫存扣減成功-id:" + id); result = new Result(200,"扣減庫存服務-庫存成功"); }else { result = new Result(201,"扣減庫存服務-庫存失敗"); } return result.toString(); } }
總結: 實踐是檢驗真理的唯一標准。
參考鏈接:https://blog.csdn.net/calonmo/article/details/106630754