Spring CloudAlibaba:Seata

文章目录


是一个分布式事务的解决方案,

分布式事务中的一些概念,也是seata中的概念:
在这里插入图片描述
在这里插入图片描述

seata安装:

  1. 下载安装seata的安装包

    进入 Seata 官网下载地址或者 Seata Github下载地址,选择 Seata 1.3.0 版本下载。

    期间遇到了很多bug:参考这篇博客配置https://blog.csdn.net/fsdad/article/details/108711252

    解压后
    在这里插入图片描述

为了解决bin目录下闪退问题,需要在bin同级目录下建logs文件夹,在里面建seata_gc.log

如下双击闪退
在这里插入图片描述
在这里插入图片描述
*注:seata官方从1.0版本后不再提供sql脚本,以及nacos推送配置脚本,需要从0.9.0的版本复制*

将红色框框的四个文件复制到1.3.0/conf目录下
在这里插入图片描述
注意lib目录下需要复制粘贴自己的mysql驱动的对应版本,否则报错
在这里插入图片描述
2. 修改1.3.0版本的file.conf
在这里插入图片描述

3 . mysql建库建表

1,上面指定了数据库为seata,所以创建一个数据库名为seata

2,建表,在seata的安装目录下有一个**db_store.sql,**刚才从0.9版本复制过来,运行即可
在这里插入图片描述
4. 继续修改配置文件,修改registry.conf

修改typenacos,此处是声明你使用的注册中心
application为seata启动后注册到nacos的服务名(愿意改就改,不愿意改就默认就行)
*注:group默认为:SEATA_GROUP,可以更改DEFAULT_GROUP*,或者使用默认分组也可以
在这里插入图片描述
在这里插入图片描述
5. 修改从0.9.0版本拉过来的config.txt文件,注意这个需要和nacos-config.sh上下级目录

//config.txt文件
service.vgroupMapping.my_test_tx_group=default  
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
store.mode=db  /*此处修改为db*/
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver  /*自定义修改*/
store.db.url=jdbc:mysql://192.168.204.201:3306/seata?useUnicode=true /*自定义修改*/
store.db.user=root  /*自定义修改*/
store.db.password=root  /*自定义修改*/
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

在这里插入图片描述
6. 启动

先启动nacos

在启动seata-server(运行安装目录下的,seata-server.bat)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在创建微服务前,这里需要 将参数配置到Nacos配置中心

修改完毕后执行1.3.0/conf下的nacos-config.sh命令为:sh nacos-config.sh 127.0.0.1

为nacos地址,按实际情况修改即可
在这里插入图片描述
在这里插入图片描述
执行完毕后打开nacos 配置中心 会看到
在这里插入图片描述
业务说明
在这里插入图片描述
下单—>库存—>账号余额

  1. 创建三个数据库
    在这里插入图片描述
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;

在这里插入图片描述
2. 创建对应的表
在这里插入图片描述

/*分别对应创建表*/
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;

/*--------------------------------------------------------------------*/
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');

/*--------------------------------------------------------------------*/
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')

在这里插入图片描述

  1. 创建回滚日志表,方便查看
    在这里插入图片描述
    注意每个库都要执行一次这个db_undo_log.sql,生成回滚日志表
drop table `undo_log`;
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;

在这里插入图片描述

  1. 每个业务都创建一个微服务,也就是要有三个微服务,订单,库存,账号

订单,seta-order-2001

  1. pom
<dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--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.2.0</version>
        </dependency>
        <!--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>
        <!--mysql-druid-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  1. 配置文件

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata?useUnicode=true&charcaterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: jh7851192
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          # 数据源其他配置
          initialSize: 5
          minIdle: 5
          maxActive: 20
          maxWait: 60000
          timeBetweenEvictionRunsMillis: 60000
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          poolPreparedStatements: true
          # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
          filters: stat,wall #,log4j
          maxPoolPreparedStatementPerConnectionSize: 20
          useGlobalDataSourceStat: true
          connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    
    seata:
      enabled: true
      application-id: ${spring.application.name}
      tx-service-group: my_test_tx_group
      enable-auto-data-source-proxy: true
      service:
        vgroup-mapping:
          my_test_tx_group: default
        grouplist:
          default: localhost:8091
        enable-degrade: false
        disable-global-transaction: false
      config:
        type: nacos
        nacos:
          namespace:
          serverAddr: localhost:8848
          group: SEATA_GROUP
          userName: ""
          password: ""
      registry:
        type: nacos
        nacos:
          application: seata-server  # 此处名称徐鹤 seata-server application一致
          server-addr: localhost:8848
          namespace:
          userName: ""
          password: ""
    

    还要额外创建其他配置文件,创建一个file.conf:

    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->rgroup
     # 事务组名称
     vgroup_mapping.fsp_tx_group = "default"
     #only support single node
     default.grouplist = "127.0.0.1:8091"
     #degrade current not support
     enableDegrade = false
     #disable
     disable = false
     #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
     max.commit.retry.timeout = "-1"
     max.rollback.retry.timeout = "-1"
    }
    
    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 = "file"
     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
     }
    }
    
    

    创建registry.conf:

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
     
      nacos {
        #serverAddr = "localhost"
        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"
      }
    }
    
    

    实际上,就是要将seata中的我们之前修改的两个配置文件复制到这个项目下

  2. 主启动类

 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动创建
 @EnableDiscoveryClient
 @EnableFeignClients
 public class SeataOrderMain2001 {
 
     public static void main(String[] args) {
         SpringApplication.run(SeataOrderMain2001.class,args);
     }
 }
  1. service层

    public interface OrderService {
    
        /**
         * 创建订单
         * @param order
         */
        void create(Order order);
    }
    
    @FeignClient(value = "seata-storage-service")
    public interface StorageService {
    
        /**
         * 减库存
         * @param productId
         * @param count
         * @return
         */
        @PostMapping(value = "/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    }
    
    @FeignClient(value = "seata-account-service")
    public interface AccountService {
    
        /**
         * 减余额
         * @param userId
         * @param money
         * @return
         */
        @PostMapping(value = "/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
     
     
    
    
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
    
        @Resource
        private OrderDao orderDao;
        @Resource
        private AccountService accountService;
        @Resource
        private StorageService storageService;
    
        /**
         * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
         * 简单说:
         * 下订单->减库存->减余额->改状态
         * GlobalTransactional seata开启分布式事务,异常时回滚,name保证唯一即可
         * @param order 订单对象
         */
        @Override
        ///@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
        public void create(Order order) {
            // 1 新建订单
            log.info("----->开始新建订单");
            orderDao.create(order);
    
            // 2 扣减库存
            log.info("----->订单微服务开始调用库存,做扣减Count");
            storageService.decrease(order.getProductId(), order.getCount());
            log.info("----->订单微服务开始调用库存,做扣减End");
    
            // 3 扣减账户
            log.info("----->订单微服务开始调用账户,做扣减Money");
            accountService.decrease(order.getUserId(), order.getMoney());
            log.info("----->订单微服务开始调用账户,做扣减End");
    
            // 4 修改订单状态,从0到1,1代表已完成
            log.info("----->修改订单状态开始");
            orderDao.update(order.getUserId(), 0);
    
            log.info("----->下订单结束了,O(∩_∩)O哈哈~");
        }
    }
    
  2. dao层,也就是接口

    @Mapper
    public interface OrderDao {
        /**
         * 1 新建订单
         * @param order
         * @return
         */
        int create(Order order);
    
        /**
         * 2 修改订单状态,从0改为1
         * @param userId
         * @param status
         * @return
         */
        int update(@Param("userId") Long userId, @Param("status") Integer status);
    }
    

    在resource下创建mapper文件夹,编写mapper.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.eiletxie.springcloud.alibaba.dao.OrderDao">
    
        <resultMap id="BaseResultMap" type="com.eiletxie.springcloud.alibaba.domain.Order">
            <id column="id" property="id" jdbcType="BIGINT"></id>
            <result column="user_id" property="userId" jdbcType="BIGINT"></result>
            <result column="product_id" property="productId" jdbcType="BIGINT"></result>
            <result column="count" property="count" jdbcType="INTEGER"></result>
            <result column="money" property="money" jdbcType="DECIMAL"></result>
            <result column="status" property="status" jdbcType="INTEGER"></result>
        </resultMap>
    
        <insert id="create" parameterType="com.eiletxie.springcloud.alibaba.domain.Order" useGeneratedKeys="true"
                keyProperty="id">
            insert into t_order(user_id,product_id,count,money,status) values (#{userId},#{productId},#{count},#{money},0);
        </insert>
    
        <update id="update">
            update t_order set status =1 where user_id =#{userId} and status=#{status};
       </update>
    </mapper>
     
    
  3. controller层

    @RestController
    public class OrderController {
        @Resource
        private OrderService orderService;
    
    
        /**
         * 创建订单
         *
         * @param order
         * @return
         */
        @GetMapping("/order/create")
        public CommonResult create(Order order) {
            orderService.create(order);
            return new CommonResult(200, "订单创建成功");
        }
    
    
    }
    
  4. entity类(也叫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);
        }
    }
     
    

在这里插入图片描述
7 . config配置类

   @Configuration
   @MapperScan({"com.eiletxie.springcloud.alibaba.dao"})		指定我们的接口的位置
   public class MyBatisConfig {
   
   
   }
    
   
   /**
    * @Author EiletXie
    * @Since 2020/3/18 21:51
    * 使用Seata对数据源进行代理
    */
   @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 druidDataSource) {
           return new DataSourceProxy(druidDataSource);
       }
   
       @Bean
       public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
           SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
           bean.setDataSource(dataSourceProxy);
           ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
           bean.setMapperLocations(resolver.getResources(mapperLocations));
           return bean.getObject();
       }
   }
    
    
   
 ==库存==,seta-storage-2002

看脑图

  1. pom
  2. 配置文件
  3. 主启动类
  4. service层
  5. dao层
  6. controller层
==账号==,seta-account-2003

看脑图

  1. pom
  2. 配置文件
  3. 主启动类
  4. service层
  5. dao层
  6. controller层

5 . 全局创建完成后,首先测试不加seata
在这里插入图片描述
在这里插入图片描述
6. 使用seata:

订单模块的serviceImpl类中的create方法添加启动分布式事务的注解

/**
	这里添加开启分布式事务的注解,name指定当前全局事务的名称
	rollbackFor表示,发生什么异常需要回滚
	noRollbackFor:表示,发生什么异常不需要回滚
*/
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
///@GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)
public void create(Order order) {
    // 1 新建订单
    log.info("----->开始新建订单");
    orderDao.create(order);

    // 2 扣减库存
    log.info("----->订单微服务开始调用库存,做扣减Count");
    storageService.decrease(order.getProductId(), order.getCount());
    log.info("----->订单微服务开始调用库存,做扣减End");

    // 3 扣减账户
    log.info("----->订单微服务开始调用账户,做扣减Money");
    accountService.decrease(order.getUserId(), order.getMoney());
    log.info("----->订单微服务开始调用账户,做扣减End");

    // 4 修改订单状态,从0到1,1代表已完成
    log.info("----->修改订单状态开始");
    orderDao.update(order.getUserId(), 0);

    log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}

  1. 此时在测试

    发现,发生异常后,直接回滚了,前面的修改操作都回滚了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值