微服务架构师封神之路08-微服务Rest接口调用客户端性能比较,RestTemplate vs WebClient
目前最新版本Springboot(2.4.2)下两大HTTP客户端体系,两种HTTP调用使用了不同的底层设计:
- RestTemplate。阻塞式客户端,在底层,RestTemplate 使用了基于每个请求对应一个线程模型(thread-per-request)。
- WebClient。WebClient 使用 Spring Reactive Framework 所提供的异步非阻塞解决方案。Reactive 框架使用事件驱动的体系结构。它提供了通过 Reactive Streams API 组合异步逻辑的方法。因此,与同步/阻塞方法相比,Reactive 可以使用更少的线程和系统资源来处理更多的逻辑。
后面在同等条件下(线程池最大500线程)对他们的性能进行对比。
RestTemplate Config
pom中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
......
<!-- httpcomponents, for restTemplate -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
初始化RestTemplate并设置线程池,
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(500);
//connectionManager.setDefaultMaxPerRoute(150);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(500) //返回数据的超时时间
.setConnectTimeout(10000) //连接上服务器的超时时间
.setConnectionRequestTimeout(5000) //从连接池中获取连接的超时时间
.build();
HttpClient httpClient = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager).build();
return builder.requestFactory(()->new HttpComponentsClientHttpRequestFactory(httpClient)).build();
}
}
WebClient Config
在pom中添加依赖
<!-- spring webClient -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
初始化WebClient并配置线程池,
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(){
//配置动态连接池
//ConnectionProvider provider = ConnectionProvider.elastic("elastic pool");
//配置固定大小连接池,如最大连接数、连接获取超时、空闲连接死亡时间等
ConnectionProvider provider = ConnectionProvider
.builder("webClient-connection-pool")
.maxConnections(500)
.maxIdleTime(Duration.ofMillis(10000))
.pendingAcquireTimeout(Duration.ofMillis(100))
.build();
HttpClient httpClient = HttpClient.create(provider).keepAlive(true);
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
}
性能对比
统计1000次同步请求所花费的时间。目标Rest接口就部署在本地,所以network所花费的时间可以说非常短。
代码示例:
RestTemplate
long t0 = System.currentTimeMillis();
for(int i=0; i<no;i++){
ResponseEntity<Booking[]> responseEntity =
restTemplate.postForEntity(Constant.URL_BOOK_TICKETS,request,Booking[].class);
Booking[] bookings = responseEntity.getBody();
log.info("Result: {}",Arrays.toString(bookings));
}
long t1 = System.currentTimeMillis();
long time = t1 - t0;
log.info("RestTemplate http call: {}, take: {}",no,time);
WebClient
URI uri = URI.create(Constant.URL_BOOK_TICKETS);
long t0 = Instant.now().toEpochMilli();
for(int i=0; i<no; i++){
Mono<Booking[]> resp = webClient.post()
.uri(uri)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(bookingList),List.class)
.retrieve()
.bodyToMono(Booking[].class);
log.info("Result: {}", Arrays.toString(resp.block()));
}
long t1 = Instant.now().toEpochMilli();
long time = t1 - t0;
log.info("WebClient http call: {}, take: {}",no,time);
测试结果
对每种请求方式运行多次,将最初几次结果去掉,以避免系统在初始化阶段资源还没有被充分使用的情况影响测试结果。
- RestTemplate: 稳定在321ms左右
- WebClent: 稳定在221ms左右
结论:
- 即使在使用同步调用的情况下,WebClient也要好于RestTemplate。
- 如果我们可以将业务设计成异步的方式,使用WebClient的优势将更加明显。