分布式事务Seata使用

分布式事务Seata使用

码农世界 2024-05-17 后端 73 次浏览 0个评论

我们要学习seata,首先需要具备如下技术储备:

  • 数据库事务的基本知识;
  • maven工具的使用;
  • 熟悉SpringCloudAlibaba技术栈;
  • 掌握SpringDataJPA简单使用;

    一. Seata基本概念

    1.seata是什么

    Seata是阿里巴巴中间件团队发起了开源项目,其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题,后来更名为 Seata。

    Seata的设计目标是对业务无侵入,因此从业务无侵入的2PC方案着手,在传统2PC的基础上演进。它把一个分布式事务理解成一个包含了若干分支事务的全局事务。

    全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据库的本地事务。

    2.seata基本架构

    听到这里,是不是觉得很晦涩?那么威哥通过一幅图来帮助你们进一步理解seata的架构:

    分布式事务Seata使用

    通过这幅图,我们看到了seata的三个重要的组件,分别是TC TM RM。那么他们到底是什么东西呢?

    • TC:Transaction Coordinator事务协调器,管理全局的分支事务的状态,用于全局性事务的提交和回滚。
    • TM:Transaction Manager 事务管理器,用于开启、提交或者回滚全局事务。
    • RM:Resource Manager资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。

      3.seata执行流程

      搞清楚了这几个组件的含义之后,那么seata的整个执行流程我们就可以梳理清楚了:

      • A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID。
      • 服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖。
      • A服务执行分支事务,向数据库做操作。
      • A服务开始远程调用B服务,此时XID会在微服务的调用链上传播。
      • B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖。
      • B服务执行分支事务,向数据库做操作。
      • 全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚。
      • TC协调其管辖之下的所有分支事务,决定是否回滚。

        二. 案例环境搭建

        我们搞清楚Seata的相关概念之后,现在威哥带领大家实现一个需求:通过订单微服务实现下订单的操作,然后通知库存微服务进行库存的扣减。

        1. 前期准备

        我们需要先准备订单和商品实体类。

        //商品
        @Entity(name = "shop_product")
        @Data
        public class Product {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Integer pid;//主键
            private String pname;//商品名称
            private Double pprice;//商品价格
            private Integer stock;//库存
        }
        //订单
        @Entity(name = "shop_order")
        @Data
        public class Order {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long oid;//订单id
            private Integer uid;//用户id
            private String username;//用户名
            private Integer pid;//商品id
            private String pname;//商品名称
            private Double pprice;//商品单价
            private Integer number;//购买数量
        }

        我们还需要准备项目必备的pom依赖:

        这是父工程的pom.xml文件:

        
            
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    ${spring-cloud.version}
                    pom
                    import
                
                
                    com.alibaba.cloud
                    spring-cloud-alibaba-dependencies
                    ${spring-cloud-alibaba.version}
                    pom
                    import
                
            
        

        2. 搭建对应的微服务

        现在我们分别搭建商品微服务和订单微服务

        2.1 创建公共通用模块

        我们创建shop-common模块,专门存放一些公共的实体类和工具类,便于其他模块进行共享。

        2.1.1 在公共模块添加相关的依赖

        
            
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    ${spring-cloud.version}
                    pom
                    import
                
                
                    com.alibaba.cloud
                    spring-cloud-alibaba-dependencies
                    ${spring-cloud-alibaba.version}
                    pom
                    import
                
            
        

        然后把之前准备的实体类都拷贝到这个shop-common中来。

        2.2 搭建订单微服务模块

        2.2.1 添加必要依赖

        取名shop-order,在这个模块里面添加相关的依赖。

        
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
         
            
                org.springframework.cloud
                spring-cloud-starter-openfeign
            
         
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                com.qf.common
                springcloudAlibaba-common
                1.0-SNAPSHOT
            
            
                com.qf.feign
                springcloudAlibaba-order-product-feign
                1.0-SNAPSHOT
            
         
         
            
                org.springframework.boot
                spring-boot-starter-test
            
            
                junit
                junit
            
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-config
            
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-seata
            
        

        2.2.2 编写controller

        在这里,我们编写一个下单操作的服务接口。

        @RestController
        @RequestMapping("order")
        @Slf4j
        public class OrderController {
            @Autowired
            OrderService5 orderService5;
            @RequestMapping("prod/{pid}")
            public Order order(@PathVariable("pid") Integer pid){
                return orderService5.createOrder(pid);
            }
        }

        2.2.3 编写service

        public interface OrderService {
            Order createOrder(Integer pid);
        }
        @Service
        @Slf4j
        public class OrderServiceImpl implements OrderService {
            @Autowired
            OrderFeign orderFeign;
            @Autowired
            OrderDao orderDao;
            @Override
            public Order createOrder(Integer pid) {
                //查询指定的商品信息
                Product product = orderFeign.findProductByPid(pid);
                log.info("查询到的商品信息是:{}", JSON.toJSONString(product));
                //执行下单的操作
                Order order = new Order();
                order.setUid(1003);
                order.setUsername("测试Seata案例");
                order.setPid(pid);
                order.setPname(product.getPname());
                order.setPprice(product.getPprice());
                //设置订单中的商品数量
                order.setNumber(1);
                orderDao.save(order);
                log.info("订单创建成功,订单信息是:{}",JSON.toJSONString(order));
                //执行扣减库存的操作
                orderFeign.reduceStock(pid,order.getNumber());
                return order;
            }
        }

        2.2.4 编写feign客户端

        @FeignClient(name = "service-product")
        public interface OrderFeign {
            @RequestMapping("product/{pid}")
            public Product findProductByPid(@PathVariable("pid") Integer pid);
            @RequestMapping("product/reduceStock")
            void reduceStock(@RequestParam("pid") Integer pid,@RequestParam("number") Integer number);
        }

        2.2.5 编写dao

        public interface OrderDao extends JpaRepository {
        }

        2.3 搭建商品微服务模块

        2.3.1 添加必要依赖

        取名shop-product,在这个模块里面添加相关的依赖。

        
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                com.qf.common
                springcloudAlibaba-common
                1.0-SNAPSHOT
            
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-config
            
         
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-seata
            
        

        2.3.2 编写controller

        @RestController
        @Slf4j
        @RefreshScope //配置信息的即时刷新
        public class ProductController {
            @Autowired
            ProductService productService;
            //根据id查询对应的商品信息
            @RequestMapping("product/{pid}")
            public Product findProductByPid(@PathVariable("pid") Integer pid){
                Product product = productService.findProductByPid(pid);
                //JSON.toJSONString  把指定数据转换成json串
                log.info("查询到的对应的商品是:" + JSON.toJSONString(product));
                return product;
            }
         
              //扣减库存
            @RequestMapping("product/reduceStock")
            public void reduceStock(@RequestParam("pid") Integer pid,@RequestParam("number") Integer number){
                productService.reduceStock(pid,number);
            }
        }

        2.3.3 编写service

        public interface ProductService {
            Product findProductByPid(Integer pid);
            void reduceStock(Integer pid, Integer number);
        }
        @Service
        public class ProductServiceImpl implements ProductService {
            @Autowired
            ProductDao productDao;
            @Override
            public Product findProductByPid(Integer pid) {
                Optional optional = productDao.findById(pid);
                return optional.get();
            }
            @Override
            public void reduceStock(Integer pid, Integer number) {
                Product product = productDao.findById(pid).get();
                product.setStock(product.getStock() -  number);
                productDao.save(product);
            }
        }

        2.3.4 编写dao

        public interface ProductDao extends JpaRepository {
        }

        现在我们启动测试,目前代码是没有什么问题的。但是如果我手动模拟异常。具体操作如下:

        分布式事务Seata使用

        此时我们再去测试,这个时候就出现了问题了。我们发现订单能够下单成功,但是库存没有扣减。这样就出现了数据不一致的事务问题。那么我们可以使用seata来帮我们解决问题。

        三. 配置使用seata

        1. 下载seata

        同学们可以在如下资源链接上进行下载:

        下载地址:https://github.com/seata/seata/releases/v0.9.0/

        2. 配置Seata

        我们下载下来之后,会是一个压缩包。我们把这个压缩包打开之后进行相关配置。

        2.1 修改registry.conf,指定seata使用nacos注册中心

        registry {
          # 支持的注册中心有:file 、nacos 、eureka、redis、zk、consul、etcd3、sofa  我们使用自己的注册中心即可  所以删除其他注册中心相关的配置
          type = "nacos"
          nacos {
            serverAddr = "localhost"
            namespace = ""
            cluster = "default"
          }
         
        }
        config {
          # file、nacos 、apollo、zk、consul、etcd3
          type = "nacos"
          nacos {
            serverAddr = "localhost"
            namespace = ""
          }
        }

        2.2 修改nacos-config.txt,指定我们的服务名称

        分布式事务Seata使用

        2.3 初始化seata在nacos中的配置

        我们需要把seata相关的配置信息在nacos配置中心进行注册。

        # 初始化seata 的nacos配置 # 注意: 这里要保证nacos是已经正常运行的 
        cd conf 
        nacos-config.sh 127.0.0.1

        执行成功后可以打开Naco的控制台,在配置列表中,可以看到初始化了很多Group为SEATA_GROUP的配置。

        分布式事务Seata使用

        2.4 启动seata服务

        切换到bin目录执行以下命令:

        cd bin
        seata-server.bat -p 9000 -m file

        启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr的服务。如果入下图所示,小伙伴们,seata服务启动成功!!!!!

        分布式事务Seata使用

        3. 使用seata进行事务控制

        3.1 初始化一张数据表,用来seata进行日志记录

        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;

        3.2 在微服务中添加seata相关的依赖

        
             com.alibaba.cloud
             spring-cloud-starter-alibaba-seata
         
         
         
             com.alibaba.cloud
             spring-cloud-starter-alibaba-nacos-config
         

        3.3 配置DataSourceProxyConfig代理数据源

        Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的 Bean,且是 @Primary默认的数据源,否则事务不会回滚,无法实现分布式事务.

        在shop-product和shop-order里面都添加如下配置类:

        @Configuration
         public class DataSourceProxyConfig {
             @Bean
             @ConfigurationProperties(prefix = "spring.datasource")
             public DruidDataSource druidDataSource() {
                 return new DruidDataSource();
             }
         
             @Primary
             @Bean
             public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
                 return new DataSourceProxy(druidDataSource);
             }
         }
        • 在shop-product和shop-order的resources目录添加registry.conf(直接将seata里面的配置复制过来即可)。
        • 在shop-product和shop-order的resources目录添加bootstrap.yml里面,然后添加配置。

          分布式事务Seata使用

          3.4 在shop-order微服务中开启全局事务

          分布式事务Seata使用

          4. 结果测试

          我们发生请求:http://localhost:8091/order5/prod/1

          此时查看数据库,我们发现事务问题得到了控制。就是当发生异常的时候,下单的记录被回滚了,而且库存也没有出现扣减。

          分布式事务Seata使用

          分布式事务Seata使用

          到现在,我们的分布式事务就得到了控制。小伙伴们,你们有没有学会呢?

转载请注明来自码农世界,本文标题:《分布式事务Seata使用》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,73人围观)参与讨论

还没有评论,来说两句吧...

Top