收到多个客户端请求后,阻塞方法的性能显著下降。
而 Reactive 非阻塞方法的表现应该与请求数量无关,性能稳定
添加 Spring Boot WebFlux Starter 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
耗时比较
@GetMapping("/slow-service-tweets") private List<Tweet> getAllTweets() { Thread.sleep(2000L); // 延迟 return Arrays.asList( new Tweet("RestTemplate rules", "@user1"), new Tweet("WebClient is better", "@user2"), new Tweet("OK, both are useful", "@user1")); }
RestTemplate 调用耗时服务
@GetMapping("/tweets-blocking") public List<Tweet> getTweetsBlocking() { log.info("Starting BLOCKING Controller!"); final String uri = getSlowServiceUri(); RestTemplate restTemplate = new RestTemplate(); ResponseEntity<List<Tweet>> response = restTemplate.exchange( uri, HttpMethod.GET, null, new ParameterizedTypeReference<List<Tweet>>(){}); List<Tweet> result = response.getBody(); result.forEach(tweet -> log.info(tweet.toString())); log.info("Exiting BLOCKING Controller!"); return result; }
由于 RestTemplate 是同步调用,调用 Endpoint 时代码将进入阻塞等待被调用的耗时服务响应。只有在收到响应后,才会执行方法中的后续代码,日志如下:
Starting BLOCKING Controller! Tweet(text=RestTemplate rules, username=@user1) Tweet(text=WebClient is better, username=@user2) Tweet(text=OK, both are useful, username=@user1) Exiting BLOCKING Controller!
接下来 WebClient 调用耗时服务
@GetMapping(value = "/tweets-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Tweet> getTweetsNonBlocking() { log.info("Starting NON-BLOCKING Controller!"); Flux<Tweet> tweetFlux = WebClient.create() .get() .uri(getSlowServiceUri()) .retrieve() .bodyToFlux(Tweet.class); tweetFlux.subscribe(tweet -> log.info(tweet.toString())); log.info("Exiting NON-BLOCKING Controller!"); return tweetFlux; }
WebClient 返回 Flux publisher 后就执行完成了。结果就绪时,publisher 会向订阅者发送推文列表。注意:客户端(这里指 Web 浏览器)调用/tweets-non-blocking Endpoint 也可以订阅 Flux 对象。 这个 Endpoint 方法在收到响应前就已执行完成。
Starting NON-BLOCKING Controller! Exiting NON-BLOCKING Controller! Tweet(text=RestTemplate rules, username=@user1) Tweet(text=WebClient is better, username=@user2) Tweet(text=OK, both are useful, username=@user1)
总结:
这篇文章讨论了 Spring 中使用 Web Client 的两种不同方式。
RestTemplate 采用 Java Servlet API,因而是阻塞式同步调用。相反,WebClient 是异步的,等待响应的同时不会阻塞正在执行的线程。只有在响应结果准备就绪时,才会发起通知。
RestTemplate 仍然有用武之地。非阻塞模式在某些场景下比阻塞方法占用系统资源要少得多,这时 WebClient 是一种更好的选择