Java 异步调用实践

本文详细介绍了Java异步调用的实践,包括BIO与NIO模型的区别,CompletableFuture的使用,RPC和HTTP异步调用优化,以及如何通过CompletableFuture提高系统吞吐量。通过案例分析,展示了如何避免嵌套join导致的“死锁”,推荐最佳实践,如避免使用默认线程池,设置超时等。
摘要由CSDN通过智能技术生成

本文介绍了线上业务中的一些异步调用实践经验,包含 IO 模型介绍、CompletableFuture 的基本使用、RPC 异步调用、异步 HTTP 客户端 Spring WebClient 的使用等。RPC 使用前文介绍的手写 RPC 框架,该框架支持异步调用。

本文要点:

  • 为什么需要异步调用
  • CompletableFuture 基本使用
  • RPC 异步调用
  • HTTP 异步调用
  • 编排 CompletableFuture 提高吞吐量

为什么异步

BIO 模型

首先我们先回顾一下 BIO 模型:

当用户进程调用了recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。所以,Blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了。

同步调用

在同步调用的场景下,依次请求多个接口,耗时长、性能差,接口响应时长 T > T1+T2+T3+……+Tn。

减少同步等待

一般这个时候为了减少同步等待时间,会使用线程池来同时处理多个任务,接口的响应时间就是 MAX(T1,T2,T3):

大概代码如下:

Future<String> future = executorService.submit(() -> {
  Thread.sleep(2000);
  return "hello world";
});
while (true) {
  if (future.isDone()) {
    System.out.println(future.get());
    break;
  }
}
复制代码

同步模型中使用线程池确实能实现异步调用的效果,也能压缩同步等待的时间,但是也有一些缺陷:

  • CPU 资源大量浪费在阻塞等待上,导致 CPU 资源利用率低。
  • 为了增加并发度,会引入更多额外的线程池,随着 CPU 调度线程数的增加,会导致更严重的资源争用,上下文切换占用 CPU 资源。
  • 线程池中的线程都是阻塞的,硬件资源无法充分利用,系统吞吐量容易达到瓶颈。

NIO 模型

为了解决 BIO 中的缺陷,引入 NIO 模型:

当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问 kernel 数据好了没有。

异步优化思路

我们知道了 NIO 的调用方式比 BIO 好,那我们怎么能在业务编码中使用到 NIO 呢?自己动手将 BIO 替换成 NIO 肯定不现实,已有组件支持 NIO 的可以直接使用,不支持的继续使用自定义线程池。

  • 通过 RPC NIO 异步调用、 HTTP 异步调用的方式降低线程数,从而降低调度(上下文切换)开销。
  • 没有原生支持 NIO 异步调用的继续使用线程池。
  • 引入 CompletableFuture 对业务流程进行编排,降低依赖之间的阻塞。

简述CompletableFuture

CompletableFuture 是 java.util.concurrent 库在 java 8 中新增的主要工具,同传统的 Future 相比,其支持流式计算、函数式编程、完成通知、自定义异常处理等很多新的特性。

常用 API 举例

supplyAsync

CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{    
  try{
    Thread.sleep(1000L);
    return "hello world";
  } catch (Exception e){
    return "failed";
  }
});
System.out.println(future.join());
// output
hello world
复制代码

开启异步任务,到另一个线程执行。

complete

CompletableFuture<String> future1 = new CompletableFuture<>();
future.complete("hello world");        //异步线程执行
future.whenComplete((res, throwable) -> {
  System.out.println(res);
});
System.out.println(future1.join());
CompletableFuture<String> future2 = new CompletableFuture<>();
future.completeExceptionally(new Throwable("failed")); //异步线程执行
System.out.println(future2.join());
// output
hello world
hello world
  
Exception in thread "main" 
java.util.concurrent.CompletionException: 
java.lang.Throwable: failed
复制代码

complete 正常完成该 CompletableFuture。

completeExceptionally 异常完成该 CompletableFuture。

thenApply

String original = "Message";
CompletableFuture<String> cf = 
  CompletableFuture.completedFuture(original).thenApply(String::toUpperCase);
System.out.println(cf.join());
// output
MESSAGE
复制代码

任务后置处理。

图示:

thenCombine

CompletableFuture<String> cf = 
  CompletableFuture.completedFuture("Message").thenApply(String::toUpperCase);
CompletableFuture<String> cf1 = 
  CompletableFuture.completedFuture("Message").then
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值