CompletableFuture原理与实践

背景

​ 随着订单量的持续上升,商家端提供了商家接单、配送等一系列核心功能,业务对系统吞吐量的要求也越来越高。商家端 API 服务是流量入口,所有商家端流量都会由其调度、聚合,对外面向商家提供功能接口对内调度各个下游服务获取数据进行聚合,具有鲜明的 I/O 密集型(I/O Bound)特点。同步加载弊端逐渐显现,因此考虑改为异步加载
部分文章转载pdf文档,无来源地址信息,在此说明

为何需要并行加载

外卖商家端 API 服务是典型的 I/O 密集型(I/O Bound)服务。除此之外,该服务的交易业务还有两个比较大的特点:

1、**服务端必须一次返回订单卡片所有内容。**需要从下游三十多个服务中获取数据。在特定条件下,如第一次登录和长时间没登录的情况下,客户端会分页拉取多个订单,这样发起的远程调用会更多。

**2、商家端和服务端交互频繁。**商家对订单状态变化敏感,多种推拉机制保证每次变更能够触达商家,导致 App 和服务端的交互频繁,每次变更需要拉取订单最新的全部内容。

并行加载的实现方式

​ 并行从下游获取数据,从 IO 模型上来讲分为同步模型异步模型

同步模型

从各个服务获取数据最常见的是同步调用。在同步调用的场景下,接口耗时长、性能差,接口响应时长 T > T1+T2+T3+……+Tn,这时为了缩短接口的响应时间一般会使用线程池的方式并行获取数据,商家端订单卡片的组装正是使用了这种方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yr4WOinN-1682568858766)(C:\Users\202230236\AppData\Roaming\Typora\typora-user-images\image-20230427110120742.png)]

产生的问题

1、**CPU 资源大量浪费在阻塞等待上,导致 CPU 资源利用率低。**在 Java 8 之前,一般会通过回调的方式来减少阻塞,但是大量使用回调,导致代码可读性和可维护性大大降低。

2、为了增加并发度,会引入更多额外的线程池,随着 CPU 调度线程数的增加,会导致更严重的资源争用,宝贵的 CPU 资源被损耗在上下文切换上,而且线程本身也会占用系统资源,且不能无限增加。

3、同步模型下,会导致硬件资源无法充分利用,系统吞吐量容易达到瓶颈。

NIO 异步模型

主要通过以下两种方式来减少线程池的调度开销和阻塞时间:

通过 RPC NIO 异步调用的方式可以降低线程数,从而降低调度(上下文切换)开销。

通过引入 CompletableFuture(下文简称 CF)对业务流程进行编排,降低依赖之间的阻塞。


CompletableFuture 使用与原理

​ CompletableFuture 是由 Java 8 引入的,之前一般通过 Future 实现异步

Future 用于表示异步计算的结果,只能通过阻塞或者轮询的方式获取结果,而且不支持设置回调方法,Java 8 之前若要设置回调一般会使用 guava 的ListenableFuture,回调的引入又会导致臭名昭著的回调地狱(下面的例子会通过 ListenableFuture 的使用来具体进行展示)。

● CompletableFuture 对 Future 进行了扩展,可以通过设置回调的方式处理计算结果,同时也支持组合操作,支持进一步的编排,同时一定程度解决了回调地狱的问题。

​ 使用 CompletableFuture 也是构建依赖树的过程。一个 CompletableFuture 的完成会触发另外一系列依赖它的 CompletableFuture 的执行。根据 CompletableFuture 依赖数量,可以分为以下几类:零依赖一元依赖二元依赖多元依赖

零依赖

//1、使用 runAsync 或 supplyAsync 发起异步调用
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
    return“result1”;
}, executor);

一元依赖:依赖一个 CF

CompletableFuture<String> cf3 = cf1.thenApply(result1 -> {
     //result1 为 CF1 的结果
     //......
     return“result3”;
});

二元依赖:依赖两个 CF

CompletableFuture<String> cf4 = cf1.thenCombine(cf2, (result1, result2) -> {
     //result1 和 result2 分别为 cf1 和 cf2 的结果
     return“result4”;
});

多元依赖:依赖多个 CF

CompletableFuture<Void> cf6 = CompletableFuture.allOf(cf3, cf4, cf5);
    CompletableFuture<String> result = cf6.thenApply(v -> {
     // 这里的 join 并不会阻塞,因为传给 thenApply 的函数是在 CF3、CF4、CF5 全部完成时,才会执行 。
     result3 = cf3.join();
     result4 = cf4.join();
     result5 = cf5.join();
     // 根据 result3、result4、result5 组装最终 result;
     return“result”;
});

CompletableFuture 原理

​ CompletableFuture 中包含两个字段:result 和 stackresult 用于存储当前 CF的结果stack(Completion)表示当前 CF 完成后需要触发的依赖动作(Dependency Actions),去触发依赖它的 CF 的计算,依赖动作可以有多个(表示有多个依赖它的 CF),以栈(Treiber stack)的形式存储,stack 表示栈顶元素。这种方式类似“观察者模式”,依赖动作(Dependency Action)都封装在一个单独Completion 子类中。

CompletableFuture 的设计思想

​ 按照类似“观察者模式”的设计思想,原理分析可以从“观察者”和“被观察者”两个方面着手。由于回调种类多,但结构差异不大,所以这里单以一元依赖中的thenApply 为例,不再枚举全部回调类型

线程阻塞问题

​ CompletableFuture 实现了 CompletionStage 接口,通过丰富的回调方法,支持各种组合操作,每种组合场景都有同步异步两种方法。

1、同步方法(即不带 Async 后缀的方法)有两种情况。

● 如果注册时被依赖的操作已经执行完成,则直接由当前线程执行。

● 如果注册时被依赖的操作还未执行完,则由回调线程执行。

2、异步方法(即带 Async 后缀的方法):可以选择是否传递线程池参数 Executor 运行在指定线程池中;当不传递 Executor 时,会使用 ForkJoinPool 中的共用线程池CommonPool(CommonPool 的大小是 CPU 核数 -1,如果是 IO 密集的应用,线程数可能成为瓶颈)。

// 例如:
ExecutorService threadPool1 = new ThreadPoolExecutor(10, 10, 0L, 
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
     System.out.println(“supplyAsync 执行线程:”+Thread.currentThread().getName());
     // 业务操作
     return“”;
}, threadPool1);

// 此时,如果 future1 中的业务操作已经执行完毕并返回,则该 thenApply 直接由当前main 线程执行;否则,将会由执行以上业务操作的 threadPool1 中的线程执行。
future1.thenApply(value -> {
     System.out.println(“thenApply 执行线程:” + Thread.currentThread().getName());
     return value +1;
});

// 使用 ForkJoinPool 中的共用线程池 CommonPool
future1.thenApplyAsync(value -> {
    //do something
     return value +1;
});
// 使用指定线程池
future1.thenApplyAsync(value -> {
    //do something
     return value +1;
}, threadPool1); 

线程池须知

1、异步回调要传线程池

异步回调方法可以选择是否传递线程池参数 Executor,这里我们建议强制传线程池,且根据实际情况做线程池隔离。当不传递线程池时,会使用 ForkJoinPool 中的公共线程池 CommonPool,这里所有调用将共用该线程池,核心线程数 = 处理器数量 -1(单核核心线程数为 1),所有异步回调都会共用该 CommonPool,核心与非核心业务都竞争同一个池中的线程,很容易成为系统瓶颈

手动传递线程池参数可以更方便的调节参数,并且可以给不同的业务分配不同的线程池,以求资源隔离,减少不同业务之间的相互干扰。

2、线程池循环引用会导致死锁

​ 若出现父子任务 ,需要将父任务与子任务做线程池隔离,两个任务请求不同的线程池,避免循环依赖导致的阻塞

3、异步 RPC 调用注意不要阻塞 IO 线程池

服务异步化后很多步骤都会依赖于异步 RPC 调用的结果,这时需要特别注意一点,如果是使用基于 NIO(比如 Netty)的异步 RPC,则返回结果是由 IO 线程负责设置的,即回调方法由 IO 线程触发,CompletableFuture 同步回调(如 thenApply、thenAccept 等无 Async 后缀的方法)。

​ 如果依赖的同步 RPC 调用的返回结果,那么这些同步回调将运行在 IO 线程上,而整个服务只有一个 IO 线程池,这时需要保证同步回调中不能有阻塞等耗时过长的逻辑,否则在这些逻辑执行完成前,IO 线程将一直被占用,影响整个服务的响应

异常处理

​ 由于异步执行的任务在其他线程上执行,而异常信息存储在线程栈中,因此当前线程除非阻塞等待返回结果,否则无法通过 try\catch 捕获异常。CompletableFuture提供了异常捕获回调 exceptionally,相当于同步调用中的 try\catch

CompletableFuture<WmOrderOpRemarkResult> remarkResultFuture = wmOrderAdditionInfoThriftService.findOrderCancelledRemarkByOrderIdAsync(orderId);
    // 业务方法,内部会发起异步rpc 调用
     return remarkResultFuture.exceptionally(err -> {// 通过 exceptionally 捕获异常,打印日志并返回默认值
         log.error(WmOrderRemarkService.getCancelTypeAsync Exception orderId={}, orderId, err);
     return 0;
});

CompletableFuture 在回调方法中对异常进行了包装。大部分异常会封装成 CompletionException 后抛出,真正的异常存储在 cause 属性中,因此如果调用链中经过了回调方法处理那么就需要用 Throwable.getCause() 方法提取真正的异常。但是,有些情况下会直接返回真正的异常,最好使用工具类提取异常 ExceptionUtils.extractRealException(err)

public class ExceptionUtils {
    public static Throwable extractRealException(Throwable throwable){
     // 这里判断异常类型是否为 CompletionException、ExecutionException,如果是则进行提取,否则直接返回。
        if (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
            if (throwable.getCause() != null) {
              return throwable.getCause();
            }
        }
        return throwable;
    }
}

附录

自定义函数

@FunctionalInterface
public interface ThriftAsyncCall {
   void invoke() throws TException ;
}

CompletableFuture 处理工具类

/**
 * CompletableFuture 封装工具类
 */
@Slf4j
public class FutureUtils {

/**

 \* 该方法为美团内部 rpc 注册监听的封装,可以作为其他实现的参照

 \* OctoThriftCallback 为 thrift 回调方法

 \* ThriftAsyncCall 为自定义函数,用来表示一次 thrift 调用(定义如上)

 */

public static <T> CompletableFuture<T> toCompletableFuture(final OctoThriftCallback<?,T> callback , ThriftAsyncCall thriftCall) {

 CompletableFuture<T> thriftResultFuture = new CompletableFuture<>();

 callback.addObserver(new OctoObserver<T>() {

 @Override

 public void onSuccess(T t) {

 thriftResultFuture.complete(t);

 }

 @Override

 public void onFailure(Throwable throwable) {

 thriftResultFuture.completeExceptionally(throwable);

 }

 });

 if (thriftCall != null) {

 try {

 thriftCall.invoke();

 } catch (TException e) {

 thriftResultFuture.completeExceptionally(e);

 }

 }

 return thriftResultFuture;

}

 /**

 \* 设置 CF 状态为失败

 */

 public static <T> CompletableFuture<T> failed(Throwable ex) {

 CompletableFuture<T> completableFuture = new CompletableFuture<>();
 completableFuture.completeExceptionally(ex);
 return completableFuture;
 }

 /**

 \* 设置 CF 状态为成功

 */

 public static <T> CompletableFuture<T> success(T result) {
 CompletableFuture<T> completableFuture = new CompletableFuture<>();
	 completableFuture.complete(result);
	 return completableFuture;
 }

 /**

 \* 将 List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>

 */

 public static <T> CompletableFuture<List<T>> 
sequence(Collection<CompletableFuture<T>> completableFutures) {
	 return CompletableFuture.allOf(completableFutures.toArray(new
		CompletableFuture<?>[0]))
		.thenApply(v -> completableFutures.stream()
		.map(CompletableFuture::join)
		.collect(Collectors.toList())
	 );
 }

 /**

 \* 将 List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>

 \* 多用于分页查询的场景

 */

 public static <T> CompletableFuture<List<T>> 

sequenceList(Collection<CompletableFuture<List<T>>> completableFutures) {

	 return CompletableFuture.allOf(completableFutures.toArray(new
	CompletableFuture<?>[0]))
	 .thenApply(v -> completableFutures.stream()
	 .flatMap( listFuture -> listFuture.join().stream())
	 .collect(Collectors.toList())
	 );
 }

 /*

 \* 将 List<CompletableFuture<Map<K, V>>> 转为 CompletableFuture<Map<K, V>>

 \* @Param mergeFunction 自定义 key 冲突时的 merge 策略

 */

 public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(

 Collection<CompletableFuture<Map<K, V>>> completableFutures, 

BinaryOperator<V> mergeFunction) {
 return CompletableFuture
	 .allOf(completableFutures.toArray(new
	CompletableFuture<?>[0]))
	 .thenApply(v -> completableFutures.stream().
	map(CompletableFuture::join)
	 .flatMap(map -> map.entrySet().stream())
	 .collect(Collectors.toMap(Entry::getKey, 
	Entry::getValue, mergeFunction)));
 }

 /**

 \* 将 List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>,并过

滤调 null 值

 */

 public static <T> CompletableFuture<List<T>> 

sequenceNonNull(Collection<CompletableFuture<T>> completableFutures) {

	 return CompletableFuture.allOf(completableFutures.toArray(new
	CompletableFuture<?>[0]))
	 .thenApply(v -> completableFutures.stream()
	 .map(CompletableFuture::join)
	 .filter(e -> e != null)
	 .collect(Collectors.toList())
	 );

 }

 /**

 \* 将 List<CompletableFuture<List<T>>> 转为

CompletableFuture<List<T>>,并过滤调 null 值

 \* 多用于分页查询的场景

 */

 public static <T> CompletableFuture<List<T>> 

sequenceListNonNull(Collection<CompletableFuture<List<T>>> completableFutures) {
	 return CompletableFuture.allOf(completableFutures.toArray(new
		CompletableFuture<?>[0]))
		 .thenApply(v -> completableFutures.stream()
		 .flatMap( listFuture -> listFuture.join().stream().
		filter(e -> e != null))
		 .collect(Collectors.toList())
	 );

 }

 /**

 \* 将 List<CompletableFuture<Map<K, V>>> 转为

CompletableFuture<Map<K, V>>

 \* @Param filterFunction 自定义过滤策略

 */

 public static <T> CompletableFuture<List<T>> 

sequence(Collection<CompletableFuture<T>> completableFutures,

 Predicate<? super T> 

filterFunction) {

 return CompletableFuture.allOf(completableFutures.toArray(new

CompletableFuture<?>[0]))

 .thenApply(v -> completableFutures.stream()

 .map(CompletableFuture::join)

 .filter(filterFunction)

 .collect(Collectors.toList())

 );

 }

 /**

 \* 将 List<CompletableFuture<List<T>>> 转为

CompletableFuture<List<T>>

 \* @Param filterFunction 自定义过滤策略

 */

 public static <T> CompletableFuture<List<T>> 

sequenceList(Collection<CompletableFuture<List<T>>> completableFutures,

 Predicate<? 

super T> filterFunction) {

 return CompletableFuture.allOf(completableFutures.toArray(new

CompletableFuture<?>[0]))

 .thenApply(v -> completableFutures.stream()

 .flatMap( listFuture -> listFuture.join().stream().

filter(filterFunction))

 .collect(Collectors.toList())

 );

 }

/**

 \* 将 CompletableFuture<Map<K,V>> 的 list 转为

CompletableFuture<Map<K,V>>。 多个 map 合并为一个 map。 如果 key 冲突,采用新

的 value 覆盖。

 */

 public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(

 Collection<CompletableFuture<Map<K, V>>> completableFutures) {

 return CompletableFuture

 .allOf(completableFutures.toArray(new

CompletableFuture<?>[0]))

 .thenApply(v -> completableFutures.stream().

map(CompletableFuture::join)

 .flatMap(map -> map.entrySet().stream())

 .collect(Collectors.toMap(Entry::getKey, 

Entry::getValue, (a, b) -> b)));

 }}

打印日志

@Slf4j

 public abstract class AbstractLogAction<R> {

 protected final String methodName;

 protected final Object[] args;

public AbstractLogAction(String methodName, Object... args) {

 this.methodName = methodName;

 this.args = args;

}

protected void logResult(R result, Throwable throwable) {

 if (throwable != null) {

 boolean isBusinessError = throwable instanceof TBase || 

(throwable.getCause() != null && throwable

 .getCause() instanceof TBase);

 if (isBusinessError) {

 logBusinessError(throwable);

 } else if (throwable instanceof DegradeException || throwable 

instanceof DegradeRuntimeException) {// 这里为内部 rpc 框架抛出的异常,使用时

可以酌情修改

 if (RhinoSwitch.getBoolean(“isPrintDegradeLog”, false)) {

 log.error({} degrade exception, param:{} , error:{}, 

methodName, args, throwable);

 }

 } else {

 log.error({} unknown error, param:{} , error:{}, 

methodName, args, ExceptionUtils.extractRealException(throwable));

 }

 } else {

 if (isLogResult()) {

 log.info({} param:{} , result:{}, methodName, args, 

result);

 } else {

 log.info({} param:{}, methodName, args);

 }

 }

}

private void logBusinessError(Throwable throwable) {

 log.error({} business error, param:{} , error:{}, 

methodName, args, throwable.toString(), ExceptionUtils.

extractRealException(throwable));

}

private boolean isLogResult() {

 // 这里是动态配置开关,用于动态控制日志打印,开源动态配置中心可以使用 nacos、

apollo 等,如果项目没有使用配置中心则可以删除

 return RhinoSwitch.getBoolean(methodName + “_isLogResult”, false);

}}

日志处理实现类

/**
 \* 发生异常时,根据是否为业务异常打印日志。
 \* 跟 CompletableFuture.whenComplete 配合使用,不改变 completableFuture 的
结果(正常 OR 异常)
 */
@Slf4j
public class LogErrorAction<R> extends AbstractLogAction<R> implements BiConsumer<R, Throwable> {
    public LogErrorAction(String methodName, Object... args) {
        super(methodName, args);
    }
    @Override
    public void accept(R result, Throwable throwable) {
        logResult(result, throwable);
    }
}

打印日志方式

completableFuture.whenComplete(new LogErrorAction<>(“orderService.getOrder”, params));

异常情况返回默认值

/**

 \* 当发生异常时返回自定义的值

 */

public class DefaultValueHandle<R> extends AbstractLogAction<R> 

implements BiFunction<R, Throwable, R> {

 private final R defaultValue;

/**

 \* 当返回值为空的时候是否替换为默认值

 */

private final boolean isNullToDefault;

/**

 \* @param methodName 方法名称

 \* @param defaultValue 当异常发生时自定义返回的默认值

 \* @param args 方法入参

 */

 public DefaultValueHandle(String methodName, R defaultValue, 

Object... args) {

 super(methodName, args);

 this.defaultValue = defaultValue;

 this.isNullToDefault = false;

 }

/**

 \* @param isNullToDefault

 \* @param defaultValue 当异常发生时自定义返回的默认值

 \* @param methodName 方法名称

 \* @param args 方法入参

 */

 public DefaultValueHandle(boolean isNullToDefault, R defaultValue,

 String methodName, Object... args) {

 super(methodName, args);

 this.defaultValue = defaultValue;

 this.isNullToDefault = isNullToDefault;

 }

@Override

public R apply(R result, Throwable throwable) {

 logResult(result, throwable);

 if (throwable != null) {

 return defaultValue;

 }

 if (result == null && isNullToDefault) {

 return defaultValue;

 }

 return result;

}

public static <R> DefaultValueHandle.DefaultValueHandleBuilder<R> 

builder() {

 return new DefaultValueHandle.DefaultValueHandleBuilder<>();

}

public static class DefaultValueHandleBuilder<R> {

 private boolean isNullToDefault;

 private R defaultValue;

 private String methodName;

 private Object[] args;

 DefaultValueHandleBuilder() {

 }

 public DefaultValueHandle.DefaultValueHandleBuilder<R> 

isNullToDefault(final boolean isNullToDefault) {

 this.isNullToDefault = isNullToDefault;

 return this;

 }

 public DefaultValueHandle.DefaultValueHandleBuilder<R> 

defaultValue(final R defaultValue) {

 this.defaultValue = defaultValue;

 return this;

 }

 public DefaultValueHandle.DefaultValueHandleBuilder<R> 

methodName(final String methodName) {

 this.methodName = methodName;

 return this;

 }

 public DefaultValueHandle.DefaultValueHandleBuilder<R> args(final

Object... args) {

 this.args = args;

 return this;

 }

 public DefaultValueHandle<R> build() {

 return new DefaultValueHandle<R>(this.isNullToDefault, this.

defaultValue, this.methodName, this.args);

 }

 public String toString() {

 returnDefaultValueHandle.

DefaultValueHandleBuilder(isNullToDefault=+ this.isNullToDefault +,

 defaultValue=+ this.defaultValue +, methodName=+ this.methodName 

\+, args=+ Arrays.deepToString(this.args) +);

 }

}

默认返回值应用示例

completableFuture.handle(new DefaultValueHandle<>(“orderService.getOrder”, Collections.emptyMap(), params));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值