1. Spring Boot 集成 Seata 解決分布式事務(wù)問(wèn)題

        共 6723字,需瀏覽 14分鐘

         ·

        2020-12-30 13:33

        seata 簡(jiǎn)介

        Seata 是 阿里巴巴2019年開(kāi)源的分布式事務(wù)解決方案,致力于在微服務(wù)架構(gòu)下提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。在 Seata 開(kāi)源之前,Seata 對(duì)應(yīng)的內(nèi)部版本在阿里內(nèi)部一直扮演著分布式一致性中間件的角色,幫助阿里度過(guò)歷年的雙11,對(duì)各業(yè)務(wù)進(jìn)行了有力的支撐。經(jīng)過(guò)多年沉淀與積累,2019.1 Seata 正式宣布對(duì)外開(kāi)源 。目前 Seata 1.0 已經(jīng) GA。

        微服務(wù)中的分布式事務(wù)問(wèn)題

        讓我們想象一下傳統(tǒng)的單片應(yīng)用程序,它的業(yè)務(wù)由3個(gè)模塊組成,他們使用單個(gè)本地?cái)?shù)據(jù)源。自然,本地事務(wù)將保證數(shù)據(jù)的一致性。

        微服務(wù)架構(gòu)已發(fā)生了變化。上面提到的3個(gè)模塊被設(shè)計(jì)為3種服務(wù)。本地事務(wù)自然可以保證每個(gè)服務(wù)中的數(shù)據(jù)一致性。但是整個(gè)業(yè)務(wù)邏輯范圍如何?

        Seata怎么辦?

        我們說(shuō),分布式事務(wù)是由一批分支事務(wù)組成的全局事務(wù),通常分支事務(wù)只是本地事務(wù)。

        Seata有3個(gè)基本組成部分:

        • 事務(wù)協(xié)調(diào)器(TC):維護(hù)全局事務(wù)和分支事務(wù)的狀態(tài),驅(qū)動(dòng)全局提交或回滾。

        • 事務(wù)管理器TM:定義全局事務(wù)的范圍:開(kāi)始全局事務(wù),提交或回滾全局事務(wù)。

        • 資源管理器(RM):管理正在處理的分支事務(wù)的資源,與TC對(duì)話以注冊(cè)分支事務(wù)并報(bào)告分支事務(wù)的狀態(tài),并驅(qū)動(dòng)分支事務(wù)的提交或回滾。

        Seata管理的分布式事務(wù)的典型生命周期:

        1. TM要求TC開(kāi)始一項(xiàng)新的全局事務(wù)。TC生成代表全局事務(wù)的XID。

        2. XID通過(guò)微服務(wù)的調(diào)用鏈傳播。

        3. RM將本地事務(wù)注冊(cè)為XID到TC的相應(yīng)全局事務(wù)的分支。

        4. TM要求TC提交或回退相應(yīng)的XID全局事務(wù)。

        5. TC驅(qū)動(dòng)XID的相應(yīng)全局事務(wù)下的所有分支事務(wù)以完成分支提交或回滾。

        快速開(kāi)始

        用例

        用戶(hù)購(gòu)買(mǎi)商品的業(yè)務(wù)邏輯。整個(gè)業(yè)務(wù)邏輯由3個(gè)微服務(wù)提供支持:

        • 倉(cāng)儲(chǔ)服務(wù):對(duì)給定的商品扣除倉(cāng)儲(chǔ)數(shù)量。

        • 訂單服務(wù):根據(jù)采購(gòu)需求創(chuàng)建訂單。

        • 賬戶(hù)服務(wù):從用戶(hù)帳戶(hù)中扣除余額。

        環(huán)境準(zhǔn)備

        步驟 1:建立數(shù)據(jù)庫(kù)

        1. # db_seata

        2. DROP SCHEMA IF EXISTS db_seata;

        3. CREATE SCHEMA db_seata;

        4. USE db_seata;


        5. # Account

        6. CREATE TABLE `account_tbl` (

        7. `id` INT(11) NOT NULL AUTO_INCREMENT,

        8. `user_id` VARCHAR(255) DEFAULT NULL,

        9. `money` INT(11) DEFAULT 0,

        10. PRIMARY KEY (`id`)

        11. ) ENGINE = InnoDB DEFAULT CHARSET = utf8;


        12. INSERT INTO account_tbl (id, user_id, money)

        13. VALUES (1, '1001', 10000);

        14. INSERT INTO account_tbl (id, user_id, money)

        15. VALUES (2, '1002', 10000);


        16. # Order

        17. CREATE TABLE `order_tbl`

        18. (

        19. `id` INT(11) NOT NULL AUTO_INCREMENT,

        20. `user_id` VARCHAR(255) DEFAULT NULL,

        21. `commodity_code` VARCHAR(255) DEFAULT NULL,

        22. `count` INT(11) DEFAULT '0',

        23. `money` INT(11) DEFAULT '0',

        24. PRIMARY KEY (`id`)

        25. ) ENGINE = InnoDB DEFAULT CHARSET = utf8;


        26. # Storage

        27. CREATE TABLE `storage_tbl` (

        28. `id` INT(11) NOT NULL AUTO_INCREMENT,

        29. `commodity_code` VARCHAR(255) DEFAULT NULL,

        30. `count` INT(11) DEFAULT '0',

        31. PRIMARY KEY (`id`),

        32. UNIQUE KEY `commodity_code` (`commodity_code`)

        33. ) ENGINE = InnoDB DEFAULT CHARSET = utf8;



        34. INSERT INTO storage_tbl (id, commodity_code, count)

        35. VALUES (1, '2001', 1000);


        36. CREATE TABLE `undo_log` (

        37. `id` bigint(20) NOT NULL AUTO_INCREMENT,

        38. `branch_id` bigint(20) NOT NULL,

        39. `xid` varchar(100) NOT NULL,

        40. `context` varchar(128) NOT NULL,

        41. `rollback_info` longblob NOT NULL,

        42. `log_status` int(11) NOT NULL,

        43. `log_created` datetime NOT NULL,

        44. `log_modified` datetime NOT NULL,

        45. PRIMARY KEY (`id`),

        46. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)

        47. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

        seata AT 模式需要 undo_log 表,另外三張是業(yè)務(wù)表。

        步驟 2: 啟動(dòng) Seata Server

        Server端存儲(chǔ)模式(store.mode)現(xiàn)有file、db兩種(后續(xù)將引入raft),file模式無(wú)需改動(dòng),直接啟動(dòng)即可。db模式需要導(dǎo)入用于存儲(chǔ)全局事務(wù)回話信息的三張表。

        注:file模式為單機(jī)模式,全局事務(wù)會(huì)話信息內(nèi)存中讀寫(xiě)并持久化本地文件root.data,性能較高; db模式為高可用模式,全局事務(wù)會(huì)話信息通過(guò)db共享,相應(yīng)性能差些

        可以直接通過(guò)bash 腳本啟動(dòng) Seata Server,也可以通過(guò) Docker 鏡像啟動(dòng),但是 Docker 方式目前只支持使用 file 模式,不支持將 Seata-Server 注冊(cè)到 Eureka 或 Nacos 等注冊(cè)中心。

        通過(guò)腳本啟動(dòng)

        在 https://github.com/seata/seata/releases 下載相應(yīng)版本的 Seata Server,解壓后執(zhí)行以下命令啟動(dòng),這里使用 file 配置

        通過(guò) Docker 啟動(dòng)
        1. docker run --name seata-server -p 8091:8091 seataio/seata-server:latest

        項(xiàng)目介紹

        項(xiàng)目名地址說(shuō)明
        sbm-account-service127.0.0.1:8081賬戶(hù)服務(wù)
        sbm-order-service127.0.0.1:8082訂單服務(wù)
        sbm-storage-service127.0.0.1:8083倉(cāng)儲(chǔ)服務(wù)
        sbm-business-service127.0.0.1:8084主業(yè)務(wù)
        seata-server172.16.2.101:8091seata-server

        核心代碼

        為了不讓篇幅太長(zhǎng),這里只給出部分代碼,詳細(xì)代碼文末會(huì)給出源碼地址

        maven 引入 seata 的依賴(lài) eata-spring-boot-starter

        1. io.seata

        2. seata-spring-boot-starter

        3. 1.0.0

        倉(cāng)儲(chǔ)服務(wù)

        application.properties
        1. spring.application.name=account-service

        2. server.port=8081

        3. spring.datasource.url=jdbc:mysql://172.16.2.101:3306/db_seata?useSSL=false&serverTimezone=UTC

        4. spring.datasource.username=root

        5. spring.datasource.password=123456

        6. seata.tx-service-group=my_test_tx_group

        7. mybatis.mapper-locations=classpath*:mapper/*Mapper.xml

        8. seata.service.grouplist=172.16.2.101:8091

        9. logging.level.io.seata=info

        10. logging.level.io.seata.samples.account.persistence.AccountMapper=debug

        StorageService
        1. public interface StorageService {


        2. /**

        3. * 扣除存儲(chǔ)數(shù)量

        4. */

        5. void deduct(String commodityCode, int count);

        6. }

        訂單服務(wù)

        1. public interface OrderService {


        2. /**

        3. * 創(chuàng)建訂單

        4. */

        5. Order create(String userId, String commodityCode, int orderCount);

        6. }

        賬戶(hù)服務(wù)

        1. public interface AccountService {


        2. /**

        3. * 從用戶(hù)賬戶(hù)中借出

        4. */

        5. void debit(String userId, int money);

        6. }

        主要業(yè)務(wù)邏輯

        只需要使用一個(gè) @GlobalTransactional 注解在業(yè)務(wù)方法上。

        1. @GlobalTransactional

        2. public void purchase(String userId, String commodityCode, int orderCount) {

        3. LOGGER.info("purchase begin ... xid: " + RootContext.getXID());

        4. storageClient.deduct(commodityCode, orderCount);

        5. orderClient.create(userId, commodityCode, orderCount);

        6. }

        XID 的傳遞

        全局事務(wù)ID的跨服務(wù)傳遞,需要我們自己實(shí)現(xiàn),這里通過(guò)攔截器的方式。每個(gè)服務(wù)都需要添加下面兩個(gè)類(lèi)。

        SeataFilter
        1. @Component

        2. public class SeataFilter implements Filter {

        3. @Override

        4. public void init(FilterConfig filterConfig) throws ServletException {

        5. }


        6. @Override

        7. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        8. HttpServletRequest req = (HttpServletRequest) servletRequest;

        9. String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());

        10. boolean isBind = false;

        11. if (StringUtils.isNotBlank(xid)) {

        12. RootContext.bind(xid);

        13. isBind = true;

        14. }

        15. try {

        16. filterChain.doFilter(servletRequest, servletResponse);

        17. } finally {

        18. if (isBind) {

        19. RootContext.unbind();

        20. }

        21. }

        22. }


        23. @Override

        24. public void destroy() {

        25. }

        26. }

        SeataRestTemplateAutoConfiguration
        1. @Configuration

        2. public class SeataRestTemplateAutoConfiguration {

        3. @Autowired(

        4. required = false

        5. )

        6. private Collection<RestTemplate> restTemplates;

        7. @Autowired

        8. private SeataRestTemplateInterceptor seataRestTemplateInterceptor;


        9. public SeataRestTemplateAutoConfiguration() {

        10. }


        11. @Bean

        12. public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {

        13. return new SeataRestTemplateInterceptor();

        14. }


        15. @PostConstruct

        16. public void init() {

        17. if (this.restTemplates != null) {

        18. Iterator var1 = this.restTemplates.iterator();


        19. while (var1.hasNext()) {

        20. RestTemplate restTemplate = (RestTemplate) var1.next();

        21. List<ClientHttpRequestInterceptor> interceptors = new ArrayList(restTemplate.getInterceptors());

        22. interceptors.add(this.seataRestTemplateInterceptor);

        23. restTemplate.setInterceptors(interceptors);

        24. }

        25. }


        26. }

        27. }

        測(cè)試

        測(cè)試成功場(chǎng)景:

        1. curl -X POST http://127.0.0.1:8084/api/business/purchase/commit

        此時(shí)返回結(jié)果為:true

        測(cè)試失敗場(chǎng)景:

        UserId 為1002 的用戶(hù)下單,sbm-account-service會(huì)拋出異常,事務(wù)會(huì)回滾

        1. http://127.0.0.1:8084/api/business/purchase/rollback

        此時(shí)返回結(jié)果為:false

        查看 undo_log 的日志或者主鍵,可以看到在執(zhí)行過(guò)程中有保存數(shù)據(jù)。如查看主鍵自增的值,在執(zhí)行前后的值會(huì)發(fā)生變化,在執(zhí)行前是 1,執(zhí)行后是 7 。

        源碼地址

        https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-seata

        參考

        http://seata.io/zh-cn/docs/overview/what-is-seata.html


        推薦閱讀:


        喜歡我可以給我設(shè)為星標(biāo)哦

        好文章,我“在看”


        瀏覽 43
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 日韩做爱网站 | A片视频在线观看 | 伊伊成人网 | 小婷的第一次又紧又湿视频 | 欧美日韩精品一区二区无码视频 |