RabbitMQ+redis+Redisson分布式锁+seata实现订单服务

引言

订单服务涉及许多方面,分布式事务,分布式锁,例如订单超时未支付要取消订单,订单如何防止重复提交,如何防止超卖、这里都会使用到。

  • 开启分布式事务可以保证跨多个服务的数据操作的一致性和完整性,
  • 使用分布式锁可以确保在同一时间只有一个操作能够成功执行,避免并发引起的问题。

订单流程(只展示重要的内容,具体可以到源码查看)

订单确认

public OrderConfirmVO confirmOrder(Long skuId) {

Long memberId = SecurityUtils.getMemberId();

// 解决子线程无法获取HttpServletRequest请求对象中数据的问题,子线程指getOrderItemsFuture,getMemberAddressFuture,generateOrderTokenFuture
//feign远程调用会被拦截提取attributes并转发
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(attributes, true);

// 获取订单商品
//使用了 CompletableFuture 来实现异步执行获取订单项的操作
CompletableFuture<List<OrderItemDTO>> getOrderItemsFuture = CompletableFuture.supplyAsync(
	() -> this.getOrderItems(skuId, memberId), threadPoolExecutor)
.exceptionally(ex -> {
	log.error("Failed to get order items: {}", ex.toString());
	return null;
});

// 用户收货地址
CompletableFuture<List<MemberAddressDTO>> getMemberAddressFuture = CompletableFuture.supplyAsync(() -> {
	Result<List<MemberAddressDTO>> getMemberAddressResult = memberFeignClient.listMemberAddresses(memberId);
	if (Result.isSuccess(getMemberAddressResult)) {
		return getMemberAddressResult.getData();
	}
	return null;
}, threadPoolExecutor).exceptionally(ex -> {
	log.error("Failed to get addresses for memberId {} : {}", memberId, ex.toString());
	return null;
});

// 生成唯一令牌,防止重复提交(原理:提交会消耗令牌,令牌被消耗无法再次提交)
CompletableFuture<String> generateOrderTokenFuture = CompletableFuture.supplyAsync(() -> {
	String orderToken = this.generateTradeNo(memberId);
	redisTemplate.opsForValue().set(OrderConstants.ORDER_TOKEN_PREFIX + orderToken, orderToken);
	return orderToken;
}, threadPoolExecutor).exceptionally(ex -> {
	log.error("Failed to generate order token .");
	return null;
});
//CompletableFuture.allOf 方法,可以等待所有 CompletableFuture 对象都完成再进行后续操作,
// 确保获取和设置属性的操作都能够成功执行。这样可以避免程序出现异常,
CompletableFuture.allOf(getOrderItemsFuture, getMemberAddressFuture, generateOrderTokenFuture).join();
OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
orderConfirmVO.setOrderItems(getOrderItemsFuture.join());
orderConfirmVO.setAddresses(getMemberAddressFuture.join());
orderConfirmVO.setOrderToken(generateOrderTokenFuture.join());

log.info("Order confirm response for skuId {}: {}", skuId, orderConfirmVO);
return orderConfirmVO;
}

防止订单重复提交

通过生成唯一令牌解决,方法:

  • 根据自定义方法generateTradeNo生成订单号,将时间戳+3位随机数+5位id(由会员id组成,不够5位补0,超过5位保留后5位)组成订单号

      private String generateTradeNo(Long memberId) {
      //当 memberId 的位数小于 5 位时,使用 0 来填充位数不足的部分,如果 memberId 已经是 5 位数或更长,则不进行填充
      String userIdFilledZero = String.format("%05d", memberId);
      //超出五位的保留后五位
      String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
      // 在前面加上wxo(wx order)等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
      // 将时间戳+3位随机数+五位id组成商户订单号,规则参考自<a href="https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html">大众点评</a>
      return System.currentTimeMillis() + RandomUtil.randomNumbers(3) + fiveDigitsUserId;
      }
    
  • 根据订单防重提交令牌缓存键前缀+订单号作为key存入redis,值为订单号。

  • 订单提交的时候,会通过lua脚本验证redis是否含有该key,有则返回0,通过断言阻止重复提交,下面订单提交方法的代码含有该部分。

  • 扩展:CompletableFuture 来实现异步执行获取订单项的操作,可以提高响应速度,减少阻塞时间,同时通过CompletableFuture可以更方便地处理异常,每个异步操作可以独立地捕获和处理异常,避免异常传递给上层调用者,提高了系统的健壮性和容错性。CompletableFuture对于处理大量并发请求的场景非常重要,可以提升系统的性能和用户体验。

  • 涉及的自定义线程池(相关解释直接在代码注释上了)

      /**
       * 自定义订单线程池
       *
       */
      @Configuration
      @Slf4j
      public class ThreadPoolConfig {
    
      	@Bean
      	public ThreadPoolExecutor threadPoolEx
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二价亚铁.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值