生成订单链路中的技术问题说明文档

大纲

1.生成订单链路的业务代码

2.生成订单链路中可能会出现数据不一致的问题

3.Seata AT模式下的分布式事务的原理

4.Seata AT模式下的分布式事务的读写隔离原理

5.Seata AT模式下的死锁问题以及超时机制

6.Seata AT模式下的读写隔离机制的影响

7.生成订单链路使用Seata AT模式的具体步骤

8.生成订单链路使用Seata AT模式时的原理流程

9.生成订单链路使用Seata AT模式时的并发问题

10.生成订单链路如何解决库存全局锁争用问题

1.生成订单链路的业务代码

(1)生成订单流程

(2)入参检查与风控检查

(3)获取商品信息与计算订单价格及验证价格

(4)锁定优惠券与商品库存

(5)新增订单到数据库

(6)发送延迟消息到MQ

(1)生成订单流程

scss 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //提交订单/生成订单接口
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public CreateOrderDTO createOrder(CreateOrderRequest createOrderRequest) {
        //1.入参检查
        checkCreateOrderRequestParam(createOrderRequest);
        
        //2.风控检查
        checkRisk(createOrderRequest);
        
        //3.获取商品信息
        List<ProductSkuDTO> productSkuList = listProductSkus(createOrderRequest);
        
        //4.计算订单价格
        CalculateOrderAmountDTO calculateOrderAmountDTO = calculateOrderAmount(createOrderRequest, productSkuList);
        
        //5.验证订单实付金额
        checkRealPayAmount(createOrderRequest, calculateOrderAmountDTO);
        
        //6.锁定优惠券
        lockUserCoupon(createOrderRequest);
        
        //7.锁定商品库存
        lockProductStock(createOrderRequest);
       
        //8.生成订单到数据库
        addNewOrder(createOrderRequest, productSkuList, calculateOrderAmountDTO);
        
        //9.发送订单延迟消息用于支付超时自动关单
        sendPayOrderTimeoutDelayMessage(createOrderRequest);
        
        //返回订单信息
        CreateOrderDTO createOrderDTO = new CreateOrderDTO();
        createOrderDTO.setOrderId(createOrderRequest.getOrderId());
        return createOrderDTO;
    }
    ...
}

(2)入参检查与风控检查

ini 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //检查创建订单请求参数
    private void checkCreateOrderRequestParam(CreateOrderRequest createOrderRequest) {
        ParamCheckUtil.checkObjectNonNull(createOrderRequest);
        //订单ID检查
        String orderId = createOrderRequest.getOrderId();
        ParamCheckUtil.checkStringNonEmpty(orderId, OrderErrorCodeEnum.ORDER_ID_IS_NULL);

        //业务线标识检查
        Integer businessIdentifier = createOrderRequest.getBusinessIdentifier();
        ParamCheckUtil.checkObjectNonNull(businessIdentifier, OrderErrorCodeEnum.BUSINESS_IDENTIFIER_IS_NULL);
        if (BusinessIdentifierEnum.getByCode(businessIdentifier) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.BUSINESS_IDENTIFIER_ERROR);
        }

        //用户ID检查
        String userId = createOrderRequest.getUserId();
        ParamCheckUtil.checkStringNonEmpty(userId, OrderErrorCodeEnum.USER_ID_IS_NULL);

        //订单类型检查
        Integer orderType = createOrderRequest.getOrderType();
        ParamCheckUtil.checkObjectNonNull(businessIdentifier, OrderErrorCodeEnum.ORDER_TYPE_IS_NULL);
        if (OrderTypeEnum.getByCode(orderType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_TYPE_ERROR);
        }

        //卖家ID检查
        String sellerId = createOrderRequest.getSellerId();
        ParamCheckUtil.checkStringNonEmpty(sellerId, OrderErrorCodeEnum.SELLER_ID_IS_NULL);

        //配送类型检查
        Integer deliveryType = createOrderRequest.getDeliveryType();
        ParamCheckUtil.checkObjectNonNull(deliveryType, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        if (DeliveryTypeEnum.getByCode(deliveryType) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.DELIVERY_TYPE_ERROR);
        }

        //地址信息检查
        String province = createOrderRequest.getProvince();
        String city = createOrderRequest.getCity();
        String area = createOrderRequest.getArea();
        String streetAddress = createOrderRequest.getStreet();
        ParamCheckUtil.checkStringNonEmpty(province, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(city, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(area, OrderErrorCodeEnum.USER_ADDRESS_ERROR);
        ParamCheckUtil.checkStringNonEmpty(streetAddress, OrderErrorCodeEnum.USER_ADDRESS_ERROR);

        //区域ID检查
        String regionId = createOrderRequest.getRegionId();
        ParamCheckUtil.checkStringNonEmpty(regionId, OrderErrorCodeEnum.REGION_ID_IS_NULL);

        //经纬度检查
        BigDecimal lon = createOrderRequest.getLon();
        BigDecimal lat = createOrderRequest.getLat();
        ParamCheckUtil.checkObjectNonNull(lon, OrderErrorCodeEnum.USER_LOCATION_IS_NULL);
        ParamCheckUtil.checkObjectNonNull(lat, OrderErrorCodeEnum.USER_LOCATION_IS_NULL);

        //收货人信息检查
        String receiverName = createOrderRequest.getReceiverName();
        String receiverPhone = createOrderRequest.getReceiverPhone();
        ParamCheckUtil.checkStringNonEmpty(receiverName, OrderErrorCodeEnum.ORDER_RECEIVER_IS_NULL);
        ParamCheckUtil.checkStringNonEmpty(receiverPhone, OrderErrorCodeEnum.ORDER_RECEIVER_IS_NULL);

        //客户端设备信息检查
        String clientIp = createOrderRequest.getClientIp();
        ParamCheckUtil.checkStringNonEmpty(clientIp, OrderErrorCodeEnum.CLIENT_IP_IS_NULL);

        //商品条目信息检查
        List<CreateOrderRequest.OrderItemRequest> orderItemRequestList = createOrderRequest.getOrderItemRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(orderItemRequestList, OrderErrorCodeEnum.ORDER_ITEM_IS_NULL);
        for (CreateOrderRequest.OrderItemRequest orderItemRequest : orderItemRequestList) {
            Integer productType = orderItemRequest.getProductType();
            Integer saleQuantity = orderItemRequest.getSaleQuantity();
            String skuCode = orderItemRequest.getSkuCode();
            ParamCheckUtil.checkObjectNonNull(productType, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
            ParamCheckUtil.checkObjectNonNull(saleQuantity, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
            ParamCheckUtil.checkStringNonEmpty(skuCode, OrderErrorCodeEnum.ORDER_ITEM_PARAM_ERROR);
        }

        //订单费用信息检查
        List<CreateOrderRequest.OrderAmountRequest> orderAmountRequestList = createOrderRequest.getOrderAmountRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(orderAmountRequestList, OrderErrorCodeEnum.ORDER_AMOUNT_IS_NULL);
        for (CreateOrderRequest.OrderAmountRequest orderAmountRequest : orderAmountRequestList) {
            Integer amountType = orderAmountRequest.getAmountType();
            ParamCheckUtil.checkObjectNonNull(amountType, OrderErrorCodeEnum.ORDER_AMOUNT_TYPE_IS_NULL);
            if (AmountTypeEnum.getByCode(amountType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ORDER_AMOUNT_TYPE_PARAM_ERROR);
            }
        }
        Map<Integer, Integer> orderAmountMap = orderAmountRequestList.stream()
            .collect(Collectors.toMap(CreateOrderRequest.OrderAmountRequest::getAmountType, CreateOrderRequest.OrderAmountRequest::getAmount));

        //订单支付原价不能为空
        if (orderAmountMap.get(AmountTypeEnum.ORIGIN_PAY_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_ORIGIN_PAY_AMOUNT_IS_NULL);
        }

        //订单运费不能为空
        if (orderAmountMap.get(AmountTypeEnum.SHIPPING_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_SHIPPING_AMOUNT_IS_NULL);
        }

        //订单实付金额不能为空
        if (orderAmountMap.get(AmountTypeEnum.REAL_PAY_AMOUNT.getCode()) == null) {
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_REAL_PAY_AMOUNT_IS_NULL);
        }
        if (StringUtils.isNotEmpty(createOrderRequest.getCouponId())) {
            //订单优惠券抵扣金额不能为空
            if (orderAmountMap.get(AmountTypeEnum.COUPON_DISCOUNT_AMOUNT.getCode()) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ORDER_DISCOUNT_AMOUNT_IS_NULL);
            }
        }

        //订单支付信息检查
        List<CreateOrderRequest.PaymentRequest> paymentRequestList = createOrderRequest.getPaymentRequestList();
        ParamCheckUtil.checkCollectionNonEmpty(paymentRequestList, OrderErrorCodeEnum.ORDER_PAYMENT_IS_NULL);
        for (CreateOrderRequest.PaymentRequest paymentRequest : paymentRequestList) {
            Integer payType = paymentRequest.getPayType();
            Integer accountType = paymentRequest.getAccountType();
            if (payType == null || PayTypeEnum.getByCode(payType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
            }
            if (accountType == null || AccountTypeEnum.getByCode(accountType) == null) {
                throw new OrderBizException(OrderErrorCodeEnum.ACCOUNT_TYPE_PARAM_ERROR);
            }
        }
    }

    //风控检查
    private void checkRisk(CreateOrderRequest createOrderRequest) {
        //调用风控服务进行风控检查
        CheckOrderRiskRequest checkOrderRiskRequest = createOrderRequest.clone(CheckOrderRiskRequest.class);
        JsonResult<CheckOrderRiskDTO> jsonResult = riskApi.checkOrderRisk(checkOrderRiskRequest);
        if (!jsonResult.getSuccess()) {
            throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
        }
    }
    ...
}

(3)获取商品信息与计算订单价格及验证价格

ini 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //获取订单条目商品信息
    private List<ProductSkuDTO> listProductSkus(CreateOrderRequest createOrderRequest) {
        List<CreateOrderRequest.OrderItemRequest> orderItemRequestList = createOrderRequest.getOrderItemRequestList();
        List<ProductSkuDTO> productSkuList = new ArrayList<>();
        for (CreateOrderRequest.OrderItemRequest orderItemRequest : orderItemRequestList) {
            String skuCode = orderItemRequest.getSkuCode();
            ProductSkuQuery productSkuQuery = new ProductSkuQuery();
            productSkuQuery.setSkuCode(skuCode);
            productSkuQuery.setSellerId(createOrderRequest.getSellerId());
            JsonResult<ProductSkuDTO> jsonResult = productApi.getProductSku(productSkuQuery);
            if (!jsonResult.getSuccess()) {
                throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
            }
            ProductSkuDTO productSkuDTO = jsonResult.getData();
            //sku不存在
            if (productSkuDTO == null) {
                throw new OrderBizException(OrderErrorCodeEnum.PRODUCT_SKU_CODE_ERROR, skuCode);
            }
            productSkuList.add(productSkuDTO);
        }
        return productSkuList;
    }

    //计算订单价格,如果使用了优惠券、红包、积分等,会一并进行扣减
    //@param createOrderRequest 订单信息
    //@param productSkuList     商品信息
    private CalculateOrderAmountDTO calculateOrderAmount(CreateOrderRequest createOrderRequest, List<ProductSkuDTO> productSkuList) {
        CalculateOrderAmountRequest calculateOrderPriceRequest = createOrderRequest.clone(CalculateOrderAmountRequest.class, CloneDirection.FORWARD);
        //订单条目补充商品信息
        Map<String, ProductSkuDTO> productSkuDTOMap = productSkuList.stream().collect(Collectors.toMap(ProductSkuDTO::getSkuCode, Function.identity()));
        calculateOrderPriceRequest.getOrderItemRequestList().forEach(item -> {
            String skuCode = item.getSkuCode();
            ProductSkuDTO productSkuDTO = productSkuDTOMap.get(skuCode);
            item.setProductId(productSkuDTO.getProductId());
            item.setSalePrice(productSkuDTO.getSalePrice());
        });
        //调用营销服务计算订单价格
        JsonResult<CalculateOrderAmountDTO> jsonResult = marketApi.calculateOrderAmount(calculateOrderPriceRequest);
        //检查价格计算结果
        if (!jsonResult.getSuccess()) {
            throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
        }
        CalculateOrderAmountDTO calculateOrderAmountDTO = jsonResult.getData();
        if (calculateOrderAmountDTO == null) {
            throw new OrderBizException(OrderErrorCodeEnum.CALCULATE_ORDER_AMOUNT_ERROR);
        }
        //订单费用信息
        List<OrderAmountDTO> orderAmountList = ObjectUtil.convertList(calculateOrderAmountDTO.getOrderAmountList(), OrderAmountDTO.class);
        if (orderAmountList == null || orderAmountList.isEmpty()) {
            throw new OrderBizException(OrderErrorCodeEnum.CALCULATE_ORDER_AMOUNT_ERROR);
        }
        //订单条目费用明细
        List<OrderAmountDetailDTO> orderItemAmountList = ObjectUtil.convertList(calculateOrderAmountDTO.getOrderAmountDetail(), OrderAmountDetailDTO.class);
        if (orderItemAmountList == null || orderItemAmountList.isEmpty()) {
            throw new OrderBizException(OrderErrorCodeEnum.CALCULATE_ORDER_AMOUNT_ERROR);
        }
        return calculateOrderAmountDTO;
    }

    //验证订单实付金额
    private void checkRealPayAmount(CreateOrderRequest createOrderRequest, CalculateOrderAmountDTO calculateOrderAmountDTO) {
        List<CreateOrderRequest.OrderAmountRequest> originOrderAmountRequestList = createOrderRequest.getOrderAmountRequestList();
        Map<Integer, CreateOrderRequest.OrderAmountRequest> originOrderAmountMap =
            originOrderAmountRequestList.stream().collect(Collectors.toMap(CreateOrderRequest.OrderAmountRequest::getAmountType, Function.identity()));
        //前端给的实付金额
        Integer originRealPayAmount = originOrderAmountMap.get(AmountTypeEnum.REAL_PAY_AMOUNT.getCode()).getAmount();
        List<CalculateOrderAmountDTO.OrderAmountDTO> orderAmountDTOList = calculateOrderAmountDTO.getOrderAmountList();
        Map<Integer, CalculateOrderAmountDTO.OrderAmountDTO> orderAmountMap =
            orderAmountDTOList.stream().collect(Collectors.toMap(CalculateOrderAmountDTO.OrderAmountDTO::getAmountType, Function.identity()));
        //营销计算出来的实付金额
        Integer realPayAmount = orderAmountMap.get(AmountTypeEnum.REAL_PAY_AMOUNT.getCode()).getAmount();
        if (!originRealPayAmount.equals(realPayAmount)) {
            //订单验价失败
            throw new OrderBizException(OrderErrorCodeEnum.ORDER_CHECK_REAL_PAY_AMOUNT_FAIL);
        }
    }
    ...
}

(4)锁定优惠券与商品库存

typescript 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //锁定用户优惠券
    private void lockUserCoupon(CreateOrderRequest createOrderRequest) {
        String couponId = createOrderRequest.getCouponId();
        if (StringUtils.isEmpty(couponId)) {
            return;
        }
        LockUserCouponRequest lockUserCouponRequest = createOrderRequest.clone(LockUserCouponRequest.class);
        //调用营销服务锁定用户优惠券
        JsonResult<Boolean> jsonResult = marketApi.lockUserCoupon(lockUserCouponRequest);
        //检查锁定用户优惠券结果
        if (!jsonResult.getSuccess()) {
            throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
        }
    }

    //锁定商品库存
    private void lockProductStock(CreateOrderRequest createOrderRequest) {
        String orderId = createOrderRequest.getOrderId();
        List<LockProductStockRequest.OrderItemRequest> orderItemRequestList = ObjectUtil.convertList(
            createOrderRequest.getOrderItemRequestList(), LockProductStockRequest.OrderItemRequest.class);
        LockProductStockRequest lockProductStockRequest = new LockProductStockRequest();
        lockProductStockRequest.setOrderId(orderId);
        lockProductStockRequest.setOrderItemRequestList(orderItemRequestList);
        JsonResult<Boolean> jsonResult = inventoryApi.lockProductStock(lockProductStockRequest);
        //检查锁定商品库存结果
        if (!jsonResult.getSuccess()) {
            throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
        }
    }
    ...
}

@DubboService(version = "1.0.0", interfaceClass = MarketApi.class, retries = 0)
public class MarketApiImpl implements MarketApi {
    ...
    //锁定用户优惠券记录
    @Override
    public JsonResult<Boolean> lockUserCoupon(LockUserCouponRequest lockUserCouponRequest) {
        try {
            Boolean result = couponService.lockUserCoupon(lockUserCouponRequest);
            return JsonResult.buildSuccess(result);
        } catch (MarketBizException e) {
            log.error("biz error", e);
            return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
        } catch (Exception e) {
            log.error("system error", e);
            return JsonResult.buildError(e.getMessage());
        }
    }
    ...
}

@Service
public class CouponServiceImpl implements CouponService {
    ...
    //锁定用户优惠券
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean lockUserCoupon(LockUserCouponRequest lockUserCouponRequest) {
        //检查入参
        checkLockUserCouponRequest(lockUserCouponRequest);
        String userId = lockUserCouponRequest.getUserId();
        String couponId = lockUserCouponRequest.getCouponId();
        CouponDO couponDO = couponDAO.getUserCoupon(userId, couponId);
        if (couponDO == null) {
            throw new MarketBizException(MarketErrorCodeEnum.USER_COUPON_IS_NULL);
        }
        //判断优惠券是否已经使用了
        if (CouponUsedStatusEnum.USED.getCode().equals(couponDO.getUsed())) {
            throw new MarketBizException(MarketErrorCodeEnum.USER_COUPON_IS_USED);
        }
        couponDO.setUsed(CouponUsedStatusEnum.USED.getCode());
        couponDO.setUsedTime(new Date());
        couponDAO.updateById(couponDO);
        return true;
    }
    ...
}

@DubboService(version = "1.0.0", interfaceClass = InventoryApi.class, retries = 0)
public class InventoryApiImpl implements InventoryApi {
    @Autowired
    private InventoryService inventoryService;


    //锁定商品库存
    @Override
    public JsonResult<Boolean> lockProductStock(LockProductStockRequest lockProductStockRequest) {
        try {
            Boolean result = inventoryService.lockProductStock(lockProductStockRequest);
            return JsonResult.buildSuccess(result);
        } catch (InventoryBizException e) {
            log.error("biz error", e);
            return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
        } catch (Exception e) {
            log.error("system error", e);
            return JsonResult.buildError(e.getMessage());
        }
    }
    ...
}

@Service
public class InventoryServiceImpl implements InventoryService {
    ...
    //锁定商品库存
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean lockProductStock(LockProductStockRequest lockProductStockRequest) {
        //检查入参
        checkLockProductStockRequest(lockProductStockRequest);
        List<LockProductStockRequest.OrderItemRequest> orderItemRequestList = lockProductStockRequest.getOrderItemRequestList();
        for (LockProductStockRequest.OrderItemRequest orderItemRequest : orderItemRequestList) {
            String skuCode = orderItemRequest.getSkuCode();
            ProductStockDO productStockDO = productStockDAO.getBySkuCode(skuCode);
            if (productStockDO == null) {
                throw new InventoryBizException(InventoryErrorCodeEnum.PRODUCT_SKU_STOCK_ERROR);
            }
            Integer saleQuantity = orderItemRequest.getSaleQuantity();
            //执行库存扣减,并需要解决防止超卖的问题
            int nums = productStockDAO.lockProductStock(skuCode, saleQuantity);
            if (nums <= 0) {
                throw new InventoryBizException(InventoryErrorCodeEnum.LOCK_PRODUCT_SKU_STOCK_ERROR);
            }
        }
        return true;
    }
    ...
}

(5)新增订单到数据库

scss 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //新增订单数据到数据库
    private void addNewOrder(CreateOrderRequest createOrderRequest, List<ProductSkuDTO> productSkuList, CalculateOrderAmountDTO calculateOrderAmountDTO) {
        //封装新订单数据
        NewOrderDataHolder newOrderDataHolder = new NewOrderDataHolder();
        //生成主订单
        FullOrderData fullMasterOrderData = addNewMasterOrder(createOrderRequest, productSkuList, calculateOrderAmountDTO);
        //封装主订单数据到NewOrderData对象中
        newOrderDataHolder.appendOrderData(fullMasterOrderData);

        //如果存在多种商品类型,需要按商品类型进行拆单
        Map<Integer, List<ProductSkuDTO>> productTypeMap = productSkuList.stream().collect(Collectors.groupingBy(ProductSkuDTO::getProductType));
        if (productTypeMap.keySet().size() > 1) {
            for (Integer productType : productTypeMap.keySet()) {
                //生成子订单
                FullOrderData fullSubOrderData = addNewSubOrder(fullMasterOrderData, productType);
                //封装子订单数据到NewOrderData对象中
                newOrderDataHolder.appendOrderData(fullSubOrderData);
            }
        }

        //保存订单到数据库
        //订单信息
        List<OrderInfoDO> orderInfoDOList = newOrderDataHolder.getOrderInfoDOList();
        if (!orderInfoDOList.isEmpty()) {
            orderInfoDAO.saveBatch(orderInfoDOList);
        }
        //订单条目
        List<OrderItemDO> orderItemDOList = newOrderDataHolder.getOrderItemDOList();
        if (!orderItemDOList.isEmpty()) {
            orderItemDAO.saveBatch(orderItemDOList);
        }
        //订单配送信息
        List<OrderDeliveryDetailDO> orderDeliveryDetailDOList = newOrderDataHolder.getOrderDeliveryDetailDOList();
        if (!orderDeliveryDetailDOList.isEmpty()) {
            orderDeliveryDetailDAO.saveBatch(orderDeliveryDetailDOList);
        }
        //订单支付信息
        List<OrderPaymentDetailDO> orderPaymentDetailDOList = newOrderDataHolder.getOrderPaymentDetailDOList();
        if (!orderPaymentDetailDOList.isEmpty()) {
            orderPaymentDetailDAO.saveBatch(orderPaymentDetailDOList);
        }
        //订单费用信息
        List<OrderAmountDO> orderAmountDOList = newOrderDataHolder.getOrderAmountDOList();
        if (!orderAmountDOList.isEmpty()) {
            orderAmountDAO.saveBatch(orderAmountDOList);
        }
        //订单费用明细
        List<OrderAmountDetailDO> orderAmountDetailDOList = newOrderDataHolder.getOrderAmountDetailDOList();
        if (!orderAmountDetailDOList.isEmpty()) {
            orderAmountDetailDAO.saveBatch(orderAmountDetailDOList);
        }
        //订单状态变更日志信息
        List<OrderOperateLogDO> orderOperateLogDOList = newOrderDataHolder.getOrderOperateLogDOList();
        if (!orderOperateLogDOList.isEmpty()) {
            orderOperateLogDAO.saveBatch(orderOperateLogDOList);
        }
        //订单快照数据
        List<OrderSnapshotDO> orderSnapshotDOList = newOrderDataHolder.getOrderSnapshotDOList();
        if (!orderSnapshotDOList.isEmpty()) {
            orderSnapshotDAO.saveBatch(orderSnapshotDOList);
        }
    }

    //新增主订单信息订单
    private FullOrderData addNewMasterOrder(CreateOrderRequest createOrderRequest, List<ProductSkuDTO> productSkuList, CalculateOrderAmountDTO calculateOrderAmountDTO) {
        NewOrderBuilder newOrderBuilder = new NewOrderBuilder(createOrderRequest, productSkuList, calculateOrderAmountDTO, orderProperties);
        FullOrderData fullOrderData = newOrderBuilder.buildOrder()
            .buildOrderItems()
            .buildOrderDeliveryDetail()
            .buildOrderPaymentDetail()
            .buildOrderAmount()
            .buildOrderAmountDetail()
            .buildOperateLog()
            .buildOrderSnapshot()
            .build();
        //订单信息
        OrderInfoDO orderInfoDO = fullOrderData.getOrderInfoDO();
        //订单条目信息
        List<OrderItemDO> orderItemDOList = fullOrderData.getOrderItemDOList();
        //订单费用信息
        List<OrderAmountDO> orderAmountDOList = fullOrderData.getOrderAmountDOList();
        //补全地址信息
        OrderDeliveryDetailDO orderDeliveryDetailDO = fullOrderData.getOrderDeliveryDetailDO();
        String detailAddress = getDetailAddress(orderDeliveryDetailDO);
        orderDeliveryDetailDO.setDetailAddress(detailAddress);
        //补全订单状态变更日志
        OrderOperateLogDO orderOperateLogDO = fullOrderData.getOrderOperateLogDO();
        String remark = "创建订单操作0-10";
        orderOperateLogDO.setRemark(remark);
        //补全订单商品快照信息
        List<OrderSnapshotDO> orderSnapshotDOList = fullOrderData.getOrderSnapshotDOList();
        for (OrderSnapshotDO orderSnapshotDO : orderSnapshotDOList) {
            //优惠券信息
            if (orderSnapshotDO.getSnapshotType().equals(SnapshotTypeEnum.ORDER_COUPON.getCode())) {
                ...
            }
            //订单费用信息
            else if (orderSnapshotDO.getSnapshotType().equals(SnapshotTypeEnum.ORDER_AMOUNT.getCode())) {
                orderSnapshotDO.setSnapshotJson(JsonUtil.object2Json(orderAmountDOList));
            }
            //订单条目信息
            else if (orderSnapshotDO.getSnapshotType().equals(SnapshotTypeEnum.ORDER_ITEM.getCode())) {
                orderSnapshotDO.setSnapshotJson(JsonUtil.object2Json(orderItemDOList));
            }
        }
        return fullOrderData;
    }
    ...
}

(6)发送延迟消息到MQ

typescript 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //发送支付订单超时延迟消息,用于支付超时自动关单
    private void sendPayOrderTimeoutDelayMessage(CreateOrderRequest createOrderRequest) {
        PayOrderTimeoutDelayMessage message = new PayOrderTimeoutDelayMessage();
        message.setOrderId(createOrderRequest.getOrderId());
        message.setBusinessIdentifier(createOrderRequest.getBusinessIdentifier());
        message.setCancelType(OrderCancelTypeEnum.TIMEOUT_CANCELED.getCode());
        message.setUserId(createOrderRequest.getUserId());
        message.setOrderType(createOrderRequest.getOrderType());
        message.setOrderStatus(OrderStatusEnum.CREATED.getCode());
        String msgJson = JsonUtil.object2Json(message);
        defaultProducer.sendMessage(
            RocketMqConstant.PAY_ORDER_TIMEOUT_DELAY_TOPIC, 
            msgJson,
            RocketDelayedLevel.DELAYED_30m, 
            "支付订单超时延迟消息"
        );
    }
    ...
}

@Component
public class DefaultProducer {
    private final DefaultMQProducer producer;

    @Autowired
    public DefaultProducer(RocketMQProperties rocketMQProperties) {
        producer = new DefaultMQProducer(RocketMqConstant.ORDER_DEFAULT_PRODUCER_GROUP);
        producer.setNamesrvAddr(rocketMQProperties.getNameServer());
        start();
    }

    //对象在使用之前必须要调用一次,只能初始化一次
    public void start() {
        try {
            this.producer.start();
        } catch (MQClientException e) {
            log.error("producer start error", e);
        }
    }
    ...

    //发送消息
    public void sendMessage(String topic, String message, Integer delayTimeLevel, String type) {
        Message msg = new Message(topic, message.getBytes(StandardCharsets.UTF_8));
        try {
            if (delayTimeLevel > 0) {
                msg.setDelayTimeLevel(delayTimeLevel);
            }
            SendResult send = producer.send(msg);
            if (SendStatus.SEND_OK == send.getSendStatus()) {
                log.info("发送MQ消息成功, type:{}, message:{}", type, message);
            } else {
                throw new OrderBizException(send.getSendStatus().toString());
            }
        } catch (Exception e) {
            log.error("发送MQ消息失败:", e);
            throw new OrderBizException(OrderErrorCodeEnum.SEND_MQ_FAILED);
        }
    }
    ...
}

2.生成订单链路中可能会出现数据不一致的问题

(1)生成订单链路中的不一致问题

(2)分布式事务场景下的典型问题

(3)解决方案对比

(1)生成订单链路中的不一致问题

在更新优惠券本地事务、更新库存本地事务、插入订单数据本地事务中,可能会出现优惠券和库存都已经更新成功了,但订单数据却插入失败,此时就会出现数据不一致的问题。

(2)分布式事务场景下的典型问题

一.部分成功问题

优惠券锁定成功但库存锁定失败

库存锁定成功但订单创建失败

订单创建成功但MQ消息发送失败

二.中间状态可见性问题

其他系统可能看到中间不一致状态

重复操作导致数据异常

三.补偿机制缺失问题

缺乏自动化的补偿机制

人工干预成本高

(3)解决方案对比

3.Seata AT模式下的分布式事务的原理

(1)分布式事务原理分析

(2)核心组件交互图

(3)关键处理说明

(1)分布式事务原理分析

说明一: 需要部署一个Seata Server服务器。

说明二: 在各个服务的分支事务的数据库中,需要新增一张undo_log表,用来记录各个服务的分支事务失败时可以执行的回滚SQL。

说明三: 当入口服务开启一个分布式事务时,需要向Seata Server开启一个全局事务。

说明四: 各个服务对其分支事务的执行情况会同步给Seata Server服务器。

说明五: 当Seata Server发现某分支事务失败时,便会通知各服务进行事务回滚。

说明六: 当各个服务进行事务回滚时,会从undo_log表查出对应SQL去执行。

(2)核心组件交互图

(3)关键处理说明

一.事务协调器(TC)

二.资源管理器(RM)

三.undo_log表结构增强

四.全局锁冲突处理

五.异步重试机制

六.关键路径优化

七.全局锁优化

makefile 复制代码
说明一: 全局事务ID生成
基于Snowflake算法生成全局唯一XID
格式:IP+Port+Timestamp+Sequence

说明二: 分支事务注册
每个本地事务需要向TC注册分支
分支ID(Branch ID)与XID关联

说明三: Undo Log存储优化
采用压缩存储减少IO
异步清理机制

说明四: 全局锁管理
基于数据库实现的分布式锁
锁超时自动释放机制

一.事务协调器(TC)

ini 复制代码
# 核心职责: 全局事务状态管理、分支事务协调、全局锁管理
# 生产建议: TC服务端配置(seata-server.properties)
store.mode=db # 高可用模式建议用db/nacos
server.max.commit.retry.timeout=120000
server.max.rollback.retry.timeout=120000

二.资源管理器(RM)

csharp 复制代码
# 关键行为: 数据镜像捕获(前镜像/后镜像) + 本地事务与全局锁关联
# 优化配置: application.yml
seata:
  client:
    rm:
      report.retry.count: 5 # 分支状态上报重试次数
      lock.retry.internal: 10ms # 锁重试间隔
      lock.retry.times: 30 # 锁重试次数

三.undo_log表结构增强

less 复制代码
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL COMMENT '分支事务ID',
  `xid` varchar(128) NOT NULL COMMENT '全局事务ID',
  `context` varchar(128) NOT NULL COMMENT '上下文',
  `rollback_info` longblob NOT NULL COMMENT '回滚信息',
  `log_status` int(11) NOT NULL COMMENT '状态',
  `log_created` datetime(6) NOT NULL COMMENT '创建时间',
  `log_modified` datetime(6) NOT NULL COMMENT '修改时间',
  `ext` varchar(100) DEFAULT NULL COMMENT '扩展字段',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
  KEY `idx_log_created` (`log_created`) # 新增索引加速清理
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

四.全局锁冲突处理

java 复制代码
// 在业务代码中添加锁冲突处理
@GlobalTransactional(lockRetryInternal = 100, lockRetryTimes = 5)
public void businessMethod() {
    try {
        // 业务逻辑
    } catch (LockConflictException e) {
        // 1.记录告警日志
        log.warn("Global lock conflict detected", e);
        // 2.触发补偿机制
        compensationService.scheduleRetry();
        throw e;
    }
}

五.异步重试机制

六.关键路径优化

ini 复制代码
# 前镜像/后镜像查询合并(步骤5+7)
SELECT * FROM product WHERE id=? FOR UPDATE /* 同时作为前后镜像 */

# 批量undo log写入(步骤8/20)
// 使用MyBatis批量插入
undoLogMapper.batchInsert(undoLogs); 

七.全局锁优化

ini 复制代码
# 1.细粒度锁设计(按SKU分片)
# 2.锁超时动态调整
SeataBeanPostProcessor.setGlobalLockTimeout(skuId, 3000);
csharp 复制代码
# 建议配合Seata 1.6.0+版本使用, 其新增的全局锁优化算法可减少约40%的锁冲突概率.

# 全局锁优化算法核心设计一: 分段哈希锁(Segment Hash Lock)
# 优化点: 
# 将单锁竞争改为多段锁竞争, 类似ConcurrentHashMap的分段锁设计
# 不同资源ID通过哈希路由到不同段, 减少无关资源的锁竞争


// 新锁结构设计(Seata 1.6.0+)
public class SegmentLockManager {
    private final LockSegment[] segments; // 锁分段数组

    // 分段数默认16(可配置)
    public SegmentLockManager(int segmentCount) {
        this.segments = new LockSegment[segmentCount];
        for (int i = 0; i < segmentCount; i++) {
            segments[i] = new LockSegment();
        }
    }

    // 哈希路由算法(替代原单一全局锁)
    public LockSegment getSegment(String resourceId) {
        int hash = Math.abs(resourceId.hashCode());
        return segments[hash % segments.length];
    }
}


# 全局锁优化算法核心设计二: 锁获取策略升级
# 优化效果: 锁获取成功率提升32%

/* 原版(1.6.0前) */
SELECT * FROM lock_table WHERE resource_id = 'sku_001' FOR UPDATE;

/* 优化版(1.6.0+) */
-- 1. 先尝试非阻塞获取(避免线程挂起)
SELECT * FROM lock_table WHERE resource_id = 'sku_001' AND xid != 'current_xid' SKIP LOCKED LIMIT 1;
-- 2. 无冲突时直接获取锁
INSERT INTO lock_table(resource_id, xid) VALUES ('sku_001', 'current_xid');


# 全局锁优化算法核心设计三: 死锁检测算法升级
# 改进点: 
# 采用等待图(Wait-for Graph)算法替代简单超时
# 检测到环路后主动中断代价最小的事务(基于事务年龄和操作量)

4.Seata AT模式下的分布式事务的读写隔离原理

(1)原理说明

(2)隔离级别对照表

(3)全局锁获取流程

(1)原理说明

为了避免有其他线程修改某数据后又进行回滚,就一定要加本地锁 + 全局锁。本地锁是为了避免当前机器的其他线程对数据进行修改并回滚,全局锁是为了避免分布式机器的线程对数据进行修改并回滚。

服务A在更新某数据之前,需要先获取本地锁。服务A在成功获取本地锁之后,需要插入undo log数据。接着,服务A需要向Seata Server服务器获取全局锁。服务A在成功获取全局锁之后,会提交本地事务并释放本地锁。

如果服务A对服务B进行RPC调用并提交其本地事务,则继续按前面的步骤处理服务B的数据更新。当服务B的本地事务也提交完成后,不需要继续执行其他分支事务了,服务A便可以提交分布式事务,并释放全局锁。

分布式事务的全链路在执行完毕前,对应数据的全局锁是不会释放的。

(2)隔离级别对照表

(3)全局锁获取流程

java 复制代码
public class DefaultLockManager implements LockManager {
    public boolean acquireLock(BranchSession branchSession) {
        // 1.检查是否已经持有锁
        if (isLocked(branchSession.getXid(), branchSession.getResourceId())) {
            return true;
        }

        // 2.尝试获取全局锁
        boolean result = doAcquireLock(branchSession);

        // 3.获取失败时重试
        if (!result && retryTimes > 0) {
            for (int i = 0; i < retryTimes; i++) {
                Thread.sleep(retryInterval);
                result = doAcquireLock(branchSession);
                if (result) break;
            }
        }

        // 4.记录锁获取结果
        if (result) {
            addLockRecord(branchSession);
        }

        return result;
    }

    private boolean doAcquireLock(BranchSession branchSession) {
        // 实现数据库层面的锁获取
        Connection conn = DataSourceUtils.getConnection(dataSource);
        try {
            PreparedStatement ps = conn.prepareStatement(
                "SELECT * FROM lock_table WHERE xid = ? AND branch_id = ? FOR UPDATE");
            // 设置参数并执行
            // ...
            return true;
        } catch (SQLException e) {
            if (isDeadLock(e)) {
                // 死锁处理
                return false;
            }
            throw new LockConflictException("Acquire lock failed", e);
        }
    }
}

5.Seata AT模式下的死锁问题以及超时机制

(1)死锁问题分析

(2)死锁检测优化

(3)死锁处理流程

(1)死锁问题分析

Seata的一条事务链路里,每一个事务都会按如下顺序执行:首先获取本地锁更新本地数据,然后插入undo log记录,接着获取本地数据对应的全局锁,最后提交本地事务并释放本地锁。

Seata的一条事务链路里,一个事务执行完就会继续执行下一个事务。如果事务链路里的所有事务都执行完成了,那么就提交事务,并释放全局锁。如果某个事务需要回滚,那么就需要获取该事务本地数据的本地锁,然后获取undo log记录生成逆向操作的SQL语句来进行补偿和更新,补偿完毕才能释放本地数据的全局锁。

由于Seata AT模式的写隔离是通过本地数据的全局锁来实现的,所以写隔离的过程中,就涉及到了本地数据的本地锁和全局锁两把锁,这时候就很容易导致出现死锁的情况。

比如当事务1的分支事务提交数据1的本地事务后,会释放数据1的本地锁。此时事务2的分支事务就可以获取数据1的本地锁,但要等待获取事务1释放数据1的全局锁后,才能释放数据1的本地锁。如果事务1的后续分支事务出现异常需要进行回滚,那么事务1就需要获取数据1的本地锁,执行回滚补偿处理。事务1执行完分支事务的回滚补偿处理后,才能释放数据1的全局锁。

于是就出现了这样的死锁场景:事务1对数据1的回滚,占用了数据1的全局锁,需等待获取数据1的本地锁。事务2对数据1的更新,占用了数据1的本地锁,需等待获取数据1的全局锁。

Seata为了解决这个问题,会引入等待全局锁的超时机制。如果事务2在等待数据1的全局锁时出现超时,就会释放其占用的本地锁。从而让事务1能获取到数据1的本地锁,完成其事务操作,而不用一直等待。

(2)死锁检测优化

scss 复制代码
一.等待图(WFG)检测
构建资源等待图
定期检测图中环

二.超时策略分级
全局锁获取超时(默认10s)
分支事务执行超时(默认60s)
全局事务超时(默认60s)

(3)死锁处理流程

makefile 复制代码
步骤一: 检测到死锁或超时
步骤二: 选择牺牲者(victim)事务
步骤三: 记录死锁日志
步骤四: 发送告警通知
步骤五: 触发事务回滚
步骤六: 释放所有持有的锁

6.Seata AT模式下的读写隔离机制的影响

由于全局锁的存在,会严重影响Seata AT分布式事务的并发吞吐量。所以除非是金融级别的系统,才会使用像Seata AT模式这么严格的事务来保证数据的强一致性。

当然,通常情况下分布式事务基本都是更新不同的数据。只要更新不同的数据,那么Seata AT分布式事务也不会出现全局锁等待。只有一些特殊情况下,才可能会出现大量分布式事务更新同一条数据。当使用Seata AT分布式事务时,特别注意尽量不要让全局锁等待。

如果不使用全局锁,那么Seata AT模式的分布式事务就会出现写未提交。就可能出现分支事务更新失败时无法回滚,因为回滚的数据已被覆盖。

Seata AT模式的分布式事务默认是读未提交的,即分布式事务在未提交前,分支事务更新的数据是可被其他事务读到的。

此外,很多公司都是使用基于RocketMQ的柔性事务来实现分布式事务。

7.生成订单链路使用Seata AT模式的具体步骤

(1)生成订单链路中分布式事务的主要分支事务

(2)订单系统 + 优惠券系统 + 商品库存系统都需要在pom.xml文件中引入Seata

(3)订单系统的生成订单接口作为分布式事务入口需添加@GlobalTransactional注解开启全局事务

(4)优惠券系统的锁定优惠券接口需添加Spring的事务注解@Transactional

(5)商品库存系统的锁定库存接口需添加Spring的事务注解@Transactional

(6)各分支事务操作的数据库需要添加undo log表

(1)生成订单链路中分布式事务的主要分支事务

分布式事务入口:订单系统的生成订单接口

分支事务1:优惠券系统锁定优惠券

分支事务2:商品库存系统锁定商品库存

分支事务3:订单系统创建订单数据

(2)订单系统 + 优惠券系统 + 商品库存系统都需要在pom.xml文件中引入Seata

xml 复制代码
<!-- 引入seata整合分布式事务 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 跟安装的seata-server需要保持版本一致 -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

(3)订单系统的生成订单接口作为分布式事务入口需添加@GlobalTransactional注解开启全局事务

通过添加Seata提供的注解@GlobalTransactional来开启全局事务。

scss 复制代码
@Service
public class OrderServiceImpl implements OrderService {
    ...
    //提交订单/生成订单接口
    //@param createOrderRequest 提交订单请求入参
    //@return 订单号
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public CreateOrderDTO createOrder(CreateOrderRequest createOrderRequest) {
        //1.入参检查
        checkCreateOrderRequestParam(createOrderRequest);
        //2.风控检查
        checkRisk(createOrderRequest);
        //3.获取商品信息
        List<ProductSkuDTO> productSkuList = listProductSkus(createOrderRequest);
        //4.计算订单价格
        CalculateOrderAmountDTO calculateOrderAmountDTO = calculateOrderAmount(createOrderRequest, productSkuList);
        //5.验证订单实付金额
        checkRealPayAmount(createOrderRequest, calculateOrderAmountDTO);
        //6.锁定优惠券
        lockUserCoupon(createOrderRequest);
        //7.锁定商品库存
        lockProductStock(createOrderRequest);
        //8.生成订单到数据库
        addNewOrder(createOrderRequest, productSkuList, calculateOrderAmountDTO);
        //9.发送订单延迟消息用于支付超时自动关单
        sendPayOrderTimeoutDelayMessage(createOrderRequest);
        //返回订单信息
        CreateOrderDTO createOrderDTO = new CreateOrderDTO();
        createOrderDTO.setOrderId(createOrderRequest.getOrderId());
        return createOrderDTO;
    }
    ...
}

(4)优惠券系统的锁定优惠券接口需添加Spring的事务注解@Transactional

通过添加Spring提供的@Transactional注解来开启本地事务。Seata会代理Spring的事务,进行本地锁申请 + undo log写入 + 全局锁请求 + 提交/回滚本地事务等操作。

typescript 复制代码
@Service
public class CouponServiceImpl implements CouponService {
    ...
    //锁定用户优惠券
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean lockUserCoupon(LockUserCouponRequest lockUserCouponRequest) {
        //检查入参
        checkLockUserCouponRequest(lockUserCouponRequest);
        String userId = lockUserCouponRequest.getUserId();
        String couponId = lockUserCouponRequest.getCouponId();
        CouponDO couponDO = couponDAO.getUserCoupon(userId, couponId);
        if (couponDO == null) {
            throw new MarketBizException(MarketErrorCodeEnum.USER_COUPON_IS_NULL);
        }
        //判断优惠券是否已经使用了
        if (CouponUsedStatusEnum.USED.getCode().equals(couponDO.getUsed())) {
            throw new MarketBizException(MarketErrorCodeEnum.USER_COUPON_IS_USED);
        }
        couponDO.setUsed(CouponUsedStatusEnum.USED.getCode());
        couponDO.setUsedTime(new Date());
        couponDAO.updateById(couponDO);
        return true;
    }
    ...
}

(5)商品库存系统的锁定库存接口需添加Spring的事务注解@Transactional

通过添加Spring提供的@Transactional注解来开启本地事务。Seata会代理Spring的事务,进行本地锁申请 + undo log写入 + 全局锁请求 + 提交/回滚本地事务等操作。

ini 复制代码
@Service
public class InventoryServiceImpl implements InventoryService {
    ...
    //锁定商品库存
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean lockProductStock(LockProductStockRequest lockProductStockRequest) {
        //检查入参
        checkLockProductStockRequest(lockProductStockRequest);
        List<LockProductStockRequest.OrderItemRequest> orderItemRequestList = lockProductStockRequest.getOrderItemRequestList();
        for (LockProductStockRequest.OrderItemRequest orderItemRequest : orderItemRequestList) {
            String skuCode = orderItemRequest.getSkuCode();
            ProductStockDO productStockDO = productStockDAO.getBySkuCode(skuCode);
            if (productStockDO == null) {
                throw new InventoryBizException(InventoryErrorCodeEnum.PRODUCT_SKU_STOCK_ERROR);
            }
            Integer saleQuantity = orderItemRequest.getSaleQuantity();
            //执行库存扣减,并需要解决防止超卖的问题
            int nums = productStockDAO.lockProductStock(skuCode, saleQuantity);
            if (nums <= 0) {
                throw new InventoryBizException(InventoryErrorCodeEnum.LOCK_PRODUCT_SKU_STOCK_ERROR);
            }
        }
        return true;
    }
    ...
}

(6)各分支事务操作的数据库需要添加undo log表

订单系统的数据库 + 优惠券系统的数据库 + 库存系统的数据库,都需要添加如下一张undo_log表,提供给Seata使用。

r 复制代码
CREATE TABLE `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,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

8.生成订单链路使用Seata AT模式时的原理流程

(1)undo log的生成

(2)生成订单链路使用Seata AT模式时的原理流程

(1)undo log的生成

首先根据更新字段查询出前镜像Before Image,然后进行本地事务的更新,接着根据更新字段查询出更新后的后镜像After Image,这样就可以根据前后镜像 + 执行SQL语句的基本信息拼成一条undo log。

分布式事务入口会向Seata Server注册一个全局事务XID,分支事务会向Seata Server注册一个分支事务Branch ID。

如下是Seata官网提供的一条undo log数据示例:

json 复制代码
{
    "branchId": 641789253,
    "undoItems": [{
        "afterImage": {
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "GTS"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"
        },
        "beforeImage": {
            "rows": [{
                "fields": [{
                    "name": "id",
                    "type": 4,
                    "value": 1
                }, {
                    "name": "name",
                    "type": 12,
                    "value": "TXC"
                }, {
                    "name": "since",
                    "type": 12,
                    "value": "2014"
                }]
            }],
            "tableName": "product"
        },
        "sqlType": "UPDATE"
    }],
    "xid": "xid:xxx"
}

(2)生成订单链路使用Seata AT模式时的原理流程

bash 复制代码
# 关键点说明
# 全局事务生命周期:
#    开始:TM向TC注册全局事务记录
#    进行:RM向TC注册分支事务
#    结束:TM向TC发起全局提交/回滚
# 资源管理:
#    每个RM需要配置代理数据源
#    本地事务由Seata自动代理
# 异常处理流程:
#    业务异常:触发全局回滚
#    系统异常:根据配置决定重试或回滚
#    超时异常:自动触发回滚

9.生成订单链路使用Seata AT模式时的并发问题

(1)在锁定优惠券环节不存在并发获取全局锁问题

(2)在锁定库存环节存在并发获取全局锁问题

(3)在生成订单环节不存在并发获取全局锁问题

生成订单链路中的分布式事务环节在于:锁定优惠券 + 锁定库存 + 生成订单。

(1)在锁定优惠券环节不存在并发获取全局锁问题

每个用户都会有属于自己的优惠券。日常情况下,都是不同的用户使用不同的优惠券购买商品,所以并不会出现并发获取同一条优惠券数据的全局锁的情况。

(2)在锁定库存环节存在并发获取全局锁问题

对于爆品或秒杀,大量用户可能都会基于某商品进行下单扣减库存,因此会出现并发获取同一个SKU数据的全局锁。

第一个获取到某SKU数据的全局锁的事务,在进行生成订单环节由于需要插入多条SQL,所以可能会比较耗时,从而导致并发等待获取该SKU数据的全局锁的其他事务等待时间过长。

(3)在生成订单环节不存在并发获取全局锁问题

生成订单环节,涉及到多条SQL的插入操作,也存在耗时的风险。

10.生成订单链路如何解决库存全局锁争用问题

(1)锁定库存时的全局锁争用问题分析

(2)库存分桶方案+柔性事务方案+Seata事务方案

(1)锁定库存时的全局锁争用问题分析

一个商品SKU就对应一条库存数据记录,如果大量用户同时购买一个商品SKU,必然导致多个分布式事务都去竞争和等待同一个SKU库存数据的全局锁。

(2)库存分桶方案+柔性事务方案+Seata事务方案

一.库存分桶方案

一般一个SKU就一条库存数据,在库存分桶方案下,一个SKU会有多条库存数据。比如1万的库存可分为1000条库存数据,每条库存数据可扣库存为10。每次扣减库存时,按照一定的规则和算法,选择一个库存分桶去进行扣减。

二.RocketMQ柔性事务方案

通过RocketMQ柔性事务方案来替换Seata刚性事务方案。在互联网公司里,一般的业务系统,都使用RocketMQ柔性事务。大多情况下,RocketMQ柔性事务都能确保数据是一致的。

刚性事务指的是分支事务出现异常或者失败,则全局回滚。柔性事务指的是分支事务出现异常或者失败,则不断重试直到成功。

使用RocketMQ柔性事务方案,需要确保消息成功被投递到RocketMQ。

三.使用没有全局锁的分布式事务方案,比如TCC

Seata支持AT、TCC、Saga、XA这几种事务方案。对于生成订单链路的建议是使用混合的分布式事务方案:锁定营销使用AT模式 + 锁定库存使用TCC模式。

scss 复制代码
# 库存分桶实现方案
public class InventoryBucketService {
    // 分桶数量配置
    @Value("${inventory.bucket.count:100}")
    private int bucketCount;

    public boolean reduceStock(String skuCode, int quantity) {
        // 1.获取所有可用分桶
        List<InventoryBucketDO> buckets = inventoryBucketDAO.listAvailableBuckets(skuCode);

        // 2.计算需要锁定的分桶数量
        int bucketsToLock = calculateBucketsToLock(quantity, buckets);

        // 3.随机选择分桶
        List<InventoryBucketDO> selectedBuckets = selectRandomBuckets(buckets, bucketsToLock);

        // 4.尝试锁定分桶
        for (InventoryBucketDO bucket : selectedBuckets) {
            int affected = inventoryBucketDAO.reduceBucketStock(
                bucket.getId(), 
                calculateBucketReduceQuantity(bucket, quantity));

            if (affected > 0) {
                quantity -= bucket.getAvailableQuantity();
                if (quantity <= 0) break;
            }
        }

        // 5.检查是否全部锁定成功
        if (quantity > 0) {
            // 触发补偿逻辑
            compensateStockReduction(skuCode, quantity);
            return false;
        }

        return true;
    }

    // 分桶路由策略
    private List<InventoryBucketDO> selectRandomBuckets(List<InventoryBucketDO> buckets, int count) {
        // 实现随机+权重选择算法
        // ...
    }
}
typescript 复制代码
# 混合事务模式设计
public class OrderService {
    // 使用TCC模式锁定库存
    @GlobalTransactional
    public CreateOrderDTO createOrder(CreateOrderRequest request) {
        // 1.参数校验等前置操作
        validateRequest(request);

        // 2.TCC模式锁定库存
        inventoryTccService.prepareLockStock(
            request.getOrderId(), 
            request.getSkuCode(), 
            request.getQuantity());

        // 3.AT模式锁定优惠券
        couponService.lockUserCoupon(request.getUserId(), request.getCouponId());

        // 4.创建订单
        createOrderInDB(request);

        return buildResult(request);
    }
}

// TCC库存服务实现
public class InventoryTccServiceImpl implements InventoryTccService {
    @Override
    @Transactional
    public boolean prepareLockStock(String orderId, String skuCode, int quantity) {
        // 记录预备操作
        InventoryLockDO lock = new InventoryLockDO();
        lock.setOrderId(orderId);
        lock.setSkuCode(skuCode);
        lock.setQuantity(quantity);
        lock.setStatus(InventoryLockStatus.PREPARED);
        inventoryLockDAO.insert(lock);

        // 实际库存预留
        int affected = inventoryDAO.prepareReduceStock(skuCode, quantity);
        return affected > 0;
    }

    @Override
    @Transactional
    public boolean commitLockStock(String orderId) {
        // 更新锁定状态为确认
        inventoryLockDAO.updateStatus(orderId, InventoryLockStatus.CONFIRMED);
        return true;
    }

    @Override
    @Transactional
    public boolean cancelLockStock(String orderId) {
        // 释放预留库存
        InventoryLockDO lock = inventoryLockDAO.selectByOrderId(orderId);
        if (lock != null) {
            inventoryDAO.cancelReduceStock(lock.getSkuCode(), lock.getQuantity());
            inventoryLockDAO.updateStatus(orderId, InventoryLockStatus.CANCELLED);
        }
        return true;
    }
}
相关推荐
程序员码歌4 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
java坤坤5 小时前
GoLand 项目从 0 到 1:第八天 ——GORM 命名策略陷阱与 Go 项目启动慢问题攻坚
开发语言·后端·golang
元清加油5 小时前
【Golang】:函数和包
服务器·开发语言·网络·后端·网络协议·golang
bobz9656 小时前
GPT-4.1 对比 GPT-4o
后端
Java小白程序员6 小时前
Spring Framework :IoC 容器的原理与实践
java·后端·spring
小小愿望7 小时前
前端无法获取响应头(如 Content-Disposition)的原因与解决方案
前端·后端
追逐时光者7 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 50 期(2025年8.11-8.17)
后端·.net
杨DaB8 小时前
【SpringBoot】Swagger 接口工具
java·spring boot·后端·restful·swagger
why技术8 小时前
也是震惊到我了!家里有密码锁的注意了,这真不是 BUG,是 feature。
后端·面试