<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6723字,需瀏覽 14分鐘

           ·

          2020-12-30 13:33

          seata 簡介

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

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

          讓我們想象一下傳統(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怎么辦?

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

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

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

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

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

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

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

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

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

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

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

          快速開始

          用例

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

          • 倉儲服務(wù):對給定的商品扣除倉儲數(shù)量。

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

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

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

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

          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: 啟動 Seata Server

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

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

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

          通過腳本啟動

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

          通過 Docker 啟動
          1. docker run --name seata-server -p 8091:8091 seataio/seata-server:latest

          項(xiàng)目介紹

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

          核心代碼

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

          maven 引入 seata 的依賴 eata-spring-boot-starter

          1. io.seata

          2. seata-spring-boot-starter

          3. 1.0.0

          倉儲服務(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. * 扣除存儲數(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. }

          賬戶服務(wù)

          1. public interface AccountService {


          2. /**

          3. * 從用戶賬戶中借出

          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),這里通過攔截器的方式。每個(gè)服務(wù)都需要添加下面兩個(gè)類。

          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. }

          測試

          測試成功場景:

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

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

          測試失敗場景:

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

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

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

          查看 undo_log 的日志或者主鍵,可以看到在執(zhí)行過程中有保存數(shù)據(jù)。如查看主鍵自增的值,在執(zhí)行前后的值會發(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)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一級免費网站 | 亚洲秘 无码一区二区三区电影 | 国产精品MV视频 | 熟女视频一区 | 毛片网站在线 |