Seata:一个简单易用的分布式事务解决方案

Seata:一个简单易用的分布式事务解决方案

什么是Seata?

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata的前身是Fescar,是阿里系内部一直扮演着应用架构层数据一致性的中间件角色,帮助经济体平稳的度过历年的双11,对上层业务进行了有力的技术支撑。2019年1月,Seata正式对外开源,未来将以社区共建的形式帮助用户快速落地分布式事务解决方案。

Seata logo

为什么需要Seata?

在微服务架构中,为了提高系统的可用性和可扩展性,通常会将一个大型的单体应用拆分成多个小型的微服务,每个微服务都有自己独立的数据库。这样做的好处是可以实现服务之间的解耦和自治,但也带来了一个新的问题:如何保证跨多个微服务的业务操作具有原子性和一致性?传统的本地事务已经无法满足这种场景,因为本地事务只能保证单个数据库的数据一致性,而不能保证多个数据库之间的数据一致性。这就需要引入分布式事务来解决这个问题。

分布式事务是指跨多个资源管理器(如数据库、消息队列等)执行的事务,它需要保证所有参与者都能达成一致的最终状态。分布式事务需要满足ACID属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。然而,在分布式环境中,要实现ACID属性是非常困难和代价昂贵的,因为需要考虑网络延迟、故障、并发等各种复杂因素。因此,很多分布式事务解决方案都是基于某种妥协或权衡来实现的。

Seata就是一个基于妥协或权衡来实现分布式事务的框架,它提供了多种分布式事务模式来适应不同场景和需求。目前已支持Dubbo、Spring Cloud、Sofa-RPC、Motan 和 gRPC 等RPC框架,其他框架持续集成中。

Seata architecture

Seata有哪些特色功能?

Seata目前支持以下四种分布式事务模式:

  • AT 模式:提供无侵入自动补偿的事务模式,目前已支持MySQL、Oracle、PostgreSQL、TiDB 和 MariaDB。 H2、DB2、SQLServer、达梦开发中。
  • TCC 模式:支持 TCC 模式并可与 AT 混用,灵活度更高。
  • SAGA 模式:为长事务提供有效的解决方案,提供编排式与注解式 (开发中)。
  • XA 模式:支持已实现 XA 接口的数据库的 XA 模式,目前已支持MySQL、Oracle、TiDB和MariaDB。

Seata还具有以下特点:

  • 高可用:支持计算分离集群模式,水平扩展能力强的数据库和 Redis 存储模式.Raft模式Preview阶段。
  • 易用性:提供了丰富的文档和示例,以及友好的社区支持。
  • 生态兼容:与其他阿里系开源项目如Nacos、Sentinel、RocketMQ等有良好的集成和协作。

总结

Seata是一个简单易用的分布式事务解决方案,它可以帮助开发者在微服务架构下实现数据一致性,提高系统的可靠性和稳定性。Seata目前已经有很多知名的企业在使用,如阿里巴巴、蚂蚁金服、美团、京东、滴滴等。

示例

下面是一个使用Seata AT 模式实现分布式事务的简单示例,假设有两个微服务:订单服务和库存服务,订单服务负责创建订单,库存服务负责扣减库存。当用户下单时,需要同时调用两个服务,并保证数据一致性。如果其中一个服务失败了,需要回滚另一个服务的操作。

步骤一:引入Seata依赖

在两个微服务的pom.xml文件中,添加Seata依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

步骤二:配置Seata

在两个微服务的application.properties文件中,添加Seata相关配置:

# Seata 服务端地址
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
# Seata 应用ID
seata.application.id=order-service # 或者 inventory-service
# Seata 事务组ID
seata.tx-service-group=my_test_tx_group
# Seata 数据源代理模式
seata.datasource.proxy-mode=AT
# Seata 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false # 或者 inventory_db
spring.datasource.username=root
spring.datasource.password=root

步骤三:创建数据库表

在两个微服务对应的数据库中,创建业务表和undo_log表:

-- 订单表
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  `money` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_commodity` (`user_id`,`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- 库存表
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- undo_log 表(两个数据库都需要)
CREATE TABLE `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    `ext`           VARCHAR(100) DEFAULT NULL COMMENT 'reserved field',
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

步骤四:编写业务代码

在两个微服务中,分别编写订单服务和库存服务的业务代码,使用@GlobalTransactional注解标注分布式事务的入口方法,使用@DataSourceProxy注解标注数据源代理对象,使用JdbcTemplate或者Mybatis等方式操作数据库:

// 订单服务
@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private InventoryService inventoryService;

    @GlobalTransactional // 分布式事务入口
    public void createOrder(String userId, String commodityCode, int orderCount) {
        // 扣减库存
        inventoryService.deduct(commodityCode, orderCount);

        // 创建订单
        int orderMoney = calculate(commodityCode, orderCount);
        jdbcTemplate.update("insert into order_tbl(user_id, commodity_code, count, money) values (?, ?, ?, ?)",
                new Object[]{userId, commodityCode, orderCount, orderMoney});

        // 模拟异常,测试回滚
        if (orderCount == 2) {
            throw new RuntimeException("create order failed");
        }
    }

    private int calculate(String commodityCode, int orderCount) {
        return 100 * orderCount;
    }
}

// 库存服务
@Service
public class InventoryService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DataSourceProxy // 数据源代理
    public void deduct(String commodityCode, int count) {
        jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?",
                new Object[]{count, commodityCode});
    }
}

步骤五:启动Seata服务端和客户端

下载Seata服务端的压缩包,解压后修改conf目录下的file.conf和registry.conf文件,配置好数据库和注册中心(如Nacos)等信息,然后运行bin目录下的seata-server.bat或者seata-server.sh文件,启动Seata服务端。

在两个微服务的项目目录下,运行mvn spring-boot:run命令,启动Seata客户端。

步骤六:测试分布式事务

使用Postman或者curl等工具,向订单服务发送请求,创建订单:

curl -X POST http://localhost:8081/order/create?userId=U100001&commodityCode=C00321&orderCount=2

观察控制台输出和数据库变化,可以发现当orderCount为2时,会触发异常,并回滚订单服务和库存服务的操作,保证数据一致性。当orderCount为其他值时,会正常执行,并提交订单服务和库存服务的操作。

Seata的四种事务模式详细介绍请参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值