springboot集成dubbo2.7.8+nacos1.3.2+seata1.4.0 分布式事务解决方案

序言:

分布式事务对于现在的分布式的项目来说很常见的一个问题,此次采用的是阿里开源的seata,seata官网

此次集成不需要其seata在客户端配置相关的file.conf 、registry.conf   配置文件。

不需要配置相关的配置文件时需要引入:seata-spring-boot-starter 的pom依赖,这个是我推荐使用的集成方式。

在此工程中实践分布式事务的流程是为:

 ———today服务创建订单,yesterday服务进行扣减余额,若yesterday服务扣减余额发生异常,则两个服务所执行的SQL数据进行回滚。

一、工程中的代码部分:

1、父工程的依赖:

<spring-boot.version>2.3.3.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
<dubbo.version>2.7.8</dubbo.version>
<seata.version>1.4.0</seata.version>

   <dependencyManagement>
        <dependencies>
             <!--spring boot-->
             <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud alibaba-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--seata-分布式事务管理-->
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-spring-boot-starter</artifactId>
                <version>${seata.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

2、子工程的依赖:

说明:即today服务和yesterday服务需要注册到nacos中的接口所在的工程,today和yesterday都会引入此工程

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

3、today服务和yesterday服务的启动类:

import io.seata.spring.annotation.datasource.EnableAutoDataSourceProxy;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
//开启dubbo
@EnableDubbo
//扫描工程中所依赖的包中的bean,将注册到springioc中
@ComponentScan("com.day")
//开启分布式事务的代理,将事务交给seata
@EnableAutoDataSourceProxy
//参考了pig-cloud开源项目中的动态数据源
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class XXXXXXXApplication {

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

}

4、today服务和yesterday服务的配置文件:

默认的配置文件:application.properties

公共的配置参数的配置文件:application-common.properties

当前开发环境的配置文件:application-dev.properties

其seata配置信息主要参考了seata官网:http://seata.io/zh-cn/docs/user/registry/nacos.html

在这里主要写出dubbo的配置信息和seata的配置信息:

#dubbo配置
dubbo.application.name=today-provider
dubbo.config-center.timeout=500000
dubbo.registry.address=nacos://${nacos.discovery.server-addr}
dubbo.protocol.name=dubbo
dubbo.protocol.port=20886
dubbo.scan.base-packages=com.day.api.provider.today
dubbo.consumer.check=false
dubbo.application.qos-enable=false
dubbo.application.qos-accept-foreign-ip=false
#seata配置
nacos.discovery.server-addr=47.104.78.115:8848
#seata-config
seata.application-id=${spring.application.name}
seata.tx-service-group=day_today_tx_group
seata.service.vgroup-mapping.day_today_tx_group=seata-days
seata.service.grouplist.seata-days=127.0.0.1:8091
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=${nacos.discovery.server-addr}
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos

5、today服务中调用yesterday服务的代码:

控制层:
@RestController
@Api(tags = "今天的控制层信息")
@RequestMapping("/today")
public class TodayController {
    private static final Logger log = LoggerFactory.getLogger(TodayController.class);
 
    @DubboReference(group = DubboNacosGroup.YESTERDAY_DUBBO_NACOS)
    private YesterdayProvider yesterdayProvider;

    @Autowired
    private IOrderService orderService;

    @ApiOperation(value = "测试分布式事务")
    @RequestMapping(value = "testDistributedTransaction", method = RequestMethod.POST)
    public JsonResult testDistributedTransaction() {
        Order order = new Order();
        order.setUserId("1001");
        order.setAmount(300d);
        return JsonResult.success(orderService.saveOrder(order));
    }
}
service层:
    /**
     * 用于测试分布式事务功能
     *
     * @param order
     * @author wangjunming
     * @since 2020/11/8 14:46
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public boolean saveOrder(Order order) {
        log.info("开始创建订单");
        final boolean save = save(order);
        boolean updataAmount = false;
        if(save){
            log.info("完成创建订单");
            log.info("开始扣减余额");
            AccountApiEntity accountApiEntity = new AccountApiEntity();
            accountApiEntity.setUserId(order.getUserId());
            accountApiEntity.setAmount(order.getAmount());
            updataAmount = yesterdayProvider.updataAmountByUserId(accountApiEntity);
            log.info("完成扣减余额");
        }
        log.info("开始更新订单状态");
//        int i = 12/0;
        boolean update = false;
        if(updataAmount){
            order.setStatus(Integer.valueOf("1"));
            update = update(order);
            log.info("完成更新订单状态");
        }
        return save && updataAmount && update;
    }

6、yesterday服务注册到nacos服务中的接口和类:

其中使用到的model和接口都在第二步中所创建的子工程中。这个时候需要调用在yesterday服务中的用户余额服务进行扣减余额。

接口:
public interface YesterdayProvider {

    /**
     * 用于测试分布式事务
     *
     * @author wangjunming
     * @since 2020/11/8 14:52
     */
    boolean updataAmountByUserId(AccountApiEntity accountApiEntity);


}
实现类:
import com.day.api.config.DubboNacosGroup;
import com.day.api.entitys.AccountApiEntity;
import com.day.api.provider.yesterday.YesterdayProvider;
import com.day.yesterday.persistence.entity.Account;
import com.day.yesterday.persistence.service.IAccountService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@DubboService(group = DubboNacosGroup.YESTERDAY_DUBBO_NACOS)
public class YesterdayProviderImpl implements YesterdayProvider {

    @Autowired
    private IAccountService accountService;

    /**
     * 用于测试分布式事务
     *
     * @param accountApiEntity 扣减金额
     * @author wangjunming
     * @since 2020/11/8 14:52
     */
    @Override
    public boolean updataAmountByUserId(AccountApiEntity accountApiEntity) {
        Account account = new Account();
        account.setUserId(accountApiEntity.getUserId());
        account.setAmount(accountApiEntity.getAmount());
        return accountService.updataAmountByUserId(account) != null;
    }

}

7、yesterday服务中调用用户服务进行扣减余额

    /**
     * 用于测试分布式事务,业务为根据用户ID扣减相应的余额
     *
     * @param account 扣减金额
     * @author wangjunming
     * @since 2020/11/8 14:18
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public Account updataAmountByUserId(Account account){
        final Account selOneAccount = selOne(account);
        if (selOneAccount == null) {
            log.error("余额不足!");
            return null;
        }
        if (account.getAmount().compareTo(selOneAccount.getAmount()) > 0) {
            log.error("余额不足!");
            return null;
        }
        final double amountPoor = selOneAccount.getAmount() - account.getAmount();
        selOneAccount.setAmount(amountPoor);
        final boolean update = accountMapper.updateById(selOneAccount) > 0;
        int i = 12/0;
        return update ? selOneAccount : null;
    }

--------------------------至此在代码方面的工作到此结束。

二、部署seata的server端的部分:

8、初始化seata服务端和各个服务端所需要的数据库

————获取seata服务端(server)初始化SQL语句的官网地址:https://github.com/seata/seata/blob/1.4.0/script/server/db/mysql.sql   

-- 创建数据库:
CREATE DATABASE `seata`;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `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`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

————创建today服务中的数据库和订单表:

# -----------创建订单表---------------------------------------------------------------
# 创建  seata_order 数据库
CREATE DATABASE `seata_order`;
# 创建订单表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`
(
    `id`             int(11)                                                 NOT NULL AUTO_INCREMENT,
    `order_no`       varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    `user_id`        varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    `commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    `status`         int(5)                                                  NULL DEFAULT 0,
    `amount`         double(14, 2)                                           NULL DEFAULT 0.00,
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  CHARACTER SET = utf8
  COLLATE = utf8_general_ci
  ROW_FORMAT = Dynamic;

————创建yesterday服务中的数据库和用户钱包表:

# --------------创建用户钱包表---------------------------------------------------------
#创建 seata_pay 数据库
CREATE DATABASE `seata_pay`;
#创建用户钱包表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`
(
    `id`      int(11)                                                 NOT NULL AUTO_INCREMENT,
    `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
    `amount`  double(14, 2)                                           NULL DEFAULT 0.00,
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 3
  CHARACTER SET = utf8
  COLLATE = utf8_general_ci
  ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account
-- ----------------------------
#初始化数据
INSERT INTO `account` 
VALUES (1, '1', 4000.00);
INSERT INTO `account`
VALUES (2, '1001', 50000.00);
SET FOREIGN_KEY_CHECKS = 1;

以及在today服务的数据库(seata_order)和yesterday服务的数据库(seata_pay)所创建的undo_log表

获取创建(client端)表的SQL语句官网地址:https://github.com/seata/seata/blob/1.4.0/script/client/at/db/mysql.sql

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

9、配置seata的server端所需要的配置信息

下载seata的server端地址:https://github.com/seata/seata/releases/tag/v1.4.0     

其配置信息参考:http://seata.io/zh-cn/docs/user/registry/nacos.html     

在其解压后修改conf文件下的   file.conf      和     registry.conf    

1.修改   file.conf    ,其中红色部分为此次修改的内容(就是将刚刚创建好的数据库):

## transaction log store, only used in seata-server  
store {
  ## store mode: file、db、redis
  mode = "db"

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "123456"

    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

}

2.修改   registry.conf    文件,其中红色部分是此次所需要修改的地方,以及与项目中配置文件的对应关系,在下方也有图片标注:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "seata-days"

# 重点说明:cluster = "seata-days"

# 其项目配置文件中的 seata.service.grouplist.seata-days=127.0.0.1:8091  ,此配置信息的  seata-days  ,需要对应此配置文件中  cluster = "seata-days"  中  的    "seata-days"   保持一致。
# 其项目配置文件中的 seata.service.vgroup-mapping.day_today_tx_group=seata-days    ,此配置信息的  seata-days ,需要对应此配置文件中 cluster = "seata-days"  中  的    "seata-days"  ,保持一致,

# 也就是 与  seata.service.grouplist.seata-days=127.0.0.1:8091  ,此配置信息的  seata-days   需要保持一致。
    username = "nacos"
    password = "nacos"

  }
   
}

此配置文件与项目的配置信息对应关系:

 

10、启动seata服务端

注意:在启动之前必须启动成功nacos1.3.0,本地则以单机模式启动nacos

启动seata服务:seata-server.bat  

在nacos(本地启动nacos命令:startup.cmd -m standalone )中注册的seata服务是:

————————至此则创建完成数据库,以及成功部署seata服务。

三、测试分布式事务是否成功

11、启动today服务和yesterday服务,并查看是否已注册

12、测试:

抛出异常:

AT事务在此得到的效果,

 

——————至此springboot集成dubbo2.7.8+nacos1.3.2+seata1.4.0  分布式事务解决方案,已实现效果。

docker中安装nacos1.3.1

参考:
1、https://www.cnblogs.com/binz/p/12295346.html
2、https://www.jianshu.com/p/e053f016371a

命令:
1.docker search nacos
2.docker pull nacos/nacos-server:1.3.1
3.docker run -d -p 8848:8848 -e MODE=standalone -v /usr/nacos/properties/custom.properties:/home/nacos/init.d/custom.properties -v /usr/nacos/logs:/home/nacos/logs --restart always --name nacos nacos/nacos-server:1.3.1


项目中使用的整合参考:

springboot集成dubbo2.7.8+nacos1.3.2

参考:   https://blog.csdn.net/lwb314/article/details/108233863

springboot整合mybatis-plus3.3.0+mybatis-plus动态数据源+druid1.1.22

参考:   https://gitee.com/log4j/pig/tree/master/pig-common/pig-common-datasource      此子工程。

 

此项目的github地址: www.github.com/qjyn1314/days    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值