新聞中心
基于阿里開源的Sentinel實(shí)現(xiàn)了服務(wù)的限流與容錯(cuò),并詳細(xì)介紹了Sentinel的核心技術(shù)與配置規(guī)則。簡單介紹了服務(wù)網(wǎng)關(guān),并對(duì)SpringCloud Gateway的核心架構(gòu)進(jìn)行了簡要說明,也在項(xiàng)目中整合了SpringCloud Gateway網(wǎng)關(guān)實(shí)現(xiàn)了通過網(wǎng)關(guān)訪問后端微服務(wù).

成都創(chuàng)新互聯(lián)服務(wù)熱線:13518219792,為您提供成都網(wǎng)站建設(shè)網(wǎng)頁設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),成都創(chuàng)新互聯(lián)網(wǎng)頁制作領(lǐng)域十余年,包括成都OPP膠袋等多個(gè)行業(yè)擁有多年建站經(jīng)驗(yàn),選擇成都創(chuàng)新互聯(lián),為企業(yè)保駕護(hù)航!
同時(shí),也基于SpringCloud Gateway整合Sentinel實(shí)現(xiàn)了網(wǎng)關(guān)的限流功能,詳細(xì)介紹了SpringCloud Gateway網(wǎng)關(guān)的核心技術(shù)。在鏈路追蹤章節(jié),我們開始簡單介紹了分布式鏈路追蹤技術(shù)與解決方案,隨后在項(xiàng)目中整合Sleuth實(shí)現(xiàn)了鏈路追蹤,并使用Sleuth整合ZipKin實(shí)現(xiàn)了分布式鏈路追蹤的可視化 。
在消息服務(wù)章節(jié),我們介紹了MQ的使用場景,引入MQ后的注意事項(xiàng)以及MQ的選型對(duì)比,在項(xiàng)目中整合了RocketMQ,并給大家介紹了RocketMQ的核心技術(shù)。
在服務(wù)配置章節(jié),我們首先介紹了服務(wù)配置與Nacos作為配置中心的相關(guān)概念,并在項(xiàng)目中整合了Nacos配置中心。接下來,又基于Nacos實(shí)現(xiàn)了動(dòng)態(tài)刷新與配置共享。
在分布式事務(wù)篇章,我們簡單介紹了分布式事務(wù)的核心原理與SpringCloud Alibaba技術(shù)棧中的Seata框架。接下來,我們就在項(xiàng)目中整合Seata框架實(shí)現(xiàn)分布式事務(wù)。
本章總覽?
分布式事務(wù)問題?
細(xì)心的小伙伴會(huì)發(fā)現(xiàn),目前,我們的項(xiàng)目中是不支持分布式事務(wù)的。也就是說,如果我們調(diào)用訂單微服務(wù)的下單接口提交訂單,如果扣減庫存失敗了,訂單依然會(huì)寫入訂單數(shù)據(jù)表,這是一種典型的分布式事務(wù)問題。
查詢數(shù)據(jù)表數(shù)據(jù)
(1)打開cmd終端,進(jìn)入MySQL命令行,并進(jìn)入shop商城數(shù)據(jù)庫,如下所示。
C:\Users\binghe>mysql -uroot -p
Enter password: ****
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.35 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use shop;
Database changed
(2)查看商品數(shù)據(jù)表,如下所示。
mysql> select * from t_product;
+------+------------+-------------+-------------+
| id | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 華為 | 2399.00 | 100 |
| 1002 | 小米 | 1999.00 | 100 |
| 1003 | iphone | 4999.00 | 100 |
+------+------------+-------------+-------------+
3 rows in set (0.00 sec)
這里,我們以id為1001的商品為例,此時(shí)發(fā)現(xiàn)商品的庫存為100。
(3)查詢訂單數(shù)據(jù)表,如下所示。
mysql> select * from t_order;
Empty set (0.00 sec)
可以發(fā)現(xiàn)訂單數(shù)據(jù)表為空。
(4)查詢訂單條目數(shù)據(jù)表,如下所示。
mysql> select * from t_order_item;
Empty set (0.00 sec)
可以看到,訂單條目數(shù)據(jù)表為空。
重現(xiàn)分布式事務(wù)問題
(1)復(fù)制商品微服務(wù)的io.binghe.shop.order.service.impl.OrderServiceV7Impl類為io.binghe.shop.order.service.impl.OrderServiceV8Impl類,后續(xù)的操作在io.binghe.shop.order.service.impl.OrderServiceV8Impl類中進(jìn)行,修改OrderServiceV8Impl類中上的@Service注解中的bean名稱為orderServiceV8,并修改saveOrder()方法的代碼。這里,只列出有改動(dòng)的部分代碼。
在saveOrder()方法中的扣減商品庫存和發(fā)送RocketMQ消息之間加入一行代碼int i = 1 / 0;,使其扣減庫存成功后拋出異常。
修改前的部分代碼如下所示。
Resultresult = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() == 1001){
throw new RuntimeException("觸發(fā)了商品微服務(wù)的容錯(cuò)邏輯: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("庫存扣減失敗");
}
log.info("庫存扣減成功");
rocketMQTemplate.convertAndSend("order-topic", order);
修改后的部分代碼如下所示。
Resultresult = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() == 1001){
throw new RuntimeException("觸發(fā)了商品微服務(wù)的容錯(cuò)邏輯: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("庫存扣減失敗");
}
log.info("庫存扣減成功");
int i= 1 / 0;
rocketMQTemplate.convertAndSend("order-topic", order);
(2)修改訂單微服務(wù)的io.binghe.shop.order.controller.OrderController類中注入的OrderService的bean名稱,將其修改為orderServiceV8,如下所示。
@Autowired
@Qualifier(value = "orderServiceV8")
private OrderService orderService;
(3)分別啟動(dòng)Nacos、Sentinel、ZinKin、RocketMQ,并啟動(dòng)用戶微服務(wù),商品微服務(wù),訂單微服務(wù)和服務(wù)網(wǎng)關(guān)。打開瀏覽器訪問http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。
返回的原始數(shù)據(jù)如下所示。
{"code":500,"codeMsg":"執(zhí)行失敗","data":"/ by zero"}可以看到,下單減庫存時(shí),系統(tǒng)已經(jīng)拋出了異常。
(4)查看各個(gè)微服務(wù)和網(wǎng)關(guān)輸出的日志信息,分別如下所示。
- 用戶微服務(wù)輸出的日志如下所示。
獲取到的用戶信息為:{"address":"北京","id":1001,"password":"c26be8aaf53b15054896983b43eb6a65","phone":"13212345678","username":"binghe"}可以看到,用戶微服務(wù)無異常信息。
- 商品微服務(wù)輸出的日志如下所示。
獲取到的商品信息為:{"id":1001,"proName":"華為","proPrice":2399.00,"proStock":100}
更新商品庫存?zhèn)鬟f的參數(shù)為: 商品id:1001, 購買數(shù)量:1可以看到,商品微服務(wù)無異常信息。
- 訂單微服務(wù)輸出的日志如下所示。
提交訂單時(shí)傳遞的參數(shù):{"count":1,"empty":false,"productId":1001,"userId":1001}
庫存扣減成功
服務(wù)器拋出了異常:{}
java.lang.ArithmeticException: / by zero可以看到,訂單微服務(wù)拋出了ArithmeticException異常。
- 網(wǎng)關(guān)服務(wù)輸出的日志如下所示。
執(zhí)行前置過濾器邏輯
執(zhí)行后置過濾器邏輯
訪問接口主機(jī): localhost
訪問接口端口: 10001
訪問接口URL: /server-order/order/submit_order
訪問接口URL參數(shù): userId=1001&productId=1001&count=1
訪問接口時(shí)長: 1054ms
可以看到,網(wǎng)關(guān)服務(wù)無異常信息。
查詢數(shù)據(jù)表數(shù)據(jù)
(1)打開cmd終端,進(jìn)入MySQL命令行,并進(jìn)入shop商城數(shù)據(jù)庫,如下所示。
C:\Users\binghe>mysql -uroot -p
Enter password: ****
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.35 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use shop;
Database changed
(2)查看商品數(shù)據(jù)表,如下所示。
mysql> select * from t_product;
+------+------------+-------------+-------------+
| id | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 華為 | 2399.00 | 99 |
| 1002 | 小米 | 1999.00 | 100 |
| 1003 | iphone | 4999.00 | 100 |
+------+------------+-------------+-------------+
3 rows in set (0.00 sec)
可以看到,此時(shí)商品數(shù)據(jù)表中,id為1001的商品庫存數(shù)量由100變成了99,減少了1個(gè)庫存數(shù)量。
(3)查看訂單數(shù)據(jù)表,如下所示。
mysql> select * from t_order;
Empty set (0.00 sec)
可以看到,訂單數(shù)據(jù)表為空。
(4)查看訂單條目數(shù)據(jù)表,如下所示。
mysql> select * from t_order_item;
Empty set (0.00 sec)
可以看到,訂單條目數(shù)據(jù)表為空。
綜上,在下單扣減庫存的業(yè)務(wù)邏輯中,在訂單微服務(wù)中扣減完商品庫存后,拋出了異常,導(dǎo)致商品庫存被扣減了。但是,訂單數(shù)據(jù)卻沒有寫入到數(shù)據(jù)庫中,出現(xiàn)了分布式事務(wù)問題。接下來,我們就在項(xiàng)目中整合Seata來解決分布式事務(wù)問題。
搭建并整合Seata?
接下來,我們就正式在項(xiàng)目中整合Seata來實(shí)現(xiàn)分布式事務(wù)。這里,我們主要整合Seata的AT模式。
搭建Seata基礎(chǔ)環(huán)境
(1)到https://github.com/seata/seata/releases/tag/v1.4.2鏈接下載Seata的安裝包和源碼,這里,下載的是1.4.2版本,如下所示。
這里我下載的都是zip壓縮文件。
(2)進(jìn)入Nacos,選擇的命名空間,如下所示。
點(diǎn)擊新建命名空間,并填寫Seata相關(guān)的信息,如下所示。
可以看到,這里我填寫的信息如下所示。
- 命名空間ID:seata_namespace_001,如果不填的話Nacos會(huì)自動(dòng)生成命名空間的ID。
- 命名空間名:seata。
- 描述:seata的命名空間。
「這里,需要記錄下命名空間的ID:seata_namespace_001,在后面的配置中會(huì)使用到?!?/p>
點(diǎn)擊確定后如下所示。
可以看到,這里為Seata在Nacos中創(chuàng)建了命名空間。
(3)解壓Seata安裝文件,進(jìn)入解壓后的seata/seata-server-1.4.2/conf目錄,修改registry.conf注冊文件,修改后的部分文件內(nèi)容如下所示。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "seata_namespace_001"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "seata_namespace_001"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}其中,namespace的值就是在Nacos中配置的Seata的命名空間ID:seata_namespace_001。
(4)修改Seata安裝文件的seata/seata-server-1.4.2/conf目錄下的file.conf文件,修改后的部分配置如下所示。
store {
mode = "db"
publicKey = ""
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}(5)在下載的Seata源碼的seata-1.4.2/script/config-center目錄下找到config.txt文件,如下所示。
將其復(fù)制到Seata安裝包解壓的根目錄下,如下所示。
接下來,修改Seata安裝包解壓的根目錄下的config.txt文件,這里還是只列出修改的部分,如下所示。
service.vgroupMapping.server-order-tx_group=default
service.vgroupMapping.server-product-tx_group=default
store.mode=db
store.publicKey=""
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=root
store.redis.sentinel.masterName=""
store.redis.sentinel.sentinelHosts=""
store.redis.password=""
「注意:在config.txt中,部分配置的等號(hào)“=”后面為空,需要在等號(hào)“=“后面添加空字符串""?!?/p>
(6)在下載的Seata源碼的seata-1.4.2/script/config-center/nacos目錄下找到nacos-config.sh文件,如下所示。
將nacos-config.sh文件復(fù)制到Seata安裝文件解壓目錄的seata/seata-server-1.4.2/scripts目錄下,其中scripts目錄需要手動(dòng)創(chuàng)建,如下所示。
(7).sh文件是Linux操作系統(tǒng)上的腳本文件,如果想在Windows操作系統(tǒng)上運(yùn)行.sh文件,可以在Windows操作系統(tǒng)上安裝Git后在運(yùn)行.sh文件。
接下來,在Git的Bash命令行進(jìn)入Seata安裝文件中nacos-config.sh文件所在的目錄,執(zhí)行如下命令。
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t seata_namespace_001 -u nacos -w nacos
其中,命令中的每個(gè)參數(shù)含義如下所示。
- -h:Nacos所在的IP地址。
- -p:Nacos的端口號(hào)。
- -g:分組。
- -t:命名空間的ID,這里我們填寫在Nacos中創(chuàng)建的命名空間的ID:seata_namespace_001。如果不填,默認(rèn)是public命名空間。
- -u:Nacos的用戶名。
- -w:Nacos的密碼。
執(zhí)行命令后的結(jié)果信息如下所示。
=========================================================================
Complete initialization parameters, total-count:89 , failure-count:0
=========================================================================
Init nacos config finished, please start seata-server.
可以看到,整個(gè)配置執(zhí)行成功。
(8)打開Nacos的配置管理-配置列表界面,切換到seata命名空間,可以看到有關(guān)Seata的配置都注冊到Nacos中了,如下所示。
(9)在MySQL數(shù)據(jù)庫中創(chuàng)建seata數(shù)據(jù)庫,如下所示。
create database if not exists seata;
接下來,在seata數(shù)據(jù)庫中執(zhí)行Seata源碼包seata-1.4.2/script/server/db目錄下的mysql.sql腳本文件,mysql.sql腳本的內(nèi)容如下所示。
-- -------------------------------- 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(128),
`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;
這里,也
文章標(biāo)題:分布式事務(wù):項(xiàng)目整合Seata實(shí)現(xiàn)分布式事務(wù)
本文路徑:http://fisionsoft.com.cn/article/codpgeg.html


咨詢
建站咨詢
