Spring WebClient 、RestTemplate、JDK11的HttpClient

        对其他服务进行 HTTP 调用是 Web 应用程序中的常见要求。所以,我们需要一个网络客户端工具。

1、RestTemplate也是阻塞的

        长期以来,Spring 一直提供RestTemplate作为 Web 客户端抽象。在底层,RestTemplate使用 Java Servlet API,它基于每请求线程模型。

        这意味着线程将阻塞,直到 Web 客户端收到响应。阻塞代码的问题是由于每个线程消耗了一些内存和 CPU 周期。

        当应用程序中有大量的请求时,就会有大量的线程和连接。这会给服务器资源带来负担。

        如果服务器速度很慢,用户很快就会开始看到应用程序的性能下降甚至无响应。

2、WebClient 是非阻塞的

        WebClient使用 Spring Reactive 框架提供的异步、非阻塞解决方案。

        RestTemplate为每个事件(HTTP 调用)使用调用者线程,而WebClient将为每个事件创建类似于“任务”的东西。在幕后,Reactive 框架会将这些“任务”排队并仅在适当的响应可用时执行它们。

        Reactive 框架使用事件驱动的架构。它提供了通过Reactive Streams API组合异步逻辑的方法。因此,与同步/阻塞方法相比,反应式方法可以处理更多逻辑,同时使用更少的线程和系统资源。

        WebClient是Spring WebFlux库的一部分。因此,我们还可以使用具有反应类型(Mono和Flux)的功能性、流畅的 API 作为声明性组合来编写客户端代码。

3、JDK11的HttpClient

        JDK11的HttpClient可以发送同步请求、也可以发送异步请求。windows64为的JDK11的下载地址,oracle官方的下载有些慢。

链接:https://pan.baidu.com/s/1LaM0b1qv2eG01ITd6vxg6g 
提取码:tiq4

        同步方法

public abstract <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException, InterruptedException

        异步方法

public abstract <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)

4、比较示例

        为了演示这三种方法之间的性能差异,我们需要进行多并发客户端请求进行性能测试。

        在一定数量的并行客户端请求之后,我们会看到阻塞方法的性能显着下降。

        但是,无论请求的数量如何,反应式/非阻塞方法都应该提供恒定的性能。

        对于本文,我们将实现三个REST接口,一个使用RestTemplate,另一个使用WebClient,最后一个使用jdk11中的HttpClient。

        首先,我们需要Spring Boot WebFlux 启动器依赖项(建议修改idea的maven远程仓库地址):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

        下面的代码我们的服务,返回一个list,但是休眠2秒。

private final static Logger log = LoggerFactory.getLogger(UserController.class);
private final String url = "http://127.0.0.1:9001/demo-service";

@GetMapping("/demo-service")
private List<String> getAllTweets() {
    try {
        Thread.sleep(2000L); // delay
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return Arrays.asList("RestTemplate","WebClient","HttpClient");
}

(1)使用RestTemplate调用服务

        现在让我们实现另一个 REST,它将通过 Web 客户端调用我们的服务。

@GetMapping("/resttemplate-blocking")
public List<String> getResttemplateBlocking() {
    log.info("请求开始");
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<List<String>> response = restTemplate.exchange(
            url, HttpMethod.GET, null,
            new ParameterizedTypeReference<List<String>>(){});
    List<String> result = response.getBody();
    log.info("请求完成");
    return result;
}

        当我们调用这个端点时,由于RestTemplate的同步特性,代码将阻塞等待来自我们服务的响应。此方法中的其余代码将仅在收到响应时运行。

        以下是我们将在日志中看到的内容,按时间看确认是阻塞的:

2022-05-10 19:39:07.740  INFO 21088 --- [nio-9001-exec-1] c.h.skydance.controller.UserController   : 请求开始
2022-05-10 19:39:09.799  INFO 21088 --- [nio-9001-exec-1] c.h.skydance.controller.UserController   : 请求完成

(2)使用WebClient调用服务

@GetMapping(value = "/flux-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getFluxNonBlocking() {
    log.info("Flux请求开始!");
    Flux<String> flux = WebClient.create()
            .get()
            .uri(url)
            .retrieve()
            .bodyToFlux(String.class);
    log.info("Flux请求结束!");
    flux.subscribe(result -> log.info(result.toString()));
    log.info("Flux数据接收完成!");
    return flux;
}

        在这种情况下,WebClient返回一个Flux发布者,方法执行完成。一旦结果可用,发布者将开始向其订阅者发送推文。

        让我们参考代码观察一下日志,确认是非阻塞的,请注意,此endpoint方法在收到响应之前完成。

2022-05-10 19:40:38.551  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux请求开始!
2022-05-10 19:40:38.552  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux请求结束!
2022-05-10 19:40:38.552  INFO 21088 --- [nio-9001-exec-9] c.h.skydance.controller.UserController   : Flux数据接收完成!
2022-05-10 19:40:40.574  INFO 21088 --- [ctor-http-nio-2] c.h.skydance.controller.UserController   : ["RestTemplate","WebClient","HttpClient"]

(3)使用JDK11的HttpClient的同步方式

@GetMapping(value = "/httpclient-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public String getHttpclientBlocking() {
    log.info("httpclient send请求开始");
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
    HttpResponse<String> response = null;
    try {
        response = client.send(request, HttpResponse.BodyHandlers.ofString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("httpclient send请求结束");
    return response.body();
}

        查看日志输出,是阻塞式的。

2022-05-10 19:42:27.862  INFO 21088 --- [nio-9001-exec-3] c.h.skydance.controller.UserController   : httpclient send请求开始
2022-05-10 19:42:29.884  INFO 21088 --- [nio-9001-exec-3] c.h.skydance.controller.UserController   : httpclient send请求结束

(4)使用JDK11的HttpClient的异步方式

@GetMapping(value = "/httpclient-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public String getHttpclientAsyncNonBlocking() {
    try {
        log.info("httpclient sendAsync请求开始!");
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
        CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body);
        log.info("httpclient sendAsync请求结束!");
        String arr = result.get();
        log.info("httpclient sendAsync请求结束 打印请求结果:" + arr);
        return arr;
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return null;
}

        查看结果日志的顺序,请求是非阻塞的,但是与Flux不同,这里的CompletableFuture的get方法是阻塞的。

2022-05-10 19:43:42.396  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求开始!
2022-05-10 19:43:42.398  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求结束!
2022-05-10 19:43:44.404  INFO 21088 --- [nio-9001-exec-2] c.h.skydance.controller.UserController   : httpclient sendAsync请求结束 打印请求结果:["RestTemplate","WebClient","HttpClient"]

        参考代码地址 

java_example/httprequest at main · bashendixie/java_example · GitHubContribute to bashendixie/java_example development by creating an account on GitHub.https://github.com/bashendixie/java_example/tree/main/httprequest

猜你喜欢

转载自blog.csdn.net/bashendixie5/article/details/124690659