SpringBoot+eureka+seata完成分布式事务

简单的模拟一下购买商品下单,再扣库存的场景。

假设系统有两个服务,订单服务,与库存服务。购买商品时,订单服务先创建订单,再扣除库存。这两个事务要保持一致性。

首先需要下载安装seata

seata可以到官方的git上下载。https://github.com/seata/seata/releases。可以选择下载linux版本的,也可以下载window的安装包。我下载了1.1.0版本的window安装包。下载解压后里边就有三个文件夹bin,conf,lib。其实seata也是一个web项目,需要把它注册到eureka上。
进入conf文件夹,修改file.conf、register.conf。
修改file.conf,使用数据库的话,修改mode = “db”,然后下面的db就按照自己的数据库填。
如下图file.conf配置文件
修改register.conf,把type修改成eureka,然后填写自己eureka的地址。注意:application就是你这个seata服务注册到eureka上的名字,相当于springboot项目的application.name。

  type = "eureka"
  eureka {
    serviceUrl = "http://127.0.0.1:8000/eureka"
    application = "seata"
    weight = "1"
  }

然后数据库里还需要用到几个控制事务的表global_table、branch_table、lock_table

DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` BIGINT(20) NOT NULL,
  `xid` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` BIGINT(20) NULL DEFAULT NULL,
  `resource_group_id` VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` VARCHAR(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` VARCHAR(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` TINYINT(4) NULL DEFAULT NULL,
  `client_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` VARCHAR(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` DATETIME(0) NULL DEFAULT NULL,
  `gmt_modified` DATETIME(0) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` BIGINT(20) NULL DEFAULT NULL,
  `status` TINYINT(4) NOT NULL,
  `application_id` VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` INT(11) NULL DEFAULT NULL,
  `begin_time` BIGINT(20) NULL DEFAULT NULL,
  `application_data` VARCHAR(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` DATETIME(0) NULL DEFAULT NULL,
  `gmt_modified` DATETIME(0) NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `xid` VARCHAR(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_id` BIGINT(20) NULL DEFAULT NULL,
  `branch_id` BIGINT(20) NOT NULL,
  `resource_id` VARCHAR(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `table_name` VARCHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pk` VARCHAR(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` DATETIME(0) NULL DEFAULT NULL,
  `gmt_modified` DATETIME(0) NULL DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

创建注册中心、订单服务、库存服务三个项目

主要是一些配置问题,将一个订单配置,库存的直接复制就可以了。

订单服务

pom.xml 引入依赖,别引错,否则会报错。

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
   
   application.yml中配置seata事务组 ,注意配置的事务组要与file.conf中的vgroup_mapping.xxx 的xxx一致。
spring:
  # seata分组
  cloud:
    alibaba:
      seata:
        tx-service-group: seata_group

在项目的resource下,添加两个配置文件,file.conf与register.conf(register可以直接复制上边改好的register.conf)。在刚刚安装的seata里的conf文件夹中,有一个file.conf.example文件,这个是客户端的file.conf。把它改名成file.conf,复制到项目下,修改其中的service,和store。需要注意的就是vgroup_mapping.xxx= “yyy”,这里的xxx就是上边yml中配置的事务组,yyy就是最上边配置seata服务端时,注册到eureka上的服务名字。我刚刚填的是seata,这里也填seata。

service {
  #transaction service group mapping
  vgroup_mapping.seata_group = "seata"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}
store {
  ## store mode: file、db
  mode = "db"
  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "druid"
    ## mysql/oracle/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/alipay"
    user = "root"
    password = "root"
    minConn = 1
    maxConn = 10
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  }
}

最后可能还需要添加一个undo_log表。

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NOT NULL,
  `log_modified` datetime(0) NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

由于是简单的例子,我没有分库,所以我把这几个用到表都放到一个库里了。global_table、branch_table、lock_table这三个放到seata服务端配置的数据库,undo_log放在订单服务配置的数据库。

事务的使用

在需要使用到事务的服务中加上一个配置类,并且在启动类上添加注解@Import(DataSourceProxyAutoConfiguration.class)。

@Configuration
public class DataSourceProxyAutoConfiguration {

    /**
     * 数据源属性配置
     * {@link DataSourceProperties}
     */
    private DataSourceProperties dataSourceProperties;

    public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {
        this.dataSourceProperties = dataSourceProperties;
    }

    /**
     * 配置数据源代理,用于事务回滚
     *
     * @return The default datasource
     * @see DataSourceProxy
     */
    @Primary
    @Bean("dataSource")
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(dataSourceProperties.getUrl());
        dataSource.setUsername(dataSourceProperties.getUsername());
        dataSource.setPassword(dataSourceProperties.getPassword());
        dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
        return new DataSourceProxy(dataSource);
    }

//在启动类上添加注解
@Import(DataSourceProxyAutoConfiguration.class)
public class SeataAlipayApplication 

最后,在需要用到事务的接口上添加@GlobalTransactional注解就可以了。

@GlobalTransactional
public String createOrder(String did) 

小白弄了一天- -,主要是安装配置seata时比较麻烦,使用的话直接使用一个@GlobalTransactional注解就可以了,非常方便

Logo

更多推荐