up:: SpringCloud分布式事务介绍
说明: 简单复习下分布式事务:
为啥需要分布式事务
什么是事务
事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。
本地事务
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
分布式事务
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
我们知道本地事务依赖数据库本身提供的事务特性来实现,因此以下逻辑可以控制本地事务:
begin transaction;
//1.本地数据库操作:张三减少金额
//2.本地数据库操作:李四增加金额
commit transation;
但是在分布式环境下,会变成下边这样:
begin transaction;
//1.本地数据库操作:张三减少金额
//2.远程调用:让李四增加金额
commit transation;
可以设想,当远程调用让李四增加金额成功了,由于网络问题远程调用并没有返回,此时本地事务提交失败就回滚了张三减少金额的操作,此时张三和李四的数据就不一致了。
因此在分布式架构的基础上,传统数据库事务就无法使用了,张三和李四的账户不在一个数据库中甚至不在一个应用系统里,实现转账事务需要通过远程调用,由于网络 问题就会导致分布式事务问题。
单体到微服务
下单问题
/**
* 创建订单
* @param createOrderReq
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public String create(CreateOrderReq createOrderReq) {
//首先,拿到用户ID;
Integer userId = UserInfoFilter.userThreadLocal.get().getId();
//从购物车中,查询当前用户的、购物车中的、已经被勾选的商品;
List<CartVO> cartVOList = cartService.list(userId);
//遍历查到的购物车数据,从中筛选出被勾选的;
ArrayList<CartVO> cartVOArrayListTemp = new ArrayList<>();
for (int i = 0; i < cartVOList.size(); i++) {
CartVO cartVO = cartVOList.get(i);
if (cartVO.getSelected().equals(Constant.Cart.CHECKED)) {
cartVOArrayListTemp.add(cartVO);
}
}
cartVOList = cartVOArrayListTemp;
//如果,购物车中没有已经被勾选的商品:就抛出"购物车勾选的商品为空"异常;
if (CollectionUtils.isEmpty(cartVOList)) {
throw new ImoocMallException(ImoocMallExceptionEnum.CART_SELECTED_EMPTY);
}
//判断商品是否存在;如果存在,是否是上架状态;商品库存是否足够;
validSaleStatusAndStock(cartVOList);
//把【查询购物车表cart表,获得的商品数据】转化为【能够存储到order_item表的、商品数据】
List<OrderItem> orderItemList = cartVOListToOrderItemList(cartVOList);
//扣库存;(PS:前面有判断库存的逻辑,程序如果能走到这一步,就说明库存是够的)
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
//首先,先拿到原先的product
Product product = productFeignClient.detailForFeign(orderItem.getProductId());
//然后,计算新的库存;
int stock = product.getStock() - orderItem.getQuantity();
if (stock < 0) {//上面已经检查过库存了,这儿又判断,是否是重复工作
throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
}
productFeignClient.updateStock(product.getId(),stock);
}
//把【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】给删除;也就是,删除cart表中,对应的记录;
cleanCart(cartVOList);
//编写逻辑,生成一个订单号
String orderNum = OrderCodeFactory.getOrderCode(Long.valueOf(userId));
//创建一个订单;
Order order = new Order();
order.setOrderNo(orderNum);//设置订单号
order.setUserId(userId);//设置用户id
order.setTotalPrice(totalPrice(orderItemList));//设置订单总价
order.setReceiverName(createOrderReq.getReceiverName());//设置收件人姓名
order.setReceiverAddress(createOrderReq.getReceiverAddress());//设置收件人地址
order.setReceiverMobile(createOrderReq.getReceiverMobile());//设置收件人电话
order.setOrderStatus(Constant.OrderStatusEnum.NOT_PAID.getCode());//设置订单状态
order.setPostage(0);//运费;我们这儿目前是包邮
order.setPaymentType(1);//付款方式;我们这儿只有一种1在线支付
//把这个订单,添加到order表中,新增一个订单记录;
orderMapper.insertSelective(order);
//也要利用循环,把订单中的每种商品,写到order_item表中;
for (int i = 0; i < orderItemList.size(); i++) {
OrderItem orderItem = orderItemList.get(i);
orderItem.setOrderNo(orderNum);//给其赋上订单号
orderItemMapper.insertSelective(orderItem);
}
//返回结果;
return orderNum;
}
说明: 可以看到订单的创建涉及到多个模块,如ProductFeignClient就远程的调用其它模块服务,虽然我们添加了事务@Transactional(rollbackFor = Exception.class), 但也只能保存本模块不会出错,如果远程调用商品模块成功,但由于网络问题无法返回,本订单模块就会数据回滚,而商品模块却不回滚,所以就出现了问题。。。
引入2PC理论
成功的例子
失败的例子
改进其它的分布式事务方案
了解即可。。。
分布式事务框架-Seata
Seata介绍
Seata 整体工作流程
Seata 对分布式事务的协调和控制,主要是通过 XID 和 3 个核心组件实现的。
XID 是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。
核心组件
Seata 定义了 3 个核心组件:
- TC(Transaction Coordinator):事务协调器,它是事务的协调者(这里指的是 Seata 服务器),主要负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
- TM(Transaction Manager):事务管理器,它是事务的发起者,负责定义全局事务的范围,并根据 TC 维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
- RM(Resource Manager):资源管理器,它是资源的管理者(这里可以将其理解为各服务使用的数据库)。它负责管理分支事务上的资源,向 TC 注册分支事务,汇报分支事务状态,驱动分支事务的提交或回滚。
以上三个组件相互协作,TC 以 Seata 服务器(Server)形式独立部署,TM 和 RM 则是以 Seata Client 的形式集成在微服务中运行,其整体工作流程如下图。
Seata 的整体工作流程如下(重要!!!):
- TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID;
- XID 通过服务的调用链传递到其他服务;
- RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖;
- TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议;
- TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。
大白话就是将有关涉及事务的模块给一个名字,然后全部写到上课的花名单,班长得到花名单进行逐个点名,全部到齐后才可以上课。
Seata开发流程
重要,必读!
seata-samples/quick-integration-with-spring-cloud.md at master · seata/seata-samples · GitHub
本节使用1.4.2,以免出现问题,还是跟着同样版本进行操作。