三、Seata 处理分布式事务


一、Seata 处理分布式事务


1. Seata 简介

  • Seata
  • Seata 是一款开源的 分布式事务 解决方案,致力于在微服务架构下,提供 高性能 和 简单易用 的 分布式事务服务。
  • 2019 年 1 月份蚂蚁金服 和 阿里巴巴共同开源的分布式事务解决方案。

2. 能干嘛

2.1 分布式事务处理过程(1ID + 3组件模型)
  • Transaction ID(XID 全局唯一的事务 ID)。
  • Transaction Coordinator(TC)。
    事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的 提交 或 回滚。
  • Transaction Manager(TM)。
    事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起 全局提交 或 全局回滚 的决议。
  • Resource Manager(RM)。
    资源管理器,控制分支事务,负责分支注册、状态汇报,并接收 事务协调器 的指令,驱动分支(本地)事务的 提交 和 回滚。
    在这里插入图片描述
  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  4. TM 向 TC 发起针对 XID 的全局 提交 或 回滚 决议。
  5. TC 调度 XID 下管辖的全部分支事务,完成 提交 或 回滚 请求。

3. Seata-Server 安装


3.1 下载

seata-server-0.9.0.zip


3.2 修改 file.conf

在这里插入图片描述
在这里插入图片描述


3.3 创建 store
  • seata-server-0.9.0\seata\conf\db_store.sql

3.4 修改 registry.conf
  • 指明注册中心为 Nacos,及修改 Nacos 连接信息。
    在这里插入图片描述

3.5 启动 seata-server.bat

在这里插入图片描述


4. 订单/库存/账户 业务数据库准备

  • 创建回滚日志表 seata-server-0.9.0\seata\conf\db_undo_log.sql
    在这里插入图片描述

4.1 订单库
# 创建数据库
CREATE DATABASE seata_order;
# 创建数据表
CREATE TABLE t_order (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
  `count` INT(11) DEFAULT NULL COMMENT '数量',
  `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
  `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' 
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

SELECT * FROM t_order;

4.2 库存库
# 创建数据库
CREATE DATABASE seata_storage;
# 创建数据表
CREATE TABLE t_storage (
 `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
 `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
 `total` INT(11) DEFAULT NULL COMMENT '总库存',
 `used` INT(11) DEFAULT NULL COMMENT '已用库存',
 `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');
 
SELECT * FROM t_storage;

4.3 账户库
# 创建数据库
CREATE DATABASE seata_account;
# 创建数据表
CREATE TABLE t_account (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
  `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)  
VALUES ('1', '1', '1000', '0', '1000');
 
SELECT * FROM t_account;

5. 订单/库存/账户 业务微服务准备


5.1 新建 Module seata-order-service2001

5.2 POM
<?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>cloud-2020</artifactId>
        <groupId>com.qs.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>seata-order-service2001</artifactId>

    <dependencies>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--mysql-druid-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <!--自定义API通用包-->
        <dependency>
            <groupId>com.qs.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

5.3 YML

在这里插入图片描述


server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        # 自定义事务组名称需要与seata-server中的对应
        tx-service-group: qs_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.137.155:3306/seata_order
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

5.4 主启动
@EnableDiscoveryClient
@EnableFeignClients
// 取消数据源的自动创建
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class OrderMain2001 {

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

5.5 MyBatisConfig
@Configuration
@MapperScan({"com.qs.springcloud.dao"})
public class MyBatisConfig {
}

5.6 DataSourceProxyConfig 使用 Seata 对数据源进行代理
@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }
}

5.7 Order
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    /**
     * 订单状态:0:创建中;1:已完结
     */
    private Integer status;
}

5.8 OrderDao
@Mapper
public interface OrderDao {

    /**
     * 创建订单
     */
    void create(Order order);

    /**
     * 修改订单金额
     */
    void update(@Param("userId") Long userId, @Param("status") Integer status);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.qs.springcloud.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.qs.springcloud.domain.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>

    <insert id="create">
        INSERT INTO `t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`)
        VALUES (NULL, #{userId}, #{productId}, #{count}, #{money}, 0)
    </insert>

    <update id="update">
        UPDATE `t_order`
        SET status = 1
        WHERE user_id = #{userId} AND status = #{status}
    </update>

</mapper>

5.9 IStorageService
@FeignClient(value = "seata-storage-service")
public interface IStorageService {

    /**
     * 扣减库存
     */
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}

5.10 IAccountService
@FeignClient(value = "seata-account-service")
public interface IAccountService {

    /**
     * 扣减账户余额
     */
//    @RequestMapping(value = "/account/decrease", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

5.11 IOrderService
public interface IOrderService {

    /**
     * 创建订单
     */
    void create(Order order);
}

5.12 OrderServiceImpl
  • @GlobalTransactional
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {

    @Resource
    private OrderDao orderDao;
    @Resource
    private IStorageService storageService;
    @Resource
    private IAccountService accountService;

    /**
     * 创建订单 -> 调用库存服务扣减库存 -> 调用账户服务扣减账户余额 -> 修改订单状态
     * 下订单 -> 减库存 -> 减余额 -> 改状态
     */
    @Override
    // 所有异常都回滚
    @GlobalTransactional(name = "qs-create-order", rollbackFor = Exception.class)
    public void create(Order order) {
        log.info("下订单 Start");
        orderDao.create(order);
        log.info("下订单 End");

        log.info("减库存 Start");
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("减库存 End");

        log.info("减余额 Start");
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("减余额 End");

        log.info("改状态 Start");
        orderDao.update(order.getId(), 1);
        log.info("改状态 End");
    }
}

5.13 OrderController
@RestController
public class OrderController {

    @Autowired
    private IOrderService orderService;

    @GetMapping("/order/create")
    public CommonResult create(Order order) {
        orderService.create(order);
        return CommonResult.successOfData("订单创建成功!");
    }
}

5.14 测试

6. AT 模式如何做到对业务的无侵入

  • AT 模式是 Seata 最主推的分布式事务解决方案,它是基于 XA 演进而来。
  • AT 模式是一种 无侵入 的 分布式事务 解决方案。
  • 在 AT 模式下,用户只需关注自己的 “业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段 提交 和 回滚 操作。

6.1 一阶段加载
  • 在一阶段,Seata 会拦截 “业务 SQL”。
  1. 解析 SQL 语义,找到 “业务 SQL” 要更新的业务数据,在业务数据被更新前,将其保存成 “before image”。
  2. 执行 “业务 SQL” 更新业务数据,在业务数据更新之后。
  3. 其保存成 “after image”,最后生成行锁。
  • 以上操作全部在一个 数据库事务 内完成,这样保证了一阶段操作的 原子性。
    在这里插入图片描述

6.2 二阶段提交
  • 二阶段如是顺利提交的话,因为 “业务 SQL” 在一阶段已经提交至数据库,所以 Seata 框架只需将 一阶段保存的 快照数据 和 行锁删掉,完成数据清理即可
    在这里插入图片描述

6.3 二阶段回滚
  • 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的 “业务 SQL”,还原业务数据。
  • 回滚方式便是用 “before image” 还原业务数据。
  1. 但在还原前要首先要校验脏写,对比 “数据库当前业务数据” 和 “after image”。
  2. 如果两份数据完全一致就说明 没有脏写,可以还原业务数据,如果不一致就说明 有脏写,出现脏写就需要转人工处理。
    在这里插入图片描述

6.4 总结

在这里插入图片描述


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1、课程简介Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。       在本套课程中,我们将全面的讲解Spring Cloud技术栈, 从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用Spring Cloud如何解决实际的问题。Spring Cloud各个组件相互配合,合作支持了一套完整的微服务架构。- 注册中心负责服务的注册与发现,很好将各服务连接起来- 断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。- API网关负责转发所有对外的请求和服务- 配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息- 链路追踪技术可以将所有的请求数据记录下来,方便我们进行后续分析- 各个组件又提供了功能完善的dashboard监控平台,可以方便的监控各组件的运行状况2、适应人群有一定的Java基础,并且要有一定的web开发基础。3、课程亮点       系统的学习Spring Cloud技术栈,由浅入深的讲解微服务技术。涵盖了基础知识,原理剖析,组件使用,源码分析,优劣分析,替换方案等,以案例的形式讲解微服务中的种种问题和解决方案l  微服务的基础知识n  软件架构的发展史n  微服务的核心知识(CAP,RPC等)l  注册中心n  Eureka搭建配置服务注册n  Eureka服务端高可用集群n  Eureka的原理和源码导读n  Eureka替换方案Consuln  Consul下载安装&服务注册&高可用l  服务发现与服务调用n  Ribbon负载均衡基本使用&源码分析n  Feign的使用与源码分析n  Hystrix熔断(雪崩效应,Hystrix使用与原理分析)n  Hystrix替换方案Sentinell  微服务网关n  Zuul网关使用&原理分析&源码分析n  Zuul 1.x 版本的不足与替换方案n  SpringCloud Gateway深入剖析l  链路追踪n  链路追踪的基础知识n  Sleuth的介绍与使用n  Sleuth与Zipkin的整合开发l  配置中心n  SpringClond Config与bus 开发配置中心n  开源配置中心Apollo4、主讲内容章节一:1.     微服务基础知识2.     SpringCloud概述3.     服务注册中心Eureka4.     Eureka的替换方案Consul章节二:1.     Ribbon实现客户端负载均衡2.     基于Feign的微服务调用3.     微服务熔断技术Hystrix4.     Hystrix的替换方案Sentinel章节:1.     微服务网关Zuul的基本使用2.     Zuul1.x 版本的不足和替换方案3.     深入SpringCloud Gateway4.     链路追踪Sleuth与Zipkin章节四:1.     SpringCloud Config的使用2.     SpringCloud Config结合SpringCloud Bus完成动态配置更新3.     开源配置中心Apollo

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值