Spring Cloud Alibaba +Nacos+Seata 分布式事务案例

本文介绍了如何搭建一个基于SpringCloud Alibaba Seata的微服务项目,包括SpringCloud、Nacos、Seata的版本选择与安装,以及微服务模块的创建、配置、数据库建表和Feign服务调用。详细步骤涵盖了从下载安装包到配置Nacos,再到Seata的配置和启动,最后展示了如何在微服务中实现分布式事务。
摘要由CSDN通过智能技术生成

组件及版本

Spring Cloud:Greenwich.RELEASE

Spring Cloud Alibaba:2.1.0.RELEASE

Spring boot: 2.1.3.RELEASE

Mybatis-Plus: 3.4.2

Nacos:2.2.0 官网 https://nacos.io/zh-cn/index.html

seata:0.9.0 官网 http://seata.io/zh-cn/index.html

Swagger:2.9.2

安装Nacos

下载

下载zip格式的安装包,然后进行解压缩操作

启动

#切换目录
cd nacos/bin
#命令启动
startup.cmd -m standalone

 访问nacos

 

打开浏览器输入 http://localhost:8848/nacos ,即可访问服务, 默认密码是 nacos/nacos 

注意刚进入事是没有这些配置的(我是搭建完后截的图,这些配置后边会提到)

搭建微服务项目

项目结构:

 一共两个微服务,order通过product-service-client 提供的feign 接口调用 product 中的服务

公司项目一般也是这种结构被调用的服务提供一个客户端项目依赖,调用方依赖这个客户端项目实现服务调用。

父项目pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xuweichao</groupId>
    <artifactId>springcloudalibaba-seata</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>order-service</module>
        <module>product-service</module>
    </modules>

    <name>springcloudalibaba-seata</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <!--父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>


    <!--依赖版本的锁定-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
        <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
        </dependencies>
    </dependencyManagement>
</project>

order-service 微服务pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloudalibaba-seata</artifactId>
        <groupId>com.xuweichao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>order-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.xuweichao</groupId>
            <artifactId>product-service-client</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

product-service pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloudalibaba-seata</artifactId>
        <groupId>com.xuweichao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-service</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>product-service-client</module>
        <module>product-service-server</module>
    </modules>
</project>

product-service-server pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>product-service</artifactId>
        <groupId>com.xuweichao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-service-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <dependency>
            <groupId>com.xuweichao</groupId>
            <artifactId>product-service-client</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>

        </resources>
    </build>
</project>

product-service-client pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>product-service</artifactId>
        <groupId>com.xuweichao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xuweichao</groupId>
    <artifactId>product-service-client</artifactId>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-annotation</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>


</project>

数据库建表:订单表 产品表

CREATE TABLE `order_base` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(10) DEFAULT NULL,
  `p_id` bigint(20) DEFAULT NULL,
  `p_name` varchar(50) DEFAULT NULL,
  `p_price` double(10,2) DEFAULT NULL,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8



CREATE TABLE `product_base` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `price` double(10,2) DEFAULT NULL,
  `stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

使用 代码生成工具生成数据库试题对象和 service

order-service 微服务 添加配置 application.yml

spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    password: xxx
    url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/database-name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root

server:
  port: 18080
swagger:
  enable: true
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

product-service 微服务 添加配置 application.yml

spring:
  application:
    name: product-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    password: xxx
    url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/database-name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root

server:
  port: 18080
swagger:
  enable: true
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

product-service-server 添加接口:

@Slf4j
@Api(value = "ProductBase 相关接口", tags = "ProductBase 相关接口")
@RestController
@RequestMapping("product")
public class ProductBaseController implements ProductServiceClient {
    @Autowired
    public IProductBaseService productBaseService;

    @Override
    @ApiOperation(value = "详情")
    @GetMapping("detail/{id}")
    public ProductBase  getProductBase(@PathVariable Long id) {
        log.info("获取的参数:===>>" + id);
        return productBaseService.getById(id);
    }

    @Override
    @ApiOperation(value = "修改库存")
    @GetMapping("{id}")
    public Boolean productUpdateStock(@PathVariable Long id) {
        log.info("获取的参数:===>>" + id);
        ProductBase productBase = productBaseService.getById(id);
        productBase.setStock(productBase.getStock()-1);
        return productBaseService.updateById(productBase);
    }

}

在product-service-client 添加 Feign 服务接口

@Component
@FeignClient(name = "product-service",path = "/product/")
public interface ProductServiceClient {
    @GetMapping("detail/{id}")
    ProductBase getProductBase(@PathVariable("id") Long id);

    @GetMapping("/{id}")
    Boolean productUpdateStock(@PathVariable("id") Long id);

}

order-service 中创建接口

@Slf4j
@Api(value = "OrderBase 相关接口", tags = "OrderBase 相关接口")
@RestController
@RequestMapping("order")
public class OrderBaseController {
    @Resource
    public IOrderBaseService orderBaseService;

    @ApiOperation(value = "添加", notes = "添加")
    @GetMapping("{pid}")
    public Boolean orderBaseSave(@PathVariable Long pid) {
        log.info("获取的参数:===>>" + pid);
        return orderBaseService.createOrder(pid);
    }
}

OrderServiceImpl:

@Service
public class OrderBaseServiceImpl extends ServiceImpl<OrderBaseMapper, OrderBase> implements IOrderBaseService {
    @Autowired
    private ProductServiceClient productServiceClient;

  
    @Override
    public Boolean createOrder(Long pid) {
        ProductBase productBase = productServiceClient.getProductBase(pid);
        OrderBase orderBase=new OrderBase();
        orderBase.setNumber(2);
        orderBase.setPId(productBase.getId());
        orderBase.setPName(productBase.getName());
        orderBase.setPPrice(productBase.getPrice());
        orderBase.setUserName("超超");
        this.save(orderBase);
       
        productServiceClient.productUpdateStock(orderBase.getPId());
        return Boolean.TRUE;
    }
}

这里是模拟的创建订单和减库存的操作

安装Seata

下载

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

进入conf 目录修改配置文件register.conf,我们这里是用nacos 作为注册和配置中心的,所以精简一下配置

registry {
  
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = "public"
    cluster = "default"
  }
}
config {
  type = "nacos"
  nacos {
    serverAddr = "localhost"
    namespace = "public"
	cluster = "default"
  }
}

初始Seata配置

进入 seata-0.9.0\server\src\main\resources 目录 找到 nacos-config.txt 文件

 第一个箭头哪里代表事务组

这里的语法为: service.vgroup_mapping.${your - service - gruop}=default ,中间的
${your - service - gruop} 为自己定义的服务组名称, 这里需要我们在程序的配置文件中配置。

store 那里改成自己的数据源配置,并建表

-- the table to store GlobalSession data
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `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`)
);

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

-- the table to store lock data
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` VARCHAR(128) NOT NULL,
  `xid` VARCHAR(96),
  `transaction_id` LONG ,
  `branch_id` LONG,
  `resource_id` VARCHAR(256) ,
  `table_name` VARCHAR(32) ,
  `pk` VARCHAR(36) ,
  `gmt_create` DATETIME ,
  `gmt_modified` DATETIME,
  PRIMARY KEY(`row_key`)
);

创建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=3 DEFAULT CHARSET=utf8

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

执行之后 在nacos 中就能看上最上边图中展示的效果了。

启动seata,进入 seata-server-0.9.0\seata\bin 目录执行

seata-server.bat -p 9000 -m file

将register.conf 文件分别拷贝到 order-service 和product-service-server 微服务 的resources 目录下

添加服务注册发现和 Seata事务组

分别在两个微服务resources目录下创建 bootstrap.yml 

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: public
        group: SEATA_GROUP
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: product_service_group

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: public
        group: SEATA_GROUP
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: order_service_group

在 ProductApplication 和OrderApplication 中添加注解 @EnableDiscoveryClient 用户服务发现

并在OrderApplication 添加 @EnableFeignClients(basePackages = "com.xxx") 开启feign服务调用。

修改creatOrder()方法添加注解 @GlobalTransactional 

   @GlobalTransactional
    @Override
    public Boolean createOrder(Long pid) {
        ProductBase productBase = productServiceClient.getProductBase(pid);
        OrderBase orderBase=new OrderBase();
        orderBase.setNumber(2);
        orderBase.setPId(productBase.getId());
        orderBase.setPName(productBase.getName());
        orderBase.setPPrice(productBase.getPrice());
        orderBase.setUserName("超超");
        this.save(orderBase);
        productServiceClient.productUpdateStock(orderBase.getPId());
        return Boolean.TRUE;
    }

 在服务调用方order-service中添加数据源代理配置

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

 product-base 中加入两条数据

 

然后分别启动两个微服务,在nacos界面服务列表可以看到 服务都注册成功了

 浏览器或者swagger执行

http://localhost:18080/order/1

可以看到执行成功

 

 

 订单表添加成功,库存也减了。然后任务增加一个异常测试一下事务的回滚。

 执行后可以看到订单和库存都没有成功。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值