在现代应用开发中,并行处理就像是一群配合默契的大厨,各自忙碌却井然有序,一起炒菜、煲汤、做冷盘,不但节省了时间,还大大提高了出菜速度。系统也是一样,当我们将任务合理拆分并交由多个 “线程厨师” 同时处理时,整体吞吐量自然水涨船高,响应时间也就被大大压缩。与其让一个线程从头忙到尾,不如分头并进、各显神通。
在传统的串行调用模式下,系统就像一个单人操作的厨房,每位厨师只能按部就班地完成一项任务。你必须等第一个菜做好,才能开始做第二道菜,甚至第三道菜也要排队。这样的操作方式有几个明显的问题:
而一旦网络不稳定或接口响应缓慢,问题就会更加严重,导致整个 “厨房” 瘫痪,顾客等得心急如焚,系统性能也随之下滑,用户体验直线下降。
REST 架构就像是一个高效的多厨师厨房,每个厨师都能独立操作,不必等前一个厨师的工作完成,大家各自负责自己的任务,节省了大量时间。它的并发调用优势体现得尤为明显:
典型应用场景就像是厨房里处理不同类型的菜肴:
CompletableFuture
作为 Java 8 引入的异步编程工具,提供了强大的任务编排能力。以下示例展示了如何使用它实现 REST 接口的并行调用:
package com.funtester.example;
import java.net.URI;
import java.net.http.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
public class ParallelRestCalls {
// 创建一个 HttpClient 实例,用于发送 HTTP 请求
private static final HttpClient client = HttpClient.newHttpClient();
/**
* fetch 方法:用于异步请求一个 URL 并返回响应体的内容
*
* @param url 要请求的 URL
* @return 一个 CompletableFuture,包含 HTTP 请求的响应体
*/
public static CompletableFuture<String> fetch(String url) {
// 构建一个 HttpRequest 对象,指定请求的 URI
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) // 设置请求的 URL
.build();
// 异步发送请求,并返回响应体的内容
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // 发送异步请求
.thenApply(HttpResponse::body); // 获取响应体内容
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 定义一个包含多个 URL 的列表,这些 URL 将用于并行发送请求
List<String> urls = Arrays.asList(
"https://api.funtester.com/resource1", // 第一个资源
"https://api.funtester.com/resource2", // 第二个资源
"https://api.funtester.com/resource3" // 第三个资源
);
// 使用流操作并行地发出请求
List<CompletableFuture<String>> futures = urls.stream() // 将 URL 列表转换为流
.map(ParallelRestCalls::fetch) // 对每个 URL 调用 fetch 方法
.collect(Collectors.toList()); // 收集所有的 CompletableFuture 对象
// 等待所有请求都完成,这里使用 CompletableFuture.allOf 来处理多个异步任务
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
// 当所有请求都完成后,将所有响应收集到一个列表中
CompletableFuture<List<String>> allResponses = allOf.thenApply(v ->
futures.stream() // 对每个 CompletableFuture 进行操作
.map(CompletableFuture::join) // 获取每个请求的结果(阻塞等待)
.collect(Collectors.toList()) // 收集所有响应到列表中
);
// 获取所有响应的结果,并打印输出
List<String> responses = allResponses.get(); // 阻塞等待并获取最终结果
responses.forEach(System.out::println); // 输出每个响应
}
}
这段代码演示了如何使用 CompletableFuture 实现 REST 接口的并行请求,充分利用异步编程提高性能。在发送多个 REST 请求时,这种并行处理方式显著降低了总的响应时间,尤其是在面对多个独立请求时,可以大大提升系统的吞吐量和响应速度。
实际生产环境中必须考虑接口调用失败的情况。CompletableFuture 提供了多种异常处理方式:
方案一:使用 exceptionally
/**
* 异步发送 HTTP 请求并返回响应体的内容。
*
* @param url 要请求的 URL 地址
* @return 一个 CompletableFuture,包含 HTTP 请求的响应体内容,若发生异常,返回错误信息
*/
public static CompletableFuture<String> fetch(String url) {
// 创建一个 HttpRequest 对象,构建请求并指定请求的 URL
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) // 设置请求的 URI 地址
.build(); // 构建 HttpRequest 对象
// 异步发送请求,处理响应并返回响应体内容
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // 异步发送请求并获取响应体
.thenApply(HttpResponse::body) // 提取响应体作为结果
.exceptionally(ex -> "Error: " + ex.getMessage()); // 如果发生异常,返回错误信息
}
方案二:使用 handle
/**
* 异步发送 HTTP 请求并返回响应体的内容。如果发生异常,返回错误信息。
*
* @param url 要请求的 URL 地址
* @return 一个 CompletableFuture,包含 HTTP 请求的响应体内容;若发生异常,返回错误信息
*/
public static CompletableFuture<String> fetch(String url) {
// 创建一个 HttpRequest 对象,构建请求并指定请求的 URL
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) // 设置请求的 URI 地址
.build(); // 构建 HttpRequest 对象
// 异步发送请求,处理响应并返回响应体内容,或者捕获异常
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // 异步发送请求并获取响应体
.handle((response, ex) -> { // 使用 handle 方法处理响应或异常
if (ex != null) { // 如果发生异常
return "Error: " + ex.getMessage(); // 返回错误信息
}
return response.body(); // 如果没有异常,返回响应体的内容
});
}
这种方式可以确保无论请求是否成功,都会返回一个值(响应体或者错误信息)。handle 方法与 exceptionally 方法类似,区别在于它不仅能处理异常,还能处理正常响应。
采用 CompletableFuture 来实现并发调用,犹如厨房里多个厨师分工合作、互不干扰,每个人专注于自己的一道菜。它的优势也正如多个厨师并行工作所带来的好处:
通过合理运用并行处理技术,开发者就能像厨房里的大厨一样,在保证系统稳定性和安全性的前提下,大幅提升接口的响应速度,最终为用户提供更加高效和优质的服务体验。
FunTester 原创精华
【免费合集】从 Java 开始性能测试
故障测试与 Web 前端
服务端功能测试
性能测试专题
Java、Groovy、Go
测试开发、自动化、白盒
测试理论、FunTester 风采
视频专题