在工作中,最近遇到了一个需求,需要对外暴露订单接口,在设计订单这部分代码的时候遇到一个问题,那就是你需要在创建订单的同时还要获取用户信息、获取商品信息、保存订单、做订单金额的逻辑计算。这些操作都是在一个接口中完成的,这就涉及到这些操作与操作之间会存在拉扯,存在线程阻塞的可能。
场景说明与问题所在
正如上面所说,在我这个接口中,需要同时处理:
- 用户信息
- 商品信息
- 订单保存、订单金额逻辑运算
- 保存订单
这里面涉及到的表:
- 用户表
- 商品表
- 订单表
- 订单-商品表
一个接口处理多个逻辑、多表需要注意的问题就是:线程阻塞!
**假设,当你的主线程正在执行查询用户信息的时候,由于响应时间过长了,那么涉及到查询商品信息的操作就会因为主线程一直没有执行完而一直处在等待中。**同理,线程卡到某一步的时候,下面的操作就别想执行了,一旦接口请求并发量上去了,这个问题就会变得很严重。
解决方案 | 思路
那么我的解决方案是什么呢?
思路与分析
首先我的想法是,把这部分接口操作中涉及订单部分的操作放到子线程中异步执行!这是电商系统中比较普遍的一种解决方案。
保存订单是一定会去执行的,是最有可能耗时的!在未来,我们还有可能在当前的接口中加新功能,比如:促销活动、库存服务等等。
技术与方案
基于长期的考虑,我选用的技术方案为:
CompletableFuture实现订单保存的异步编程CompletableFuture和Exceptionally实现主线程捕获子线程中的异常信息- 自定义线程池-利用池化思想去子线程重复使用
Demo 说明
上面既然提出了方案,用一个小的demo来说明一下怎么具体实施
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
//子线程执行代码
//保存订单的代码放这里...
//可能抛出异常
throw new CustomAsyncException("自定义异步任务异常");
});
future.exceptionally(ex->{
if (ex.getCause() instanceof CustomAsyncException){
log.error("捕获到自定义异常:{}",ex.getCause().getMessage());
}else {
log.error("捕获到异常信息{}",ex.getMessage());
}
return null;
});
System.out.println("主线程继续执行");
}
在这个简单的demo中,我们可以把 保存订单的代码放到异步执行CompletableFuture.runAsync(() -> {}里
在这个执行的过程中,我再通过 future.exceptionally(ex->{} 捕获一下子线程中可能抛出的异常。在这里最好针对订单创建自定义一下异常。
public class CustomAsyncException extends RuntimeException {
public CustomAsyncException(String message) {
super(message);
}
}
自定义异常以后,在第一段代码中,我们通过 if (ex.getCause() instanceof CustomAsyncException){}一旦判断出订单创建的异常抛出,那就马上捕获。
子线程捕获异常的意义何在呢?
子线程捕获异常以后可以通过接口层直接抛给前端那边,这样有利于前端知道后端哪部分出现问题。直接在主线程里捕获异常,异常只能抛到业务层,这会是个麻烦事,只有后端后台知道异常信息,前端却看不到。
实际操作
在公司代码中,我是直接在web层通过future.exceptionally(ex->{} 去监听捕获异常的
在子线程执行订单保存的时候,我选择自定义线程池,利用池化思想去子线程重复使用线程。