微服务解决方案 -- 分布式事务 Seata

Seata

Seata 是什么?

Seata is an easy-to-use, high-performance, open source distributed transaction solution.

Seata 是一个简单易用的,高性能,开源的分布式事务解决方案。

AT模式

AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,
用户的 “业务 SQL” 就是全局事务一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

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

  • 一阶段:
    在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,
    在业务数据被更新前,将其保存成before image,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,
    再将其保存成after image,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

  • 二阶段提交:
    二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

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

AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
(以上选自知乎分布式事务的4种模式)

nacos-dubbo

nacos-dubbo是一个简单的Seata AT模式的入门项目,使用dubbo去实现服务与服务之间的调用。

说明

provider 项目中只有2种类型的项目一种是api,还有一种是service
所谓api项目是项目只有接口和domain,没有实现。此项目会被该api的实现(service)和需要调用service的项目依赖。
service项目是实现api项目的项目一般是去操作数据库或者其他业务的项目。

consumer 项目也只有2种类型的项目一种是api,还有一种是service
和上面一样,service项目会去调用对应的provider的接口,使用的是dubbo rpc的通讯

business 项目是指业务层的代码项目,他会去依赖consumerapi项目。然后对外部提供RESTFul的接口

所以在这个项目中,先启动2个provider中的service,然后启动consumerservice,最后启动business项目。

准备

首先需要创建2个库,一个库存放order表,一个库存放order_item

CREATE TABLE tb_order (
    id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    order_id BIGINT(20) NOT NULL, 
    user_id BIGINT(20) NOT NULL);
CREATE TABLE tb_order_item (
    id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    user_id BIGINT(20) NOT NULL, 
    order_id BIGINT(20) NOT NULL, 
    order_item_id BIGINT(20) NOT NULL);

除此之外,还需要在每个库里都创建一个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=1 DEFAULT CHARSET=utf8;

provider

主要增加了 Seata 依赖

<dependency>
    <groupId>io.seatagroupId>
    <artifactId>seata-spring-boot-starterartifactId>
    <version>1.0.0version>
dependency>

配置文件

spring
  alibaba:
      seata:
        # 自定义事务组名称 tx_group,需要与服务端一致
        tx-service-group: tx_group

配置类,具体看项目的SeataConfiguration

import io.seata.rm.datasource.DataSourceProxy;

@Configuration
public class SeataConfiguration {
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSource(DataSource hikariDataSource) {
        return new DataSourceProxy(hikariDataSource);
    }

    @Bean
    public GlobalTransactionScanner globalTransactionScanner() {
        /**
         * applicationId:同服务名即可
         * txServiceGroup:自定义事务组名,需要与 Seata Server 配置一致
         */
        return new GlobalTransactionScanner("provider-order-item", "tx_group");
    }
}

一定要在启动类上加上@EnableTransactionManagement

transaction

这里删除去了 mybatis mysql hikari 的依赖。
配置类

import io.seata.spring.annotation.GlobalTransactionScanner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SeataConfiguration {

    @Bean
    public GlobalTransactionScanner globalTransactionScanner() {
        return new GlobalTransactionScanner("consumer-transaction", "tx_group");
    }
}

实现方法上加上注解@GlobalTransactional

@Service(version = "1.0.0")
public class TransactionServiceImpl implements TransactionService {

    @Reference(version = "1.0.0")
    private OrderService orderService;

    @Reference(version = "1.0.0")
    private OrderItemService orderItemService;

    @Override
    @GlobalTransactional
    public void doTransaction(TbOrder tbOrder, TbOrderItem tbOrderItem) throws Exception {
        System.out.println("transaction 开始全局事务,XID = " + RootContext.getXID());
        orderService.insert(tbOrder);
        orderItemService.insert(tbOrderItem);
        if (tbOrderItem.getOrderId() == null ) {
            throw  new RuntimeException(" null Exception ");
        }
    }
}

business

这里就正常调用consumer

@RestController
@RequestMapping("/v1/transaction")
public class TransactionController {

    @Reference(version = "1.0.0")
    private TransactionService transactionService;

    @GetMapping("")
    public Map<String,Object> doTransaction() throws Exception {
        TbOrder order = new TbOrder();
        order.setOrderId(1L);
        order.setUserId(1L);
        TbOrderItem orderItem = new TbOrderItem();
        orderItem.setUserId(1L);
        transactionService.doTransaction(order,orderItem);
		Map<String,Object> map = new HashMap<>();
		map.put("code",200);
		map.put("message""服务调用成功");
        return map;
    }
}

接下来就是浏览器访问测试 http://127.0.0.1:27010/v1/transaction
再到Seata服务上看到回滚的日志,再查看数据库。成功回滚

nacos-http(TBD)

nacos-http是一个使用Spring Cloud Alibaba 实现Seata AT模式的入门项目。

你可能感兴趣的:(微服务解决方案)