1. SpringCloud Alibaba Seata處理分布式事務(wù)

        共 22165字,需瀏覽 45分鐘

         ·

        2021-04-10 10:49

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

        優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          作者 |  C紫楓 

        來(lái)源 |  urlify.cn/BjYv6f

        分布式事務(wù)問(wèn)題

        一次業(yè)務(wù)操作需要垮多個(gè)數(shù)據(jù)源或需要垮多個(gè)系統(tǒng)進(jìn)行遠(yuǎn)程調(diào)用,就會(huì)產(chǎn)生分布式事務(wù)問(wèn)題。

        Seata簡(jiǎn)介

        是什么?

        Seata是一款開源的分布式事務(wù)解決方案,致力于在微服務(wù)架構(gòu)下提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。
        官網(wǎng)地址:http://seata.io/zh-cn/

        一個(gè)典型的分布式事務(wù)過(guò)程

        分布式事務(wù)處理過(guò)程:XID+三組件模型

        XID

        Transaction ID(XID):全局唯一的事務(wù)id

        三組件概念

        Transaction Coordinator(TC):事務(wù)協(xié)調(diào)器,維護(hù)全局事務(wù)的運(yùn)行狀態(tài),負(fù)責(zé)協(xié)調(diào)并驅(qū)動(dòng)全局事務(wù)的提交或回滾。
        Transaction Manager(TM):控制全局事務(wù)的邊界,負(fù)責(zé)開啟一個(gè)全局事務(wù),并最終發(fā)起全局提交或全局回滾的決議。
        Resource Manager(RM):控制分支事務(wù),負(fù)責(zé)分支注冊(cè)、狀態(tài)匯報(bào),并接受事務(wù)協(xié)調(diào)的指令,驅(qū)動(dòng)分支(本地)事務(wù)的提交和回滾。

        下載地址

        發(fā)布說(shuō)明: https://github.com/seata/seata/releases

        怎么使用

        本地@Transational
        全局@GlobalTranstional
        seata的分布式交易解決方案:

        配置說(shuō)明

        seata-server-0.9.0.zip解壓到指定目錄并修改conf目錄下的file.conf配置文件,主要修改的內(nèi)容:自定義事務(wù)組名稱+事務(wù)日志存儲(chǔ)模式為db+數(shù)據(jù)庫(kù)連接

        mysql5.7數(shù)據(jù)庫(kù)新建庫(kù)seata

        建表db_store.sql在seata-server-0.9.0\seata\conf目錄里面db_store.sql(注意seata1.0開始就沒(méi)這個(gè)數(shù)據(jù)庫(kù)文件了)

        -- the table to store GlobalSession data
        drop table if exists `global_table`;
        create table `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`)
        );
         
        -- the table to store BranchSession data
        drop table if exists `branch_table`;
        create table `branch_table` (
          `branch_id` bigint not null,
          `xid` varchar(128) not null,
          `transaction_id` bigint ,
          `resource_group_id` varchar(32),
          `resource_id` varchar(256) ,
          `lock_key` varchar(128) ,
          `branch_type` varchar(8) ,
          `status` tinyint,
          `client_id` varchar(64),
          `application_data` varchar(2000),
          `gmt_create` datetime,
          `gmt_modified` datetime,
          primary key (`branch_id`),
          key `idx_xid` (`xid`)
        );
         
        -- the table to store lock data
        drop table if exists `lock_table`;
        create table `lock_table` (
          `row_key` varchar(128) not null,
          `xid` varchar(96),
          `transaction_id` long ,
          `branch_id` long,
          `resource_id` varchar(256) ,
          `table_name` varchar(32) ,
          `pk` varchar(36) ,
          `gmt_create` datetime ,
          `gmt_modified` datetime,
          primary key(`row_key`)
        );

        修改seata-server-0.9.0\seata\conf目錄下的registry.conf目錄下的registry.conf配置文件

        先啟動(dòng)Nacos端口號(hào)8848

        再啟動(dòng)seata-server

        seata-server-0.9.0\seata\bin --->seata-server.bat
        遇到的問(wèn)題:?jiǎn)?dòng)閃退報(bào)jvm內(nèi)存不夠(可能是我用的是openJdk)
        解決方案:https://blog.csdn.net/weixin_39724194/article/details/107329330

        功能模擬

        訂單/庫(kù)存/賬戶業(yè)務(wù)數(shù)據(jù)庫(kù)準(zhǔn)備

        創(chuàng)建業(yè)務(wù)數(shù)據(jù)庫(kù)

        seata_order:存儲(chǔ)訂單的數(shù)據(jù)庫(kù)
        seata_storage:存儲(chǔ)庫(kù)存的數(shù)據(jù)庫(kù)
        seata_account:存儲(chǔ)賬戶信息的數(shù)據(jù)庫(kù)

        按照上述3庫(kù)分別建立對(duì)應(yīng)業(yè)務(wù)表

        seata_order庫(kù)下新建t_order表

        DROP TABLE IF EXISTS `t_order`;
        CREATE TABLE `t_order`  (
          `id` bigint(11) NOT NULL AUTO_INCREMENT,
          `user_id` bigint(20) DEFAULT NULL COMMENT '用戶id',
          `product_id` bigint(11) DEFAULT NULL COMMENT '產(chǎn)品id',
          `count` int(11) DEFAULT NULL COMMENT '數(shù)量',
          `money` decimal(11, 0) DEFAULT NULL COMMENT '金額',
          `status` int(1) DEFAULT NULL COMMENT '訂單狀態(tài):  0:創(chuàng)建中 1:已完結(jié)',
          PRIMARY KEY (`int`) USING BTREE
        ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '訂單表' ROW_FORMAT = Dynamic;

        seata_storage庫(kù)下新建t_storage表

        DROP TABLE IF EXISTS `t_storage`;
        CREATE TABLE `t_storage`  (
          `id` bigint(11) NOT NULL AUTO_INCREMENT,
          `product_id` bigint(11) DEFAULT NULL COMMENT '產(chǎn)品id',
          `total` int(11) DEFAULT NULL COMMENT '總庫(kù)存',
          `used` int(11) DEFAULT NULL COMMENT '已用庫(kù)存',
          `residue` int(11) DEFAULT NULL COMMENT '剩余庫(kù)存',
          PRIMARY KEY (`int`) USING BTREE
        ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '庫(kù)存' ROW_FORMAT = Dynamic;
        INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);

        seata_account庫(kù)下新建t_account表

        CREATE TABLE `t_account`  (
          `id` bigint(11) NOT NULL COMMENT 'id',
          `user_id` bigint(11) DEFAULT NULL COMMENT '用戶id',
          `total` decimal(10, 0) DEFAULT NULL COMMENT '總額度',
          `used` decimal(10, 0) DEFAULT NULL COMMENT '已用余額',
          `residue` decimal(10, 0) DEFAULT NULL COMMENT '剩余可用額度',
          PRIMARY KEY (`id`) USING BTREE
        ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '賬戶表' ROW_FORMAT = Dynamic;
         
        INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);

        按照上述3庫(kù)分別建立對(duì)應(yīng)的回滾日志表

        訂單-庫(kù)存-賬戶3個(gè)庫(kù)下都需要建各自獨(dú)立的回滾日志表,seata-server-0.9.0\seata\conf\目錄下的db_undo_log.sql,建表SQL:

        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,
          `ext` varchar(100) DEFAULT NULL,
          PRIMARY KEY (`id`),
          UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

        最終效果

        訂單/庫(kù)存/賬戶業(yè)務(wù)微服務(wù)準(zhǔn)備

        業(yè)務(wù)需求:下訂單->減庫(kù)存->扣余額->改(訂單)狀態(tài)

        新建訂單Order-Module

        1.seata-order-service2001
        2.POM

          <!--seata-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                    <exclusions>
                        <exclusion>
                            <artifactId>seata-all</artifactId>
                            <groupId>io.seata</groupId>
                        </exclusion>
                    </exclusions>
                </dependency>
                <dependency>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                    <version>1.0.0</version>
                </dependency>

        3.YML

        server:
          port: 2001

        spring:
          application:
            name: seata-order-service
          cloud:
            alibaba:
              seata:
                #自定義事務(wù)組名稱需要與seata-server中的對(duì)應(yīng),在file.conf中的service中
                tx-service-group: fsp_tx_group
            nacos:
              discovery:
                server-addr: localhost:8848
          datasource:
            driver-class-name: com.mysql.jdbc.Driver
            url: jdbc:mysql://localhost:3306/seata_order
            username: root
            password: root

        feign:
          hystrix:
            enabled: false

        logging:
          level:
            io:
              seata: info

        mybatis:
          mapperLocations: classpath:mapper/*.xml

        4.file.conf
        拷貝seata-server/conf目錄下的file.conf
        5.registry.conf
        拷貝seata-server/conf目錄下的registry.conf
        6.主啟動(dòng)類

        package com.czf.springcloud;

        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
        import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
        import org.springframework.cloud.openfeign.EnableFeignClients;

        @EnableDiscoveryClient
        @EnableFeignClients
        @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消數(shù)據(jù)源的自動(dòng)創(chuàng)建
        public class SeataOrderMainApp2001
        {

            public static void main(String[] args)
            {
                SpringApplication.run(SeataOrderMainApp2001.class, args);
            }
        }

        7.Config配置

        package com.czf.springcloud.config;

        import com.alibaba.druid.pool.DruidDataSource;
        import io.seata.rm.datasource.DataSourceProxy;
        import org.apache.ibatis.session.SqlSessionFactory;
        import org.mybatis.spring.SqlSessionFactoryBean;
        import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

        import javax.sql.DataSource;

        /**
         * @auther zzyy
         * @create 2019-12-11 16:58
         * 使用Seata對(duì)數(shù)據(jù)源進(jìn)行代理
         */
        @Configuration
        public class DataSourceProxyConfig {

            @Value("${mybatis.mapperLocations}")
            private String mapperLocations;

            @Bean
            @ConfigurationProperties(prefix = "spring.datasource")
            public DataSource druidDataSource(){
                return new DruidDataSource();
            }

            @Bean
            public DataSourceProxy dataSourceProxy(DataSource dataSource) {
                return new DataSourceProxy(dataSource);
            }

            @Bean
            public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
                SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
                sqlSessionFactoryBean.setDataSource(dataSourceProxy);//注入數(shù)據(jù)源
                sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));//相當(dāng)于mapperScaner掃描器
                sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());//配置事務(wù)
                return sqlSessionFactoryBean.getObject();
            }

        }

        package com.czf.springcloud.config;

        import org.mybatis.spring.annotation.MapperScan;
        import org.springframework.context.annotation.Configuration;

        /**
         * @auther zzyy
         * @create 2019-12-11 16:57
         */
        @Configuration
        @MapperScan({"com.czf.springcloud.dao"})
        public class MyBatisConfig {
        }

        8.Service接口及實(shí)現(xiàn)
        OrderService

        package com.czf.springcloud.service;
        import com.czf.springcloud.domain.Order;

        /**
         * @auther zzyy
         * @create 2020-02-26 15:19
         */
        public interface OrderService
        {
            void create(Order order);
        }

        OrderServiceIplm

        package com.czf.springcloud.service.impl;
        import com.czf.springcloud.dao.OrderDao;
        import com.czf.springcloud.domain.Order;
        import com.czf.springcloud.service.AccountService;
        import com.czf.springcloud.service.OrderService;
        import com.czf.springcloud.service.StorageService;
        import io.seata.spring.annotation.GlobalTransactional;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.stereotype.Service;
        import javax.annotation.Resource;

        /**
         */
        @Service
        @Slf4j
        public class OrderServiceImpl implements OrderService
        {
            @Resource
            private OrderDao orderDao;
            @Resource
            private StorageService storageService;
            @Resource
            private AccountService accountService;

            /**
             * 創(chuàng)建訂單->調(diào)用庫(kù)存服務(wù)扣減庫(kù)存->調(diào)用賬戶服務(wù)扣減賬戶余額->修改訂單狀態(tài)
             * 簡(jiǎn)單說(shuō):下訂單->扣庫(kù)存->減余額->改狀態(tài)
             */
            @Override
            @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
            public void create(Order order)
            {
                log.info("----->開始新建訂單");
                //1 新建訂單
                orderDao.create(order);

                //2 扣減庫(kù)存
                log.info("----->訂單微服務(wù)開始調(diào)用庫(kù)存,做扣減Count");
                storageService.decrease(order.getProductId(),order.getCount());
                log.info("----->訂單微服務(wù)開始調(diào)用庫(kù)存,做扣減end");

                //3 扣減賬戶
                log.info("----->訂單微服務(wù)開始調(diào)用賬戶,做扣減Money");
                accountService.decrease(order.getUserId(),order.getMoney());
                log.info("----->訂單微服務(wù)開始調(diào)用賬戶,做扣減end");

                //4 修改訂單狀態(tài),從零到1,1代表已經(jīng)完成
                log.info("----->修改訂單狀態(tài)開始");
                orderDao.update(order.getUserId(),0);
                log.info("----->修改訂單狀態(tài)結(jié)束");

                log.info("----->下訂單結(jié)束了,O(∩_∩)O哈哈~");

            }
        }

        AccountService

        @FeignClient(value = "seata-account-service")//調(diào)用2003的微服務(wù)-----服務(wù)調(diào)用
        public interface AccountService
        {
            @PostMapping(value = "/account/decrease")
            CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
        }

        StorageService

        @FeignClient(value = "seata-storage-service")//調(diào)用2002的微服務(wù)
        public interface StorageService
        {
            @PostMapping(value = "/storage/decrease")
            CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
        }

        9.Controller

        @RestController
        public class OrderController
        {
            @Resource
            private OrderService orderService;


            @GetMapping("/order/create")
            public CommonResult create(Order order)
            {
                orderService.create(order);
                return new CommonResult(200,"訂單創(chuàng)建成功");
            }
        }

        新建庫(kù)存Storage-Module(seata-storage-service2002)同理

        controller

        @RestController
        public class AccountController {

            @Resource
            AccountService accountService;

            /**
             * 扣減賬戶余額
             */
            @RequestMapping("/account/decrease")
            public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
                accountService.decrease(userId,money);
                return new CommonResult(200,"扣減賬戶余額成功!");
            }
        }

        service

        public interface AccountService {

            /**
             * 扣減賬戶余額
             * @param userId 用戶id
             * @param money 金額
             */
            void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);

        /**
         * 賬戶業(yè)務(wù)實(shí)現(xiàn)類
         * Created by zzyy on 2019/11/11.
         */
        @Service
        public class AccountServiceImpl implements AccountService {

            private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);


            @Resource
            AccountDao accountDao;

            /**
             * 扣減賬戶余額
             */
            @Override
            public void decrease(Long userId, BigDecimal money) {
                LOGGER.info("------->account-service中扣減賬戶余額開始");
                //模擬超時(shí)異常,全局事務(wù)回滾
                //暫停幾秒鐘線程
                try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
                accountDao.decrease(userId,money);
                LOGGER.info("------->account-service中扣減賬戶余額結(jié)束");
            }
        }

        新建賬戶Account-Module(seata-account-service2003)

        controller

        @RestController
        public class StorageController {

            @Autowired
            private StorageService storageService;

            /**
             * 扣減庫(kù)存
             */
            @RequestMapping("/storage/decrease")
            public CommonResult decrease(Long productId, Integer count) {
                storageService.decrease(productId, count);
                return new CommonResult(200,"扣減庫(kù)存成功!");
            }
        }

        service

        public interface StorageService {
            /**
             * 扣減庫(kù)存
             */
            void decrease(Long productId, Integer count);
        }
        @Service
        public class StorageServiceImpl implements StorageService {

            private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);

            @Resource
            private StorageDao storageDao;

            /**
             * 扣減庫(kù)存
             */
            @Override
            public void decrease(Long productId, Integer count) {
                LOGGER.info("------->storage-service中扣減庫(kù)存開始");
                storageDao.decrease(productId,count);
                LOGGER.info("------->storage-service中扣減庫(kù)存結(jié)束");
            }
        }

        測(cè)試

        測(cè)試說(shuō)明:測(cè)試回滾,再AcountService中添加線程睡眠,模擬feign的超時(shí)異常,看數(shù)據(jù)是否回滾。






        鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

        ??????

        ??長(zhǎng)按上方微信二維碼 2 秒





        感謝點(diǎn)贊支持下哈 

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 四虎永久免费 | 一级片成人| 91免费版黄色软件 | 91aaaaaa | 欧美三级片在线视频 |