引言
订单服务涉及许多方面,分布式事务,分布式锁,例如订单超时未支付要取消订单,订单如何防止重复提交,如何防止超卖、这里都会使用到。
- 开启分布式事务可以保证跨多个服务的数据操作的一致性和完整性,
- 使用分布式锁可以确保在同一时间只有一个操作能够成功执行,避免并发引起的问题。
订单流程(只展示重要的内容,具体可以到源码查看)
订单确认
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