程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

113.SpringCloud(高级四):SpringCloud Alibaba之Seata

发布于2021-05-29 22:29     阅读(654)     评论(0)     点赞(8)     收藏(3)


目录

一、分布式带来的问题

二、Seata的基本了解

1.什么是Seata

2.基本概念

(1)XID:全局唯一的事务ID

(2)TC:事务协调器

(3)TM:事务管理器

(4)RM:资源管理器

3.处理过程(重要)

三、运行Seata项目

1.下载

2.修改相关配置,安装seata-server

(1)备份配置文件file.conf,registry.conf,并修改

(2)创建数据库,并建表

(3)启动我们本地的nacos

(4)启动seata

3.编写订单

(1)新建项目

(2)编辑pom.xml

(3)编辑application.yml

(4)添加file.conf

(5)添加registry.conf

(6)主启动类

(7)domain层代码

(8)dao层代码

(9)service层代码

(10)controller层

(11)配置层

4.编写仓储模块

(1)编写pom.xml

(2)编写配置文件application.yml

(3)编写file.conf,registry.conf

(4)编写主启动类

(5)编写domain层

(6)编写dao层

(7)编写service层

(8)编写controller层

(9)编写配置层

5.编写账户模块

(1)编写pom.xml

(2)编写配置文件application.yml

(3)编写file.conf和registry.conf

(4)编写domain层

(5)编写dao层

(6)编写service层

(7)编写controller层

(8)编写配置层

(9)编写主启动类

6.测试

四、seata底层原理(重点)

1.分布式事务执行流程

2.如果做到对业务无入侵的分布式事务管理

(1)两个阶段

(2)解释两个阶段

五、打赏请求


一、分布式带来的问题

单体应用被拆分成微服务应用,原本三个模块被拆分成三个独立的应用,分别使用三个独立的数据源。

业务操作需要调用3个服务来完成,此时每个服务内部的数据一致性由本地保证,但是全局的数据一致性没法保证

 

二、Seata的基本了解

1.什么是Seata

Seata是一款开源的分布式解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务

 

官网:http://seata.io/zh-cn/

 

2.基本概念

(1)XID:全局唯一的事务ID

(2)TC:事务协调器

维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚

(3)TM:事务管理器

控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议

(4)RM:资源管理器

控制分支事务,负责分支注册、状态汇报,并接受事务协调器的指令,驱动分支(本地)事务的提交和回滚

 

3.处理过程(重要)

(1)TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID

(2)XID在微服务调用链路的上下文中传播

(3)RM向TC注册分支事务,将其纳入XID对应全局事务的管辖

(4)TM向TC发起针对XID的全局提交或回滚决议

(5)TC调度XID下管辖的全部分支事务完成提交或回滚请求

 

三、运行Seata项目

1.下载

整个项目可以在码云上down:https://gitee.com/cxy-xupeng/spring-cloud.git

下载地址:http://seata.io/zh-cn/blog/download.html

我下载的是0.9版本的,新版本的没走通。。。

 

2.修改相关配置,安装seata-server

(1)备份配置文件file.conf,registry.conf,并修改

安装好后找到file.conf配置文件,做一个备份先(养成好习惯):

 

修改file.conf:

注意,驱动的话,mysql5保持原来的不变,mysql8需要加一个cj:

registry.conf:

 

(2)创建数据库,并建表

建表语句在sb_store.sql里有:

因为是分布式服务,所以我们三个微服务会调用三个数据库,我们需要先来新建一下业务数据库:

再来建立对应的表:

订单库:

  1. CREATE TABLE `t_order` (
  2. `id` bigint NOT NULL AUTO_INCREMENT,
  3. `user_id` bigint DEFAULT NULL COMMENT '用户id',
  4. `product_id` bigint DEFAULT NULL COMMENT '产品id',
  5. `count` int DEFAULT NULL COMMENT '数量',
  6. `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  7. `status` int DEFAULT NULL COMMENT '订单状态,0:创建中,1:已完结',
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

仓储库:

  1. CREATE TABLE `t_storage` (
  2. `id` bigint NOT NULL AUTO_INCREMENT,
  3. `product_id` bigint DEFAULT NULL COMMENT '产品id',
  4. `total` int DEFAULT NULL COMMENT '总库存',
  5. `used` int DEFAULT NULL COMMENT '已用库存',
  6. `residue` int DEFAULT NULL COMMENT '剩余库存',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

账号库:

  1. CREATE TABLE `t_account` (
  2. `id` bigint NOT NULL AUTO_INCREMENT,
  3. `user_id` bigint DEFAULT NULL COMMENT '用户id',
  4. `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  5. `used` decimal(10,0) DEFAULT NULL COMMENT '已用额度',
  6. `residue` decimal(10,0) DEFAULT NULL COMMENT '剩余额度',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

新建的三个库,每个库都需要回滚日志的表,该表我们也有:

执行好后,查看一下我们的库以及对应的表:


 

(3)启动我们本地的nacos

(4)启动seata

双击批处理文件:

如果你是mysql5,应该可以直接启动成功。

 

如果你是mysql8,会报错的,我们还需要修改一些东西:

来到lib目录下,找到mysql-connector-java的jar包,它自带的是5.X的,我们用8.X的替换

然后重新双击seata-server.bat即可:

 

seata启动成功:

 

注:如果你有闪退的情况,创建一个logs目录

 

 

3.编写订单

先来看一下目录结构:

(1)新建项目

(2)编辑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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>cloud</artifactId>
  7. <groupId>com.xupeng.springcloud</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>seata-order-service2001</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.alibaba.cloud</groupId>
  15. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  16. <exclusions>
  17. <exclusion>
  18. <artifactId>seata-all</artifactId>
  19. <groupId>io.seata</groupId>
  20. </exclusion>
  21. </exclusions>
  22. </dependency>
  23. <dependency>
  24. <groupId>io.seata</groupId>
  25. <artifactId>seata-all</artifactId>
  26. <version>0.9.0</version>
  27. </dependency>
  28. <!--feign-->
  29. <dependency>
  30. <groupId>org.springframework.cloud</groupId>
  31. <artifactId>spring-cloud-starter-openfeign</artifactId>
  32. </dependency>
  33. <!--alibaba nacos-->
  34. <dependency>
  35. <groupId>com.alibaba.cloud</groupId>
  36. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.xupeng.springcloud</groupId>
  40. <artifactId>cloud-api-commons</artifactId>
  41. <version>${project.version}</version>
  42. </dependency>
  43. <dependency>
  44. <groupId>mysql</groupId>
  45. <artifactId>mysql-connector-java</artifactId>
  46. <version>8.0.22</version>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.springframework.boot</groupId>
  50. <artifactId>spring-boot-starter-web</artifactId>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-actuator</artifactId>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-devtools</artifactId>
  59. <scope>runtime</scope>
  60. <optional>true</optional>
  61. </dependency>
  62. <dependency>
  63. <groupId>org.projectlombok</groupId>
  64. <artifactId>lombok</artifactId>
  65. <optional>true</optional>
  66. </dependency>
  67. <dependency>
  68. <groupId>org.mybatis.spring.boot</groupId>
  69. <artifactId>mybatis-spring-boot-starter</artifactId>
  70. <version>2.0.1</version>
  71. </dependency>
  72. <dependency>
  73. <groupId>org.springframework.boot</groupId>
  74. <artifactId>spring-boot-starter-test</artifactId>
  75. <scope>test</scope>
  76. </dependency>
  77. <dependency>
  78. <groupId>org.mybatis</groupId>
  79. <artifactId>mybatis</artifactId>
  80. <version>3.4.6</version>
  81. </dependency>
  82. <dependency>
  83. <groupId>org.mybatis</groupId>
  84. <artifactId>mybatis-spring</artifactId>
  85. <version>1.3.2</version>
  86. </dependency>
  87. </dependencies>
  88. </project>

(3)编辑application.yml

如果你是mysql5的话,驱动需要改一下。

注意:tx-service-group属性对应的值就是我们seata中file.conf里面配置的,要保持对应

  1. server:
  2. port: 2001
  3. spring:
  4. application:
  5. name: seata-order-service
  6. cloud:
  7. alibaba:
  8. seata:
  9. tx-service-group: xupeng_tx_group
  10. nacos:
  11. discovery:
  12. server-addr: localhost:8848
  13. datasource:
  14. driver-class-name: com.mysql.cj.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/seata_order?serverTimezone=UTC
  16. username: root
  17. password: xp880000
  18. feign:
  19. hystrix:
  20. enabled: false
  21. logging:
  22. level:
  23. io:
  24. seata: info
  25. mybatis:
  26. mapperLocations: classpath:mapper/*.xml

(4)添加file.conf

我们把seata中的file.conf复制过来,此处需要修改一个地方:

标记的xupeng_tx_group,要和application.yml以及seata中的file.conf保持一致(这个值就是我们自定义的)

(5)添加registry.conf

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "nacos"
  4. nacos {
  5. serverAddr = "localhost:8848"
  6. namespace = ""
  7. cluster = "default"
  8. }
  9. eureka {
  10. serviceUrl = "http://localhost:8761/eureka"
  11. application = "default"
  12. weight = "1"
  13. }
  14. redis {
  15. serverAddr = "localhost:6379"
  16. db = "0"
  17. }
  18. zk {
  19. cluster = "default"
  20. serverAddr = "127.0.0.1:2181"
  21. session.timeout = 6000
  22. connect.timeout = 2000
  23. }
  24. consul {
  25. cluster = "default"
  26. serverAddr = "127.0.0.1:8500"
  27. }
  28. etcd3 {
  29. cluster = "default"
  30. serverAddr = "http://localhost:2379"
  31. }
  32. sofa {
  33. serverAddr = "127.0.0.1:9603"
  34. application = "default"
  35. region = "DEFAULT_ZONE"
  36. datacenter = "DefaultDataCenter"
  37. cluster = "default"
  38. group = "SEATA_GROUP"
  39. addressWaitTime = "3000"
  40. }
  41. file {
  42. name = "file.conf"
  43. }
  44. }
  45. config {
  46. # file、nacos 、apollo、zk、consul、etcd3
  47. type = "file"
  48. nacos {
  49. serverAddr = "localhost:8848"
  50. namespace = ""
  51. }
  52. consul {
  53. serverAddr = "127.0.0.1:8500"
  54. }
  55. apollo {
  56. app.id = "seata-server"
  57. apollo.meta = "http://192.168.1.204:8801"
  58. }
  59. zk {
  60. serverAddr = "127.0.0.1:2181"
  61. session.timeout = 6000
  62. connect.timeout = 2000
  63. }
  64. etcd3 {
  65. serverAddr = "http://localhost:2379"
  66. }
  67. file {
  68. name = "file.conf"
  69. }
  70. }

(6)主启动类

  1. @EnableDiscoveryClient
  2. @EnableFeignClients
  3. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
  4. public class SeataOrderMain2001 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(SeataOrderMain2001.class, args);
  7. }
  8. }

(7)domain层代码

Order:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Order {
  5. private Long id;
  6. private Long userId;
  7. private Long productId;
  8. private Integer count;
  9. private BigDecimal money;
  10. //订单状态:0-创建中,1-已完结
  11. private Integer status;
  12. }

CommonResult:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T> {
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code,String message){
  9. this(code,message,null);
  10. }
  11. }

(8)dao层代码

OrderDao:

  1. @Mapper
  2. public interface OrderDao {
  3. //新建订单
  4. void create(Order order);
  5. //修改订单状态
  6. void update(@Param("userId") Long userId, @Param("status") Integer status);
  7. }

OrderMapper.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.xupeng.springcloud.dao.OrderDao">
  4. <resultMap id="BaseResultMap" type="com.xupeng.springcloud.domain.Order">
  5. <id column="id" property="id" jdbcType="BIGINT"></id>
  6. <result column="user_id" property="userId" jdbcType="BIGINT"></result>
  7. <result column="product_id" property="productId" jdbcType="BIGINT"></result>
  8. <result column="count" property="count" jdbcType="INTEGER"></result>
  9. <result column="money" property="money" jdbcType="DECIMAL"></result>
  10. <result column="status" property="status" jdbcType="INTEGER"></result>
  11. </resultMap>
  12. <insert id="create">
  13. insert into t_order (id,user_id,product_id,count,money,status) values
  14. (null,#{user_id},#{product_id},#{count},#{money},0);
  15. </insert>
  16. <update id="update">
  17. update t_order set status = 1 where user_id=#{user_id} and status = #{status};
  18. </update>
  19. </mapper>

(9)service层代码

AccountService:

  1. @FeignClient(value = "seata-account-service")
  2. public interface AccountService {
  3. @PostMapping(value = "/account/decrease")
  4. CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
  5. }

OrderService:

  1. public interface OrderService {
  2. void create(Order order);
  3. }

StorageService:

  1. @FeignClient(value = "seata-storage-service")
  2. public interface StorageService {
  3. @PostMapping(value = "/storage/decrease")
  4. CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
  5. }

OrderServiceImpl:

  1. @Service
  2. @Slf4j
  3. public class OrderServiceImpl implements OrderService {
  4. @Resource
  5. private OrderDao orderDao;
  6. @Resource
  7. private AccountService accountService;
  8. @Resource
  9. private StorageService storageService;
  10. @Override
  11. @GlobalTransactional(name = "xupeng-create-order",rollbackFor = Exception.class)
  12. public void create(Order order) {
  13. log.info("---------开始新建订单");
  14. orderDao.create(order);
  15. log.info("---------订单微服务调用库存,做减法");
  16. storageService.decrease(order.getProductId(),order.getCount());
  17. log.info("---------订单微服务调用库存,做减法结束");
  18. log.info("---------订单微服务调用账户,做扣减");
  19. accountService.decrease(order.getUserId(),order.getMoney());
  20. log.info("---------订单微服务调用账户,做扣减结束");
  21. log.info("---------修改订单状态开始");
  22. orderDao.update(order.getUserId(),0);
  23. log.info("---------修改订单状态结束");
  24. log.info("---------开始新建订单结束");
  25. }
  26. }

(10)controller层

OrderController:

  1. @RestController
  2. public class OrderController {
  3. @Resource
  4. private OrderService orderService;
  5. @GetMapping("/order/create")
  6. public CommonResult create(Order order) {
  7. orderService.create(order);
  8. return new CommonResult(200, "订单创建成功");
  9. }
  10. }

(11)配置层

MyBatisConfig:

  1. @Configuration
  2. @MapperScan({"com.xupeng.springcloud.dao"})
  3. public class MyBatisConfig {
  4. }

DataSourceProxyConfig:

  1. @Configuration
  2. public class DataSourceProxyConfig {
  3. @Value("${mybatis.mapperLocations}")
  4. private String mapperLocations;
  5. @Bean
  6. @ConfigurationProperties(prefix = "spring.datasource")
  7. public DataSource druidDataSource() {
  8. return new DruidDataSource();
  9. }
  10. @Bean
  11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
  12. return new DataSourceProxy(dataSource);
  13. }
  14. @Bean
  15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
  16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
  18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
  19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
  20. return sqlSessionFactoryBean.getObject();
  21. }
  22. }

 

4.编写仓储模块

 

(1)编写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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>cloud</artifactId>
  7. <groupId>com.xupeng.springcloud</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>seata-storage-service2002</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.alibaba.cloud</groupId>
  15. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  16. <exclusions>
  17. <exclusion>
  18. <artifactId>seata-all</artifactId>
  19. <groupId>io.seata</groupId>
  20. </exclusion>
  21. </exclusions>
  22. </dependency>
  23. <dependency>
  24. <groupId>io.seata</groupId>
  25. <artifactId>seata-all</artifactId>
  26. <version>0.9.0</version>
  27. </dependency>
  28. <!--feign-->
  29. <dependency>
  30. <groupId>org.springframework.cloud</groupId>
  31. <artifactId>spring-cloud-starter-openfeign</artifactId>
  32. </dependency>
  33. <!--alibaba nacos-->
  34. <dependency>
  35. <groupId>com.alibaba.cloud</groupId>
  36. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.xupeng.springcloud</groupId>
  40. <artifactId>cloud-api-commons</artifactId>
  41. <version>${project.version}</version>
  42. </dependency>
  43. <dependency>
  44. <groupId>mysql</groupId>
  45. <artifactId>mysql-connector-java</artifactId>
  46. <version>8.0.22</version>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.springframework.boot</groupId>
  50. <artifactId>spring-boot-starter-web</artifactId>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-actuator</artifactId>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-devtools</artifactId>
  59. <scope>runtime</scope>
  60. <optional>true</optional>
  61. </dependency>
  62. <dependency>
  63. <groupId>org.projectlombok</groupId>
  64. <artifactId>lombok</artifactId>
  65. <optional>true</optional>
  66. </dependency>
  67. <dependency>
  68. <groupId>org.mybatis.spring.boot</groupId>
  69. <artifactId>mybatis-spring-boot-starter</artifactId>
  70. <version>2.0.1</version>
  71. </dependency>
  72. <dependency>
  73. <groupId>org.springframework.boot</groupId>
  74. <artifactId>spring-boot-starter-test</artifactId>
  75. <scope>test</scope>
  76. </dependency>
  77. <dependency>
  78. <groupId>org.mybatis</groupId>
  79. <artifactId>mybatis</artifactId>
  80. <version>3.4.6</version>
  81. </dependency>
  82. <dependency>
  83. <groupId>org.mybatis</groupId>
  84. <artifactId>mybatis-spring</artifactId>
  85. <version>1.3.2</version>
  86. </dependency>
  87. </dependencies>
  88. </project>

(2)编写配置文件application.yml

  1. server:
  2. port: 2002
  3. spring:
  4. application:
  5. name: seata-storage-service
  6. cloud:
  7. alibaba:
  8. seata:
  9. tx-service-group: xupeng_tx_group
  10. nacos:
  11. discovery:
  12. server-addr: localhost:8848
  13. datasource:
  14. driver-class-name: com.mysql.cj.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/seata_storage?serverTimezone=UTC
  16. username: root
  17. password: xp880000
  18. feign:
  19. hystrix:
  20. enabled: false
  21. logging:
  22. level:
  23. io:
  24. seata: info
  25. mybatis:
  26. mapperLocations: classpath:mapper/*.xml

(3)编写file.conf,registry.conf

同订单模块

(4)编写主启动类

SeataStorageServiceApplication2002:

  1. @EnableDiscoveryClient
  2. @EnableFeignClients
  3. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
  4. public class SeataStorageServiceApplication2002 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(SeataStorageServiceApplication2002.class, args);
  7. }
  8. }

(5)编写domain层

CommonResult:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T> {
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code,String message){
  9. this(code,message,null);
  10. }
  11. }

Storage:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Storage {
  5. private Long id;
  6. private Long productId;
  7. private Integer total;
  8. private Integer used;
  9. private Integer residue;
  10. }

(6)编写dao层

StorageDao:

  1. @Mapper
  2. public interface StorageDao {
  3. void decrease(@Param("productId") Long productId,@Param("count") Integer count);
  4. }

StorageMapper.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.xupeng.springcloud.dao.StorageDao">
  4. <resultMap id="BaseResultMap" type="com.xupeng.springcloud.domain.Storage">
  5. <id column="id" property="id" jdbcType="BIGINT"></id>
  6. <result column="product_id" property="productId" jdbcType="BIGINT"></result>
  7. <result column="total" property="total" jdbcType="INTEGER"></result>
  8. <result column="used" property="used" jdbcType="INTEGER"></result>
  9. <result column="residue" property="residue" jdbcType="INTEGER"></result>
  10. </resultMap>
  11. <update id="decrease">
  12. update t_storage set used = used + #{count},residue = residue - #{count} where product_id = #{productId}
  13. </update>
  14. </mapper>

(7)编写service层

StorageService:

  1. public interface StorageService {
  2. void decrease(Long productId, Integer count);
  3. }

StorageServiceImpl:

  1. @Service
  2. public class StorageServiceImpl implements StorageService {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
  4. @Resource
  5. private StorageDao storageDao;
  6. /**
  7. * 扣减库存
  8. * @param productId
  9. * @param count
  10. */
  11. @Override
  12. public void decrease(Long productId, Integer count) {
  13. LOGGER.info("---------开始扣减库存");
  14. storageDao.decrease(productId,count);
  15. LOGGER.info("---------扣减库存结束");
  16. }
  17. }

(8)编写controller层

StorageController:

  1. @RestController
  2. public class StorageController {
  3. @Autowired
  4. private StorageService storageService;
  5. /**
  6. * 扣减库存
  7. */
  8. @RequestMapping("/storage/decrease")
  9. public CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count")Integer count){
  10. storageService.decrease(productId, count);
  11. return new CommonResult(200,"扣减库存成功");
  12. }
  13. }

(9)编写配置层

MyBatisConfig和DataSourceProxyConfig同订单模块

 

5.编写账户模块

(1)编写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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>cloud</artifactId>
  7. <groupId>com.xupeng.springcloud</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>seata-account-service2003</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.alibaba.cloud</groupId>
  15. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  16. <exclusions>
  17. <exclusion>
  18. <artifactId>seata-all</artifactId>
  19. <groupId>io.seata</groupId>
  20. </exclusion>
  21. </exclusions>
  22. </dependency>
  23. <dependency>
  24. <groupId>io.seata</groupId>
  25. <artifactId>seata-all</artifactId>
  26. <version>0.9.0</version>
  27. </dependency>
  28. <!--feign-->
  29. <dependency>
  30. <groupId>org.springframework.cloud</groupId>
  31. <artifactId>spring-cloud-starter-openfeign</artifactId>
  32. </dependency>
  33. <!--alibaba nacos-->
  34. <dependency>
  35. <groupId>com.alibaba.cloud</groupId>
  36. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.xupeng.springcloud</groupId>
  40. <artifactId>cloud-api-commons</artifactId>
  41. <version>${project.version}</version>
  42. </dependency>
  43. <dependency>
  44. <groupId>mysql</groupId>
  45. <artifactId>mysql-connector-java</artifactId>
  46. <version>8.0.22</version>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.springframework.boot</groupId>
  50. <artifactId>spring-boot-starter-web</artifactId>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-actuator</artifactId>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-devtools</artifactId>
  59. <scope>runtime</scope>
  60. <optional>true</optional>
  61. </dependency>
  62. <dependency>
  63. <groupId>org.projectlombok</groupId>
  64. <artifactId>lombok</artifactId>
  65. <optional>true</optional>
  66. </dependency>
  67. <dependency>
  68. <groupId>org.mybatis.spring.boot</groupId>
  69. <artifactId>mybatis-spring-boot-starter</artifactId>
  70. <version>2.0.1</version>
  71. </dependency>
  72. <dependency>
  73. <groupId>org.springframework.boot</groupId>
  74. <artifactId>spring-boot-starter-test</artifactId>
  75. <scope>test</scope>
  76. </dependency>
  77. <dependency>
  78. <groupId>org.mybatis</groupId>
  79. <artifactId>mybatis</artifactId>
  80. <version>3.4.6</version>
  81. </dependency>
  82. <dependency>
  83. <groupId>org.mybatis</groupId>
  84. <artifactId>mybatis-spring</artifactId>
  85. <version>1.3.2</version>
  86. </dependency>
  87. </dependencies>
  88. </project>

(2)编写配置文件application.yml

需要修改一下数据库名

  1. server:
  2. port: 2003
  3. spring:
  4. application:
  5. name: seata-account-service
  6. cloud:
  7. alibaba:
  8. seata:
  9. tx-service-group: xupeng_tx_group
  10. nacos:
  11. discovery:
  12. server-addr: localhost:8848
  13. datasource:
  14. driver-class-name: com.mysql.cj.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/seata_account?serverTimezone=UTC
  16. username: root
  17. password: xp880000
  18. feign:
  19. hystrix:
  20. enabled: false
  21. logging:
  22. level:
  23. io:
  24. seata: info
  25. mybatis:
  26. mapperLocations: classpath:mapper/*.xml

(3)编写file.conf和registry.conf

同订单模块

(4)编写domain层

Account:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Account {
  5. private Long id;
  6. private Long userId;
  7. private BigDecimal total;
  8. private BigDecimal used;
  9. private BigDecimal residue;
  10. }

CommonResult:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T> {
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code,String message){
  9. this(code,message,null);
  10. }
  11. }

(5)编写dao层

AccountDao:

  1. @Mapper
  2. public interface AccountDao {
  3. /**
  4. * 扣减账户余额
  5. */
  6. void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
  7. }

AccountMapper.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.xupeng.springcloud.dao.AccountDao">
  4. <resultMap id="BaseResultMap" type="com.xupeng.springcloud.domain.Account">
  5. <id column="id" property="id" jdbcType="BIGINT"></id>
  6. <result column="user_id" property="userId" jdbcType="BIGINT"></result>
  7. <result column="total" property="total" jdbcType="DECIMAL"></result>
  8. <result column="used" property="used" jdbcType="DECIMAL"></result>
  9. <result column="residue" property="residue" jdbcType="DECIMAL"></result>
  10. </resultMap>
  11. <update id="decrease">
  12. update t_account set residue = residue - #{money},used = used + #{money} where user_id = #{userId}
  13. </update>
  14. </mapper>

(6)编写service层

AccountService:

  1. public interface AccountService {
  2. void decrease(Long userId, BigDecimal money);
  3. }

AccountServiceImpl:

  1. @Service
  2. public class AccountServiceImpl implements AccountService {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
  4. @Resource
  5. private AccountDao accountDao;
  6. /**
  7. * 扣减账户余额
  8. */
  9. @Override
  10. public void decrease(Long userId, BigDecimal money) {
  11. LOGGER.info("---------开始扣减账户余额");
  12. // try {
  13. // TimeUnit.SECONDS.sleep(20);
  14. // } catch (InterruptedException e) {
  15. // e.printStackTrace();
  16. // }
  17. accountDao.decrease(userId, money);
  18. LOGGER.info("---------扣减库存账户余额");
  19. }
  20. }

(7)编写controller层

  1. @RestController
  2. public class AccountController {
  3. @Autowired
  4. private AccountService accountService;
  5. /**
  6. * 扣减账户余额
  7. */
  8. @RequestMapping("/account/decrease")
  9. public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {
  10. accountService.decrease(userId, money);
  11. return new CommonResult(200, "扣减账户余额成功");
  12. }
  13. }

(8)编写配置层

DataSourceProxyConfig和MyBatisConfig同上

(9)编写主启动类

SeataAccountServiceApplication2003:

  1. @EnableDiscoveryClient
  2. @EnableFeignClients
  3. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源自动配置
  4. public class SeataAccountServiceApplication2003 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(SeataAccountServiceApplication2003.class, args);
  7. }
  8. }

 

6.测试

这里的关键是:@GlobalTransactional

@GlobalTransactional注解,开启了全局事务,如果有哪一个模块出错,就会全部回滚

 

四、seata底层原理(重点)

1.分布式事务执行流程

TC相当于seata服务器,TM就是我们@GlobalTransactional注解的地方,RM就是那三个模块

  1. TM开启分布式事务(TM向TC注册全局事务记录)
  2. 按业务场景,编排数据库、服务等事务内资源(RM向TC会报资源准备状态)
  3. TM结束分布式事务,事务第一阶段结束(TM通知TC 提交/回滚事务)
  4. TC汇总事务信息,决定分布式事务是提交还是回滚
  5. TC通知所有RM提交/回滚资源,事务第二阶段结束

2.如果做到对业务无入侵的分布式事务管理

(1)两个阶段

  1. 第一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
  2. 第二阶段:提交异步化,非常快速完成。回滚通过第一阶段的回滚日志进行反向补偿

(2)解释两个阶段

在第一阶段,seata会拦截“业务sql”,

    *1)解析sql语义,找到业务sql要更新的业务数据,在业务数据被新更新之前,将其保存成“before image”

    *2)执行业务sql,更新业务数据

    *3)在业务数据更新后,将其保存成“after image”,最后生成行锁

以上操作都是在一个数据库事务内发生,保证了一阶段操作的原子性

 

第二阶段,如果“before image”和“after image”都正常,可以直接提交。

 

第二阶段,如果需要回滚:

    seata需要回滚一阶段已经执行的业务sql,还原业务数据(即将第一阶段第二步的数据还原)

回滚方式是用“before image”还原业务数据,但在还原数据前首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致,表示没有脏写,可以还原。如果不一致,代表有脏写,需要人工处理。

 

五、打赏请求

如果本篇博客对您有所帮助,打赏一点呗,谢谢了呢~

 

原文链接:https://blog.csdn.net/qq_40594696/article/details/117228564



所属网站分类: 技术文章 > 博客

作者:小光头吃饭不用愁

链接:http://www.javaheidong.com/blog/article/207907/3d18d9112d3e0ceddad7/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

8 0
收藏该文
已收藏

评论内容:(最多支持255个字符)