ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Springcloud基础知识(18)- Spring Cloud Alibaba Seata (四) | Nacos+Seata+Openfeign 分布式事务实例(库存服务)

2022-08-01 22:02:17  阅读:171  来源: 互联网

标签:Seata Openfeign Springcloud org nacos spring import com cloud



我们以电商系统为例,来演示下业务系统如何整合 Seata。

在电商系统中,用户下单购买一件商品,简化为 3 个服务提供支持:

    Order(订单服务):创建和修改订单。
    Storage(库存服务):对指定的商品扣除仓库库存。
    Account(账户服务) :从用户帐户中扣除商品金额。

当用户从这个电商网站购买了一件商品后,其服务调用步骤如下:

    (1) 调用 Order 服务,创建一条订单数据,订单状态为 “未完成”;
    (2) 调用 Storage 服务,扣减商品库存;
    (3) 调用 Account 服务,从用户账户中扣除商品金额;
    (4) 调用 Order 服务,将订单状态修改为 “已完成”。  


本文使用 “Springcloud基础知识(11)- Spring Cloud Alibaba Nacos (一) | Nacos 简介、服务注册中心” 里的 Nacos 2.1.0 作为 Seata 的注册和配置中心,设置 Nacos 运行在 8848 端口上。

使用 “Springcloud基础知识(15)- Spring Cloud Alibaba Seata (一) | Seata 简介、事务模式、Seata Server” 里的 Seata Server 1.4.2,设置 Seata Server 运行在 8092 端口上。  

在 “Springcloud基础知识(17)- Spring Cloud Alibaba Seata (三) | 配置 db 存储模式、整合 Nacos” 里 SpringcloudDemo05 项目基础上,创建 SeataStorage 子模块。


1. 创建数据库

    在 MariaDB (MySQL) 中,创建一个名为 seata_storage 的数据库实例,并在该数据库内执行以下 SQL。

 1         DROP TABLE IF EXISTS `tbl_storages`;
 2         CREATE TABLE `tbl_storages` (
 3             `id` bigint NOT NULL AUTO_INCREMENT,
 4             `product_id` bigint DEFAULT NULL COMMENT 'product id',
 5             `total` int DEFAULT NULL COMMENT 'total inventory',
 6             `used` int DEFAULT NULL COMMENT 'used inventory',
 7             `residue` int DEFAULT NULL COMMENT 'remaining inventory',
 8             PRIMARY KEY (`id`)
 9         ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
10 
11         INSERT INTO `tbl_storages` VALUES ('1', '1', '100', '0', '100');
12 
13         DROP TABLE IF EXISTS `undo_log`;
14         CREATE TABLE `undo_log` (
15             `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
16             `xid` varchar(128) NOT NULL COMMENT 'global transaction id',
17             `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
18             `rollback_info` longblob NOT NULL COMMENT 'rollback info',
19             `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
20             `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
21             `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
22             UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
23         ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';


    执行 SQL 后,数据库 `tbl_storages` 表显示如下:

id product_id total used residue
1 1 100 0 100

 


2. 创建 Maven 模块

    选择左上的项目列表中的 SpringcloudDemo05,点击鼠标右键,选择 New -> Module 进入 New Module 页面:

        Maven -> Project SDK: 1.8 -> Check "Create from archtype" -> select "org.apache.maven.archtypes:maven-archtype-quickstart" -> Next

            Name: SeataStorage
            GroupId: com.example
            ArtifactId: SeataStorage

        -> Finish


3. 修改 pom.xml,内容如下

  1     <?xml version="1.0" encoding="UTF-8"?>
  2     <project xmlns="http://maven.apache.org/POM/4.0.0"
  3             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6         <parent>
  7             <artifactId>SpringcloudDemo05</artifactId>
  8             <groupId>com.example</groupId>
  9             <version>1.0-SNAPSHOT</version>
 10         </parent>
 11         <modelVersion>4.0.0</modelVersion>
 12 
 13         <artifactId>SeataStorage</artifactId>
 14 
 15         <name>SeataStorage</name>
 16         <!-- FIXME change it to the project's website -->
 17         <url>http://www.example.com</url>
 18 
 19         <properties>
 20             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 21             <maven.compiler.source>1.8</maven.compiler.source>
 22             <maven.compiler.target>1.8</maven.compiler.target>
 23             <maven.install.skip>true</maven.install.skip>
 24         </properties>
 25 
 26         <dependencies>
 27             <dependency>
 28                 <groupId>junit</groupId>
 29                 <artifactId>junit</artifactId>
 30                 <scope>test</scope>
 31             </dependency>
 32 
 33             <dependency>
 34                 <groupId>org.springframework.boot</groupId>
 35                 <artifactId>spring-boot-starter-web</artifactId>
 36             </dependency>        
 37             <dependency>
 38                 <groupId>org.springframework.boot</groupId>
 39                 <artifactId>spring-boot-starter-test</artifactId>
 40                 <scope>test</scope>
 41             </dependency>
 42 
 43             <!-- JDBC -->
 44             <dependency>
 45                 <groupId>org.springframework.boot</groupId>
 46                 <artifactId>spring-boot-starter-data-jdbc</artifactId>
 47             </dependency>
 48             <!-- Druid -->
 49             <dependency>
 50                 <groupId>com.alibaba</groupId>
 51                 <artifactId>druid</artifactId>
 52                 <version>1.2.8</version>
 53             </dependency>
 54 
 55             <!-- MariaDB -->
 56             <dependency>
 57                 <groupId>org.mariadb.jdbc</groupId>
 58                 <artifactId>mariadb-java-client</artifactId>
 59                 <version>${mariadb.version}</version>
 60             </dependency>
 61             <!-- MyBatis -->
 62             <dependency>
 63                 <groupId>org.mybatis.spring.boot</groupId>
 64                 <artifactId>mybatis-spring-boot-starter</artifactId>
 65                 <version>${mybatis.version}</version>
 66             </dependency>                
 67             <dependency>
 68                 <groupId>org.projectlombok</groupId>
 69                 <artifactId>lombok</artifactId>
 70                 <version>${lombok.version}</version>
 71             </dependency>
 72 
 73             <!-- nacos -->
 74             <dependency>
 75                 <groupId>com.alibaba.cloud</groupId>
 76                 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 77             </dependency>
 78             <dependency>
 79                 <groupId>com.alibaba.cloud</groupId>
 80                 <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 81             </dependency>
 82 
 83             <!-- seata -->
 84             <dependency>
 85                 <groupId>com.alibaba.cloud</groupId>
 86                 <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
 87                 <!-- Spring cloud 2021.1 自动导入的 seata 版本是 1.3.0 -->
 88                 <exclusions>
 89                     <exclusion>
 90                         <groupId>io.seata</groupId>
 91                         <artifactId>seata-spring-boot-starter</artifactId>
 92                     </exclusion>
 93                 </exclusions>
 94             </dependency>
 95             <dependency>
 96                 <groupId>io.seata</groupId>
 97                 <artifactId>seata-spring-boot-starter</artifactId>
 98                 <version>1.4.2</version>
 99             </dependency>
100 
101             <!-- OpenFeign -->
102             <!--
103             <dependency>
104                 <groupId>org.springframework.cloud</groupId>
105                 <artifactId>spring-cloud-starter-openfeign</artifactId>
106             </dependency>
107             <dependency>
108                 <groupId>org.springframework.cloud</groupId>
109                 <artifactId>spring-cloud-loadbalancer</artifactId>
110             </dependency> -->
111 
112         </dependencies>
113 
114     </project>


    注:这里我们用 seata 1.4.2 版本替换自动导入的 seata 1.3.0 版本,是因为下文需要用到 seata 1.4.2 的导入单个 dataId 配置的功能。


4. 配置文件

    1) 访问 Nacos 页面修改 seataClient.properties

        浏览器访问 http://localhost:8848/nacos/, 输入登录名和密码(默认 nacos/nacos),点击提交按钮,跳转到 Nacos Server 控制台页面。

        在 Nacos Server 控制台的 “配置管理” 下的 “配置列表” 中,创建或修改如下配置。

1             Data ID: seataClient.properties
2             Group:   SEATA_GROUP
3             配置格式: Properties
4             配置内容:
5 
6                 service.vgroupMapping.default_tx_group=default
7                 service.vgroupMapping.service-storage-group=default
8                 service.default.grouplist=127.0.0.1:8092


        注:可以把这两条内容直接加入到 seataServer.properties,无需新创建 seataClient.properties。这里分开放置 server 和 client 的配置,可以避免混淆两者的配置。

    2) 创建 src/main/resources/application.yml 文件

 1         server:
 2             port: 5001  # 端口号
 3 
 4         spring:
 5             application:
 6                 name: seata-storage-5001  # 服务名
 7             datasource: # 数据源配置
 8                 driver-class-name: org.mariadb.jdbc.Driver
 9                 name: seata_storage
10                 url: jdbc:mysql://127.0.0.1:3306/seata_storage?rewriteBatchedStatements=true
11                 username: nacos
12                 password: nacos
13             cloud:
14                 nacos:
15                     discovery:
16                         server-addr: 127.0.0.1:8848
17                         namespace:  # 留空表示使用 public
18                         group: SEATA_GROUP
19                         username: nacos
20                         password: nacos
21                     config:
22                         server-addr: ${spring.cloud.nacos.discovery.server-addr}
23                         context-path: /nacos
24                         namespace:   # 留空表示使用 public
25                         username: ${spring.cloud.nacos.discovery.username}
26                         password: ${spring.cloud.nacos.discovery.password}
27 
28         mybatis:
29             mapper-locations: classpath:mapper/*.xml
30 
31         seata:
32             #enabled: true
33             application-id: ${spring.application.name}
34             tx-service-group: service-storage-group
35             registry:
36                 type: nacos
37                 nacos:
38                     server-addr: ${spring.cloud.nacos.discovery.server-addr}
39                     application: seata-server
40                     group: ${spring.cloud.nacos.discovery.group}
41                     namespace: ${spring.cloud.nacos.discovery.namespace}
42                     username: ${spring.cloud.nacos.discovery.username}
43                     password: ${spring.cloud.nacos.discovery.password}
44             config:
45                 type: nacos
46                 nacos:
47                     server-addr:  ${spring.cloud.nacos.discovery.server-addr}
48                     group: ${spring.cloud.nacos.discovery.group}
49                     namespace: ${spring.cloud.nacos.discovery.namespace}
50                     username: ${spring.cloud.nacos.discovery.username}
51                     password: ${spring.cloud.nacos.discovery.password}
52                     dataId: seataClient.properties


5. 数据库配置

    1) 配置 Druid

        创建 src/main/java/com/example/config/DruidDataSourceConfig.java 文件

 1         package com.example.config;
 2 
 3         import javax.sql.DataSource;
 4         import java.sql.SQLException;
 5 
 6         import com.alibaba.druid.pool.DruidDataSource;
 7         import org.springframework.boot.context.properties.ConfigurationProperties;
 8         import org.springframework.context.annotation.Bean;
 9         import org.springframework.context.annotation.Configuration;
10         import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
11 
12         @Configuration
13         public class DruidDataSourceConfig implements WebMvcConfigurer {
14 
15             @ConfigurationProperties("spring.datasource")
16             @Bean
17             public DataSource dataSource() throws SQLException {
18                 DruidDataSource druidDataSource = new DruidDataSource();
19                 return druidDataSource;
20             }
21         }


    2) 实体类

        创建 src/main/java/com/example/entity/Storage.java 文件

 1         package com.example.entity;
 2 
 3         import lombok.Data;
 4         import lombok.NoArgsConstructor;
 5         import lombok.experimental.Accessors;
 6         import java.io.Serializable;
 7 
 8         @NoArgsConstructor // 无参构造函数
 9         @Data // 提供类的 get、set、equals、hashCode、canEqual、toString 方法
10         @Accessors(chain = true)
11         public class Storage implements Serializable {
12             private Long id;
13             private Long productId;
14             private Integer total;
15             private Integer used;
16             private Integer residue;
17         }


    3) Mybatis Mapper

        (1) 创建 src/main/java/com/example/mapper/StorageMapper.java 文件

 1             package com.example.mapper;
 2 
 3             import com.example.entity.Storage;
 4             import org.apache.ibatis.annotations.Mapper;
 5 
 6             @Mapper
 7             public interface StorageMapper {
 8 
 9                 Storage selectByProductId(Long productId);
10 
11                 int decrease(Storage storage);
12             }


        (2) 创建 src/main/resources/mapper/StorageMapper.xml 文件

 1             <?xml version="1.0" encoding="UTF-8"?>
 2             <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 3                                     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 4             <mapper namespace="com.example.mapper.StorageMapper">
 5                 <resultMap id="BaseResultMap" type="com.example.entity.Storage">
 6                     <id column="id" jdbcType="BIGINT" property="id"/>
 7                     <result column="product_id" jdbcType="BIGINT" property="productId"/>
 8                     <result column="total" jdbcType="INTEGER" property="total"/>
 9                     <result column="used" jdbcType="INTEGER" property="used"/>
10                     <result column="residue" jdbcType="INTEGER" property="residue"/>
11                 </resultMap>
12                 <sql id="Base_Column_List">
13                     id, product_id, total, used, residue
14                 </sql>
15                 <update id="decrease" parameterType="com.example.entity.Storage">
16                     UPDATE tbl_storages
17                     <set>
18                         <if test="total != null">
19                             total = #{total,jdbcType=INTEGER},
20                         </if>
21                         <if test="used != null">
22                             used = #{used,jdbcType=INTEGER},
23                         </if>
24                         <if test="residue != null">
25                             residue = #{residue,jdbcType=INTEGER},
26                         </if>
27                     </set>
28                     WHERE product_id = #{productId,jdbcType=BIGINT}
29                 </update>
30                 <select id="selectByProductId" parameterType="java.lang.Long" resultMap="BaseResultMap">
31                     SELECT
32                     <include refid="Base_Column_List"/>
33                     FROM tbl_storages
34                     WHERE product_id = #{productId,jdbcType=BIGINT}
35                 </select>
36             </mapper>


6. 业务操作

    1) 创建 src/main/java/com/example/service/StorageService.java 文件

1         package com.example.service;
2 
3         public interface StorageService {
4 
5             int decrease(Long productId, Integer count);
6 
7         }


    2) 创建 src/main/java/com/example/service/StorageServiceImpl.java 文件

 1         package com.example.service;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4 
 5         import com.example.entity.Storage;
 6         import com.example.mapper.StorageMapper;
 7         import org.springframework.stereotype.Service;
 8 
 9         @Service
10         public class StorageServiceImpl implements StorageService {
11             @Autowired
12             private StorageMapper storageMapper;
13 
14             @Override
15             public int decrease(Long productId, Integer count) {
16 
17                 Storage storage = storageMapper.selectByProductId(productId);
18                 System.out.println("StorageServiceImpl -> decrease(): storage = " + storage);
19 
20                 if (storage != null && storage.getResidue().intValue() >= count.intValue()) {
21 
22                     Storage storage2 = new Storage();
23                     storage2.setProductId(productId);
24                     storage.setUsed(storage.getUsed() + count);
25                     storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
26                     int ret = storageMapper.decrease(storage);
27                     System.out.println("StorageServiceImpl -> decrease(): ret = " + ret);
28                     return ret;
29                 } else {
30 
31                     System.out.println("StorageServiceImpl -> decrease(): Insufficient Balance");
32                     throw new RuntimeException("StorageServiceImpl - Insufficient Balance");
33                 }
34             }
35         }


    3) 创建 src/main/java/com/example/controller/StorageController.java 文件

 1         package com.example.controller;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;       
 4         import org.springframework.beans.factory.annotation.Value;
 5         import org.springframework.web.bind.annotation.PostMapping;
 6         import org.springframework.web.bind.annotation.RequestParam;
 7         import org.springframework.web.bind.annotation.RestController;
 8         import com.example.service.StorageService;
 9 
10         @RestController
11         public class StorageController {
12             @Autowired
13             private StorageService storageService;
14 
15             @Value("${server.port}")
16             private String serverPort;
17 
18             @PostMapping(value = "/storage/decrease")
19             public int decrease(@RequestParam("productId") Long productId,            
20                                 @RequestParam("count") Integer count) {
21 
22                 return storageService.decrease(productId, count);
23 
24             }
25         }


    4) 修改 src/main/java/com/example/App.java 文件

 1         package com.example;
 2 
 3         import org.springframework.boot.SpringApplication;
 4         import org.springframework.boot.autoconfigure.SpringBootApplication;
 5         import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 6         import org.springframework.cloud.openfeign.EnableFeignClients;
 7 
 8         @EnableDiscoveryClient
 9         @EnableFeignClients
10         @SpringBootApplication(scanBasePackages = "com.example")
11         public class App {
12             public static void main(String[] args) {
13                 SpringApplication.run(App.class, args);
14             }
15         }

 

 

7. 打包运行

    菜单 Run -> Edit Configurations (或工具条上选择) —> 进入 Run/Debug Configurations 页面 -> Click "+" add new configuration -> Select "Maven":

        Working directory: SeataStorage 所在路径
        Command line: clean package

    -> Apply / OK

    Click Run "SeataStorage [clean, package]" ,jar 包生成在目录 target/ 里

        SeataStorage-1.0-SNAPSHOT.jar
        SeataStorage-1.0-SNAPSHOT.jar.original

    打开 cmd 命令行窗口,进入 SeataStorage 模块目录,运行如下命令:

        ...\SpringcloudDemo05\SeataStorage>java -jar target\SeataStorage-1.0-SNAPSHOT.jar

    显示如下:

 1        ...
 2 
 3         INFO 29796 --- [           main] com.example.App                          : Started App in 3.527 seconds (JVM running for 3.906)
 4         INFO 29796 --- [eoutChecker_2_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.0.2:8092
 5         INFO 29796 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.0.2:8092
 6         INFO 29796 --- [eoutChecker_2_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:192.168.0.2:8092,msg:< RegisterRMRequest{resourceIds='null', applicationId='seata-storage-5001', transactionServiceGroup='service-storage-group'} >
 7         INFO 29796 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:TMROLE,address:192.168.0.2:8092,msg:< RegisterTMRequest{applicationId='seata-storage-5001', transactionServiceGroup='service-storage-group'} >
 8         INFO 29796 --- [eoutChecker_2_1] i.s.c.rpc.netty.RmNettyRemotingClient    : register RM success. client version:1.4.2, server version:1.4.2,channel:[id: 0x71db9d47, L:/192.168.0.2:49789 - R:/192.168.0.2:8092]
 9         INFO 29796 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient    : register TM success. client version:1.4.2, server version:1.4.2,channel:[id: 0xe1c5c21b, L:/192.168.0.2:49788 - R:/192.168.0.2:8092]
10         INFO 29796 --- [eoutChecker_2_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 130 ms, version:1.4.2,role:RMROLE,channel:[id: 0x71db9d47, L:/192.168.0.2:49789 - R:/192.168.0.2:8092]
11         INFO 29796 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 130 ms, version:1.4.2,role:TMROLE,channel:[id: 0xe1c5c21b, L:/192.168.0.2:49788 - R:/192.168.0.2:8092]


        注:使用 spring-cloud-starter-alibaba-seata 或 seata-spring-boot-starter 的 seata 客户端默认是开启状态 (可以设置 seata.enabled=false 来关闭)。

            seata 客户端里包含了一个全局事务扫描器 (GlobalTransactionScanner),seata 客户端运行后(30 秒左右)GlobalTransactionScanner 会调用初始化功能,使用 netty 连接 Seata 服务端。

            从 log 可以看出 SeataStorage 成功连接到了 Seata Server (192.168.0.2:8092),192.168.0.2 是本地主机的内网地址。


标签:Seata,Openfeign,Springcloud,org,nacos,spring,import,com,cloud
来源: https://www.cnblogs.com/tkuang/p/16541980.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有