zoukankan      html  css  js  c++  java
  • 11--SpringCloud Alibaba:Seata周阳老师

    2021:11--SpringCloud Alibaba:Seata

    https://www.cnblogs.com/coderD/p/14350076.html SpringCloud

    https://www.cnblogs.com/coderD/p/14350073.html SpringCloud 和 Eureka

    https://www.cnblogs.com/coderD/p/14350082.html SpringCloud 和 Zookeeper

    https://www.cnblogs.com/coderD/p/14350086.html SpringCloud-Ribbon/OpenFeign

    https://www.cnblogs.com/coderD/p/14350091.html SpringCloud:Hystrix 断路器

    https://www.cnblogs.com/coderD/p/14350097.html SpringCloud:服务网关 gateway

    https://www.cnblogs.com/coderD/p/14350099.html SpringCloud:Config/Bus

    https://www.cnblogs.com/coderD/p/14350103.html SpringCloud:Stream/Sleuth

    https://www.cnblogs.com/coderD/p/14350110.html SpringCloud Alibaba:Nacos

    https://www.cnblogs.com/coderD/p/14350114.html SpringCloud Alibaba:Sentinel

    https://www.cnblogs.com/coderD/p/14350119.html SpringCloud Alibaba:Seata

    代码:https://gitee.com/xue--dong/spring-cloud

    阳哥脑图:https://gitee.com/xue--dong/spring-cloud

    Seata 处理分布式事务

    你做的项目用的技术架构是什么?基于分布式的微服务架构?
    
    你们的分布式事务怎么控制的?
    
    只要说的分布式,你的数据库不可能有且仅有一个。
    
    多数据库,多数据源的情况下我们如何处理和面对。
    
        1.  分布式事务问题
        2.  Seata简介
        3.  Seata-Server安装
        4.  订单/库存/账户业务数据库准备
        5.  订单/库存/账户业务微服务准备
        6.  Test
    复制代码
    

    1 分布式事务问题

        分布式之前:单机单库没有这个问题。
        
            下单成功:新增一条订单记录
            库存微服务:扣掉一个库存
            支付微服务:扣减支付金额
    复制代码
    

    img

            物理上这是三个库,但是逻辑上是一个整体,都是我们这个系统中的一部分。
            
            牵扯到这样全局的,跨数据库的,多数据源的统一调度这就是我们分布式事务的前身和雏形,
            也是我们目前面临的痛点。
        
        分布式之后:用户购买商品的业务逻辑
    复制代码
    

    img

            肯定会涉及到多个数据库的:跨库,调度...
            
            原来的单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源。
            
            业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致
            性问题没法解决。
            
            什么意思:这三个单个的成功或者失败得到了保证。但是这不是我们想要的,要保证三个一起成功,一起失败。
            
            一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
            
        我们用Seata来保障全局数据的一致性问题。
    复制代码
    

    2 Seata 简介

        Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能的和简单易用的分布式事务服务。
    复制代码
    

    官网地址

    img

    2.1 Seata 术语

        1.  分布式事务处理过程的一个ID+三个组件模型
        
            全局唯一的事务ID:Transaction ID XID
                
                在这个事务ID下的所有事务会被统一控制。
            
            三组件:
                
                Transaction Coordinator(TC):
                    事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
                
                Transaction Manager(TM):
                    事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的协议。
                
                Resource Manager(RM):
                    资源管理器,控制分支事务,负责分支注册,妆台汇报,并接收事务协调器的指令,驱动分支(本地)
                    事物的提交和回滚。
    复制代码
    

    2.2 Seata 一个典型的分布式控制事务的流程:

    img

    img

    2.3 Seata-Server 下载安装:0.9 版本

        1. 解压到指定目录,修改conf目录下的file.conf配置文件,先备份
        
        2.  主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
        
        3.  file.conf
                service模块
                store模块
            
            数据库建一个seata库     
    复制代码
    

    img

        4.  在seata库建表:脚本在seata的conf目录下
    复制代码
    

    img

            三张表:分支,全局,锁
            
        5.  修改registry.conf文件
            
            注明注册中心是nacos,并修改nacos的连接信息
    复制代码
    

    img

        7.  先启动nacos
    复制代码
    

    img

            在启动seata-server
            
            双击seata-server.bat
            
            出现这些提示信息说明:seata启动成功
    复制代码
    

    img

    2.4 怎么用

        本地:  @Transactional
        全局:   @GlobalTransactional
    复制代码
    

    3 SEATA 分布式:交易案例

    img

    3.1 Seata 业务数据库准备

        前面我们安装启动了seata:
        
            我们可以理解seata就是一个分布式事物的服务器。
            
            尽量同主业务剥离开。
        
        
        以下的演示都需要先启动nacos后启动Seata,保证两个都OK。否则Seata会报错:
            no available server to connect
            
    1.  操作说明   
        这里我们会创建三个服务,一个订单,一个库存服务,一个账户服务:
            
            当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品库存,
            再通过远程调用账户服务来扣减用户账户里面的月,
            最后在订单服务中修改订单状态为已完成。
        
        整个操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
    
    2.  创建数据库
    
        1.  订单数据库准备:seata_order
        
        2.  库存数据准备:seata_storage
        
        3.  账户业务数据库准备:seata_account
        
    3.  按照上述三个库分别建立对应的业务表
    
        seata_order:t_order表
    复制代码
    CREATE TABLE t_order(
        `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
        `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
        `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
        `count` INT(11) DEFAULT NULL COMMENT '数量',
        `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
        `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
    ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
    SELECT * FROM t_order;
    复制代码
        seata_storage:t_storage表
    复制代码
    CREATE TABLE t_storage(
        `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
        `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
       `total` INT(11) DEFAULT NULL COMMENT '总库存',
        `used` INT(11) DEFAULT NULL COMMENT '已用库存',
        `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
    VALUES('1','1','100','0','100');
    SELECT * FROM t_storage;
    复制代码
        seata_account:t_account表
    复制代码
    CREATE TABLE t_account(
        `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY 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 '0' COMMENT '剩余可用额度'
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000')
    SELECT * FROM t_account;
    复制代码
    4.  按照上述3个库分别建对呀那的回滚日志表
    
        三个库下都需要建各自的回滚日志表
        
        conf目录下:db_undo_log.sql
    
    5.  最后得到数据库环境:
    复制代码
    

    img

    4 订单 / 库存 / 账户业务微服务准备

    业务需求:
        
        下订单--减库存--扣余额--改(订单)状态
    复制代码
    

    4.1 新建订单 Order-Module

    1.  seata-order-service2001
    
    2.  POM
    复制代码
        <dependencies>
            <!--seata-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <!-- 因为兼容版本问题,所以需要剔除它自带的seata的包 -->
                <exclusions>
                    <exclusion>
                        <artifactId>seata-all</artifactId>
                        <groupId>io.seata</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>0.9.0</version>
            </dependency>
    
            <!--openFeign:微服务之间的调用不适用ribbon就是用feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <!--web/actuator这两个一般一起使用,写在一起-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--监控-->
            <dependency>
                <groupId>
                    org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!--spring cloud alibaba nacos-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--SpringCloud alibaba sentinel-datasource-nacos:后续做持久化用到-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
            <!--SpringCloud alibaba Sentinel-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
            
            <!--Mybatis和SpringBoot的整合-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <!--如果没写版本,从父层面找,找到了就直接用,全局统一-->
            </dependency>
    
            <!--mysql-connector-java-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--jdbc-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    复制代码
    3.  YML
    复制代码
    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        alibaba:
          seata:
            # 这是在seata的file.conf中配置的事务组ID
            tx-service-group: txl_tx_group
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: root
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    mybatis:
      # 扫描类路径下mapper文件夹下的.xml配置文件
      mapper-locations: classpath:mapper/*.xml
    复制代码
    4.  file.conf
        
        这是2001模块的file.conf,seata软件那里还有一个总控的file.conf
        
        注意修改这两处:
    复制代码
    

    img

    transport {
      # tcp udt unix-domain-socket
      type = "TCP"
      #NIO NATIVE
      server = "NIO"
      #enable heartbeat
      heartbeat = true
      #thread factory for netty
      thread-factory {
        boss-thread-prefix = "NettyBoss"
        worker-thread-prefix = "NettyServerNIOWorker"
        server-executor-thread-prefix = "NettyServerBizHandler"
        share-boss-worker = false
        client-selector-thread-prefix = "NettyClientSelector"
        client-selector-thread-size = 1
        client-worker-thread-prefix = "NettyClientWorkerThread"
        # netty boss thread size,will not be used for UDT
        boss-thread-size = 1
        #auto default pin or 8
        worker-thread-size = 8
      }
      shutdown {
        # when destroy server, wait seconds
        wait = 3
      }
      serialization = "seata"
      compressor = "none"
    }
    
    
    service {
    
    
      vgroup_mapping.txl_tx_group = "default"
    
    
      default.grouplist = "127.0.0.1:8091"
      enableDegrade = false
      disable = false
      max.commit.retry.timeout = "-1"
      max.rollback.retry.timeout = "-1"
      disableGlobalTransaction = false
    }
    
    client {
      async.commit.buffer.limit = 10000
      lock {
        retry.internal = 10
        retry.times = 30
      }
      report.retry.count = 5
      tm.commit.retry.count = 1
      tm.rollback.retry.count = 1
    }
    
    
    ## transaction log store
    store {
      ## store mode: file、db
      mode = "db"
    
    
      ## file store
      file {
        dir = "sessionStore"
    
    
        # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
        max-branch-session-size = 16384
        # globe session size , if exceeded throws exceptions
        max-global-session-size = 512
        # file buffer size , if exceeded allocate new buffer
        file-write-buffer-cache-size = 16384
        # when recover batch read size
        session.reload.read_size = 100
        # async, sync
        flush-disk-mode = async
      }
    
    
      ## database store
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
        datasource = "dbcp"
        ## mysql/oracle/h2/oceanbase etc.
        db-type = "mysql"
        driver-class-name = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "root"
        password = "root"
        min-conn = 1
        max-conn = 3
        global.table = "global_table"
        branch.table = "branch_table"
        lock-table = "lock_table"
        query-limit = 100
      }
    }
    lock {
      ## the lock store mode: local、remote
      mode = "remote"
    
    
      local {
        ## store locks in user's database
      }
    
    
      remote {
        ## store locks in the seata's server
      }
    }
    recovery {
      #schedule committing retry period in milliseconds
      committing-retry-period = 1000
      #schedule asyn committing retry period in milliseconds
      asyn-committing-retry-period = 1000
      #schedule rollbacking retry period in milliseconds
      rollbacking-retry-period = 1000
      #schedule timeout retry period in milliseconds
      timeout-retry-period = 1000
    }
    
    
    transaction {
      undo.data.validation = true
      undo.log.serialization = "jackson"
      undo.log.save.days = 7
      #schedule delete expired undo_log in milliseconds
      undo.log.delete.period = 86400000
      undo.log.table = "undo_log"
    }
    
    
    ## metrics settings
    metrics {
      enabled = false
      registry-type = "compact"
      # multi exporters use comma divided
      exporter-list = "prometheus"
      exporter-prometheus-port = 9898
    }
    
    
    support {
      ## spring
      spring {
        # auto proxy the DataSource bean
        datasource.autoproxy = false
      }
    }
    复制代码
    5.  registry.conf
    
        知名注册到nacos中:
    复制代码
    

    img

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
    
    
      nacos {
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
      eureka {
        serviceUrl = "http://localhost:8761/eureka"
        application = "default"
        weight = "1"
      }
      redis {
        serverAddr = "localhost:6379"
        db = "0"
      }
      zk {
        cluster = "default"
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      consul {
        cluster = "default"
        serverAddr = "127.0.0.1:8500"
      }
      etcd3 {
        cluster = "default"
        serverAddr = "http://localhost:2379"
      }
      sofa {
        serverAddr = "127.0.0.1:9603"
        application = "default"
        region = "DEFAULT_ZONE"
        datacenter = "DefaultDataCenter"
        cluster = "default"
        group = "SEATA_GROUP"
        addressWaitTime = "3000"
      }
      file {
        name = "file.conf"
      }
    }
    
    
    config {
      # file、nacos 、apollo、zk、consul、etcd3
      type = "file"
    
    
      nacos {
        serverAddr = "localhost"
        namespace = ""
      }
      consul {
        serverAddr = "127.0.0.1:8500"
      }
      apollo {
        app.id = "seata-server"
        apollo.meta = "http://192.168.1.204:8801"
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      etcd3 {
        serverAddr = "http://localhost:2379"
      }
      file {
        name = "file.conf"
      }
    }
    
    复制代码
    6.  domain
    复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class CommonResult<T>
    {
        private Integer code;
        private String  message;
        private T       data;
    
    
        public CommonResult(Integer code, String message)
        {
            this(code,message,null);
        }
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Order
    {
        private Long id;
    
    
        private Long userId;
    
    
        private Long productId;
    
    
        private Integer count;
    
    
        private BigDecimal money;
    
    
        private Integer status; //订单状态:0:创建中;1:已完结
    }
    
    复制代码
    7.  Dao接口及实现
    
        1.  OrderDao
    复制代码
        @Mapper
        public interface OrderDao {
        
            //1.新建订单
            void create(Order order);
        
            //2.修改订单状态,从0->1
            void update(@Param("userId") Long userId, @Param("status") Integer status);
        
        }
    复制代码
        2.  OrderMapper.xml
    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.atguigu.springcloud.alibaba.dao.OrderDao">
    
    
        <insert id="create">
            insert into t_order (id, user_id, product_id, count, money, status) values (null, #{userId},#{productId},#{count},#{money},0);
        </insert>
    
        <update id="update">
            update t_order set status = 1 where user_id = #{userId} and status = #{status};
        </update>
    
        <!--定义一个结果集和实体类的映射表-->
        <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Order">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="user_id" property="userId" jdbcType="BIGINT"/>
            <result column="product_id" property="productId" jdbcType="BIGINT"/>
            <result column="count" property="count" jdbcType="INTEGER"/>
            <result column="money" property="money" jdbcType="DECIMAL"/>
            <result column="status" property="status" jdbcType="INTEGER"/>
        </resultMap>
        
    </mapper>
    复制代码
    8.  Service接口及实现
        
        Order2001驱动自己,外加调用库存和账户:3个service
        
        1.  OrderService
    复制代码
    public interface OrderService {
    
        void creat(Order order);
    }
    
    复制代码
        2.  StorageService
    复制代码
    @FeignClient(value = "seata-storage-service")  //这里肯定要调用库存微服务
    public interface StorageService {
    
        //比如买了5个1号商品:对1号商品库存减5
        @PostMapping(value = "/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    
    }
    复制代码
        3.  AccountService
    复制代码
    @FeignClient(value = "seata-account-service")
    public interface AccountService {
    
        @PostMapping(value = "/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long productId, @RequestParam("money") BigDecimal money);
    }
    
    复制代码
        4.  OrderServiceImpl
    复制代码
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
    
        @Resource
        private OrderDao orderDao;
        @Resource
        private StorageService storageService;
        @Resource
        private AccountService accountService;
    
    
        @Override
        public void creat(Order order) {
    
            log.info("-------->开始新建订单");
            orderDao.create(order);
    
            log.info("------>订单微服务开始调用库存,进行扣减数量");
            storageService.decrease(order.getProductId(), order.getCount());
    
            log.info("------>订单微服务开始调用账户,进行扣减钱");
            accountService.decrease(order.getUserId(), order.getMoney());
    
            log.info("------>修改订单的状态:0-->1, 1表示订单已完成");
            orderDao.update(order.getUserId(), 0);
    
            log.info("--->下订单结束了,O(∩_∩)O哈哈~");
        }
    }
    
    复制代码
    9.  Controller
    复制代码
    @RestController
    @Slf4j
    public class OrderController {
        
        @Resource
        private OrderService orderService;
        
        @GetMapping(value = "/order/create")
        public CommonResult create(Order order){
            orderService.creat(order);
            return new CommonResult(200, "订单创建成功");
        }
    }
    复制代码
    10. Config配置
        
        1.  MyBatisConfig
    复制代码
    @Configuration
    @MapperScan({"com.atguigu.springcloud.alibaba.dao"})
    public class MyBatisConfig {
    }
    复制代码
        2.  DataSourceProxyConfig
    复制代码
    package com.atguigu.springcloud.alibaba.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;
    
    @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);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
            sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
            return sqlSessionFactoryBean.getObject();
        }
    }
    复制代码
        3.  
        
    11. 主动类
        
        //取消数据源的自动创建:
        因为我们将数据源纳入我们自己的管理,并且使用seata对数据源进行代理。
    复制代码
    @EnableFeignClients
    @EnableDiscoveryClient
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动创建
    public class SeataOrderMainApp2001 {
        public static void main(String[] args) {
            SpringApplication.run(SeataOrderMainApp2001.class, args);
        }
    }
    复制代码
    12. 尝试启动
        1.  先启动nacos
        2.  在启动seata
            
            一定要开启,多敲几次回车。
            
        3.  最后启动微服务
    复制代码
    

    4.2 新建库存 Storage-Module

    1.  新建module: seata-storage-service2002
    
    2.  POM
    复制代码
        <dependencies>
            <!--spring cloud alibaba nacos-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
    
            <!--SpringCloud alibaba sentinel-datasource-nacos:后续做持久化用到-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
    
            <!--seata-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <!-- 因为兼容版本问题,所以需要剔除它自带的seata的包 -->
                <exclusions>
                    <exclusion>
                        <artifactId>seata-all</artifactId>
                        <groupId>io.seata</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>0.9.0</version>
            </dependency>
    
            <!--openFeign:微服务之间的调用不适用ribbon就是用feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
            <!--web/actuator这两个一般一起使用,写在一起-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--监控-->
            <dependency>
                <groupId>
                    org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!--SpringCloud alibaba Sentinel-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
    
            <!--Mybatis和SpringBoot的整合-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <!--如果没写版本,从父层面找,找到了就直接用,全局统一-->
            </dependency>
    
            <!--mysql-connector-java-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--jdbc-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    复制代码
    3.  yml
    复制代码
    server:
      port: 2002
    
    spring:
      application:
        name: seata-storage-service 
      cloud:
        alibaba:
          seata:
            tx-service-group: txl_tx_group
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: root
        
    logging:
      level:
        io:
          seata: info
    
    #mybatis:
    #  # 扫描类路径下mapper文件夹下的.xml配置文件
    #  mapper-locations: classpath:mapper/*.xml
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    复制代码
    4.  file.conf/registry.conf 同2001一模一样
    
    5.  domain
    
        1.  CommonResult<T>同2001一样
        
        2.  Storage
    复制代码
    @Data
    public class Storage {
    
        private Long id;
    
        // 产品id
        private Long productId;
    
        //总库存
        private Integer total;
    
        //已用库存
        private Integer used;
    
        //剩余库存
        private Integer residue;
    }
    复制代码
    6.  StorageDao
    复制代码
    @Mapper
    public interface StorageDao {
        //扣减库存:根据产品ID扣除
        void decrease(@Param("productId") Long productId, @Param("count") Integer count);
    }
    
    复制代码
    7. StorageMapper.xml
    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.atguigu.springcloud.alibaba.dao.StorageDao">
    
        <!--定义一个结果集和实体类的映射表-->
        <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Storage">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="product_id" property="productId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="INTEGER"/>
            <result column="used" property="used" jdbcType="INTEGER"/>
            <result column="residue" property="residue" jdbcType="INTEGER"/>
        </resultMap>
        
        <update id="decrease">
            update
                t_storage
            set     
                used = used + #{count}, residue = residue - #{count}
            where
                product_id = #{productId}
        </update>
        
    </mapper>
    复制代码
    8.  Service接口及实现
    
        1.  StorageService
    复制代码
    public interface StorageService {
        /**
         * 扣减库存
         */
        void decrease(Long productId, Integer count);
    }
    
    复制代码
        2.  StorageServiceImpl
    复制代码
    @Service
    public class StorageServiceImpl implements StorageService {
        
        private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
        
        @Resource
        private StorageDao storageDao;
    
        /**
         * 扣减库存
         * @param productId
         * @param count
         */
        @Override
        public void decrease(Long productId, Integer count) {
            LOGGER.info("-------->storage-service中扣减库存");
            storageDao.decrease(productId,count);
        }
    }
    复制代码
    9.  Controller
    复制代码
        @RestController
        public class StorageController {
            
            @Autowired
            private StorageService storageService;
        
            /**
             * 库存扣减
             */
            @RequestMapping("/storage/decrease")
            public CommonResult decrease(Long productId, Integer count){
                
                storageService.decrease(productId, count);
                
                return new CommonResult(200, "扣减库存成功!");
            }
        }
    复制代码
    10. config包:
        
        MyBatisConfig,DataSourceProxyConfig同2001一样
        
    11. 主启动类
    复制代码
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataStorageServiceApplication2002 {
        public static void main(String[] args) {
            SpringApplication.run(SeataStorageServiceApplication2002.class, args);
        }
    }
    
    复制代码
    

    4.3 新建账户 Account-Module

    1.  新建module
        
        seata-account-service2003
        
    2.  POM
        同2002/2001
    
    3.  yml
    复制代码
    server:
      port: 2003
    
    spring:
      application:
        name: seata-account-service
      cloud:
        alibaba:
          seata:
            tx-service-group: txl_tx_group
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
        username: root
        password: root
    
    feign:
      hystrix:
        enabled: false
    
    logging:
      level:
        io:
          seata: info
    
    #mybatis:
    #  # 扫描类路径下mapper文件夹下的.xml配置文件
    #  mapper-locations: classpath:mapper/*.xml
    mybatis:
      mapperLocations: classpath:mapper/*.xml
    复制代码
    4.  domian
        
        1.  CommonResult<T>同2001/2002
        
        2.  account
    复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account {
        
        private Long id;
        
        /**
         * 用户id
         */
        private Long userId;
        
        /**
         * 总额度
         */
        private BigDecimal total;
        
        /**
         * 已用额度
         */
        private BigDecimal used;
        
        /**
         * 剩余额度
         */
        private BigDecimal residue;
    }
    复制代码
    5.  AccountDao
    复制代码
    @Mapper
    public interface AccountDao {
    
        /**
         * 扣减账户余额
         */
        void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
    }
    复制代码
    6.  AccountMapper.xml
    复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.atguigu.springcloud.alibaba.dao.AccountDao">
    
        <!--定义一个结果集和实体类的映射表-->
        <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Account">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="user_id" property="userId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="DECIMAL"/>
            <result column="used" property="used" jdbcType="DECIMAL"/>
            <result column="residue" property="residue" jdbcType="DECIMAL"/>
        </resultMap>
    
        <update id="decrease">
            update
                t_account
            set
                residue = residue - #{money}, used = used + #{money}
            where
                user_id = #{userId}
        </update>
    
    </mapper>
    复制代码
    7.  service及实现类
        
        1.  AccountService
    复制代码
    public interface AccountService {
    
        /**
         * 扣减账户余额
         */
        void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    复制代码
        2.  AccountServiceImpl
    复制代码
    @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中扣减账户余额开始");
            //模拟超时异常:全局事务回滚
            try { 
                TimeUnit.SECONDS.sleep(20); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            }
            accountDao.decrease(userId,money);
            LOGGER.info("------->account-service中扣减账户余额结束");
        }
    }
    复制代码
    8.  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,"扣减账户余额成功!");
            
        }
    }
    复制代码
    9.  config包:
    
        MyBatisConfig
        DataSourceProxyConfig 
        同2001/2002
        
    10. 启动类 SeataAccountMainApp2003
    复制代码
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    @EnableDiscoveryClient
    @EnableFeignClients
    public class SeataAccountMainApp2003
    {
        public static void main(String[] args)
        {
            SpringApplication.run(SeataAccountMainApp2003.class, args);
        }
    }
    复制代码
    11. file.conf/registry.conf同2001/2002
    复制代码
    

    5 案例测试

    如果一直循环报错:
    
        can not connect to8091 cause:can not register RM,err:register error,role:RMROLE
        
        重启seata试试。
    
    是否真的可以只需要一个@GlobalTransactiondl注解在业务方法上即可实现事务的控制?
    
    1.  业务流程
        
        下订单---减库存---扣余额---改订单状态
        
    2.  三个库中的三张表的初始状态
    复制代码
    

    img

    5.1 测试正常下单:

    启动nacos/seata
    启动2001/2002/2003
    
    1.  数据库情况
    复制代码
    

    img

    2.  测试:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
        
        测试成功:
    复制代码
    

    img

    5.2 测试超时异常:没加 @GlobalTransactional

    1.  AccountServiceImpl添加超时异常
    复制代码
        /**
         * 扣减账户余额
         */
        @Override
        public void decrease(Long userId, BigDecimal money) {
            LOGGER.info("------->account-service中扣减账户余额开始");
            //模拟超时异常:全局事务回滚
            try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            accountDao.decrease(userId,money);
            LOGGER.info("------->account-service中扣减账户余额结束");
        }
    复制代码
    2.  报超时错误
    复制代码
    

    img

    3.  数据库:
        
        订单状态没有设置为已完成,但是库存减少了,账户余额减少了。
        而且由于feign的重试机制,账户余额还有可能被多次扣减。
    复制代码
    

    5.3 测试超时异常:加 @GlobalTransactional

    1.  在业务的入口添加全局事务控制:@GlobalTransactional
    
    //发生了任何异常,统统回滚
    @GlobalTransactional(name = "txl-create-order", rollbackFor = Exception.class)
    复制代码
    

    img

    2.  测试:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
    
        数据库初始状态
    复制代码
    

    img

        测试后:下单后数据库数据没有任何改变
    复制代码
    

    img

    img

    3.  将异常注掉再测试下:
    复制代码
    

    img

    img

        测试成功:
    复制代码
    

    img

    5.4 小结

        做好配置后,我们只需要使用一个
        @GlobalTransactional(name = "txl-create-order", rollbackFor = Exception.class)
        
        放在业务的入口,即可实现控制全局的事务。
    复制代码
    

    6. Seata 事务控制原理

        0.9 不支持集群,工作要用1.0以后的版本。
    复制代码
    

    6.1 再看 TC/TM/RM 三大组件

        TC:seata的服务器
        TM:事物的发起者,业务的入口。
            @GlobalTransactional(name = "txl-create-order", rollbackFor = Exception.class)
        RM:事务的参与者,一个数据库就是一个RM。
    复制代码
    

    img

    6.2 分布式事务的执行流程

        1.  TM开启分布式事务(TM向TC注册全局事无记录)
        
            @GlobalTransactional(name = "txl-create-order", rollbackFor = Exception.class)
        
        2.  按业务场景,编排数据库,服务等事务内资源(RM向TC汇报资源准备状态)
        
            资源管理器要向事务协调器汇报状态。
            
        3.  TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)
            
            TM通知TC是提交/回滚
            
        4.  TC汇总事务信息,决定分布式事务是提交还是回滚。
        
        5.  TC通知所有RM提交/回滚资源,事务二阶段结束。
    复制代码
    

    img

    6.3 默认 AT 模式:如何做到对业务的无侵入

        提供无侵入自动补偿的事务模式。
        
        免费的是AT,收费的是阿里云GTS。
        
        1.  AT模式如何做到对业务的无侵入。
        
            官方说法:
            
            一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
            
            二阶段:
                提交异步化,非常快速地完成。
                回滚通过一阶段的回滚日志进行反向补偿。
                
            反向补偿:前面做了insert,后面回滚时做delete。
    复制代码
    

    img

        2.  几个表
        
            我们的每个数据库都有一个自身的数据表和一个事务的回滚表:undo_log
            
            seata库中有这几个表:
            
                branch_table
                global_table
                lock_table
    复制代码
    

    img

    6.4:一阶段,二阶段提交 / 二阶段回滚

        我们解释一下一阶段,二阶段提交/二阶段回滚
        
        1.   一阶段加载
        
            一条SQL语句执行时都经历了什么:
    复制代码
    

    img

        2.  二阶段提交:
    复制代码
    

    img

        3.  二阶段回滚
            
            还原之前要检验一下脏写,因为高并发的情况下或者出了异常,不排除被其他调用修改。
    复制代码
    

    img

        4.  为什么seata库中的几张表都没有数据呢?
        
            回滚会删,成功提交也会删。
    复制代码
    

    6.5. 断点 debug 看一下

    img

        1.  seata库中的这三张表都没数据:
    复制代码
    

    img

        2.  debug启动2003
    复制代码
    

    img

        3.  seata库的三个表此时是有数据的
            
            因为程序还没走完,事务还没有提交/回滚,中间数据都还没有删掉:
    复制代码
    

    img

        4.  分析一下branch_table表。
            
            记录了各个RM:资源管理器(分支资源)的信息
    复制代码
    

    img

        5.  分析一下global_table
    复制代码
    

    img

        6.  分析一下:
            lock_table:
    复制代码
    

    img

        7.  每个业务库都有一个undo_log表
        
            此时也有数据:
    复制代码
    

    img

            rollback_info是JSON,可以解析:
            
            即:分支事务修改前的信息(beforeImage)和修改后的信息(afterImage)都在这里面。
    复制代码
    

    img

        8.  看一下`seata_storage` 库中的 undo_log表中的rollback_info信息。
        
            原始数据是:
                userd: 40
                residue: 60
    复制代码
    

    img

        9.  放行:
        
            seata库中表中的中间数据和undo_log表的数据都删除了。
            (我得storage库中undo_log种的数据没有删除,再次看又删除了)
            
            异步任务阶段的分支提交请求将异步和批量地删除响应的undo_log记录。
            
            异步:不是一下子全删除。
            
        10. 流程图
    复制代码
    

    img

  • 相关阅读:
    android 解密工具
    android打包需要的图标
    Mac 创建软链接
    历届试题 Excel地址
    算法训练 字串统计
    最长回文子串
    算法提高 P1001【大数乘法】
    算法提高 拿糖果【埃氏筛 动态规划】
    算法训练 未名湖边的烦恼
    算法提高 合并石子【动态规划】
  • 原文地址:https://www.cnblogs.com/coderD/p/14350119.html
Copyright © 2011-2022 走看看