在前面的文章中,我们介绍了分布式事务的概念以及一些解决方案。fenSeata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。
本文以一个用户下单购买商品的系统为例,介绍开源框架Seata的原理和使用,下单该系统涉及三部分服务:
分布式事务的主要作用是保证微服务情况下用户下单过程中数据的一致性。这里的一致性可以这样理解:不会出现用户余额扣除成功,但是仓储和订单相关操作失败的场景,三者要么同时成功,要么同时失败。
如果用户下单购买商品涉及到的服务都在一个传统的单机服务中,三部分服务可以共享同一个数据库实例。这种情况下,我们可以通过本地事务的一致性保证仓储/订单/账户三者之间数据的一致性。
如上图所示,在单机服务中,三部分内容共用同一个数据库实例,所以我们只需要本地事务就可以解决数据的一致性,以Spring框架为例,我们只需要在方法上添加@Transaction
注解就可以实现整个购买流程中数据的一致性:
@Transaction public void purchase(){ doStoreBusiness(); doOrderBusiness(); doAccountBusiness(); }
在微服务框架中,仓储/订单/账户服务部署在不同的服务器上,使用不同的数据库实例,与单机模式完全不同。单机模式中的事务通常要求事务涉及的数据源为同一个,并且事务涉及的数据库操作在同一个数据库链接中,分布式情况下显然不满足条件。
所以在分布式场景如何保证数据的一致性呢?这就是Seata需要解决的问题了。
Seata是用于解决分布式事务的开源框架,其内部有关于分布式事务的定义如下:分布式事务是由多个分支事务组成的全局事务,其中每个分支事务都是本地事务的形式。
Seata框架包含三部分内容:
一个典型的seata分布式事务的流程如下:
阿里巴巴:
蚂蚁金服:
Seata社区:
Seata Maven依赖
<seata.version>1.4.2</seata.version> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>${seata.version}</version> </dependency>
以上文中的用户下单购买商品的系统为例,展示Seata的使用方式:
对于三个微服务,我们用三个接口来抽象其内部的逻辑:
仓储服务
public interface StorageService { /** * 扣除存储数量 */ void deduct(String commodityCode, int count); }
订单服务
public interface OrderService { /** * 创建订单 */ Order create(String userId, String commodityCode, int orderCount); }
帐户服务
public interface AccountService { /** * 从用户账户中借出 */ void debit(String userId, int money); }
对于用户下单购买商品的逻辑,我们用以下代码来实现:
主要业务逻辑
public class BusinessServiceImpl implements BusinessService { private StorageService storageService; private OrderService orderService; /** * 采购 */ public void purchase(String userId, String commodityCode, int orderCount) { storageService.deduct(commodityCode, orderCount); orderService.create(userId, commodityCode, orderCount); } }
订单服务业务逻辑
public class OrderServiceImpl implements OrderService { private OrderDAO orderDAO; private AccountService accountService; public Order create(String userId, String commodityCode, int orderCount) { int orderMoney = calculate(commodityCode, orderCount); accountService.debit(userId, orderMoney); Order order = new Order(); order.userId = userId; order.commodityCode = commodityCode; order.count = orderCount; order.money = orderMoney; // INSERT INTO orders ... return orderDAO.insert(order); } }
在服务中引入Seata服务之后,我们只需要在分布式事务最外层的方法上添加分布式事务注解@GlobalTransactional
:
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { // ...... }
添加注解之后,在执行业务逻辑之前,Seata会先生成全局事务ID,并且在调用其它服务时,会在请求中携带全局事务ID。如果其它微服务也添加了Seata依赖,这些微服务会获取全局事务ID,并且参与到全局事务中。
本文只是简单介绍以下Seata框架,具体工作原理在后续文章中详细介绍。