公司新项目用上了 JDK 17 + Spring 2.6.6 。发现 Spring 5 新增了 WebClient 类,使用非阻塞 IO 、支持函数式编程等等一大堆优点。但是使用时,很多跟 Apache HttpClient 那一套有太多不同。
想问问各位大佬,有没有关于 WebClient 的最佳实践?
另外,如果要使用 WebClient 封装一个通用的 HttpClient 类,要注意什么?

感谢各位回复!

目前决定还是采用 Apache 的 HttpClient 作为 Http 连接的工具。

WebClient 目前先不用到项目上,后面再深入研究,毕竟 Spring 官方主推,以后可能用得上。

webclient 用的是响应式编程那套设计的, 如果不是吞吐量太大 (而且 IO 占比不高的也不好说,具体数据请自行 google ,既然你问了大概率不需要), 最佳实践就是没有必要别用,还是 spring MVC blocking IO 那套好写好维护。

如果你没用 spring webflux ,就没必要用 webclient

#1
#2
那就是回归到 JDK 自带 Http Client 、Apache HttpClient 和 OKHttp 这几个选项了

projectreactor.io/

www.baeldung.com/spring-5-webclient

就是用 reactor 包了一下呗,这有啥最佳实践的.....

配合 webflux 一路 compose/flatmap 下去就得了,如果有数据库事物操作的话不要乱用 recover/resume 操作符

貌似 reactive transaction 和 kotlin 的 suspend 函数集成还在 wip 阶段,感兴趣可以自己看看

#4
就是看了太多文章,有太多困惑。
比如:

1 )有文章说不要用 create 方法创建 WebClient 对象,要用 build 方法。但是官方文档没提及这个。

2 ) exchange 方法过时了,想获取更多响应结果的原始信息,不知道怎么处理。

3 )领导说要撸一个通用的 HttpClient 类,屏蔽各种底层实现。但是 WebClient 的函数式编程太爽了,撸个通用的,还不如直接使用。况且各种 http 服务有各种要求…

嗯随便找个顺手的就行。 说实话我觉得你对响应式编程和它的应用场景,基本概念还很模糊,最好先看看这部分知识,看点例子。 除非你处理流程都是非阻塞的,否则单独用 webclient 最后你还得 call block 拿结果,等于是阻塞模型,而且 webclient 底层是 netty 非阻塞的那套,线程设置的很少,你用阻塞来玩,性能反而更差,流量大点甚至导致线程用尽。

WebClient 是 web flux 的,响应式那套东西,怎么说呢,没必要就别用,写起来还是累
另外既然 spring 了就 RestTemplate 呗,底层适配多种 http 库,配合 Spring boot 的 RestTemplateBuilder 用,也是个“通用”的 HttpClient 了

推荐 WebClient ,RestTemplate 已经没有新功能增加了,我记得 Spring 官方文档也有写推荐使用 WebClient ,Spring MVC 项目也可以使用 WebClient 。

As of 5.0 the RestTemplate is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the WebClient which offers a more modern API and supports sync, async, and streaming scenarios.

没事别用 aio ,会变的不幸。我完全理解不了函数式编程有什么爽的。
让我用 aio 我不如直接重新用 go 写。

配合 mvc 用那就简单了,直接 block()就行

exchangeToMono/exchangeToFlux 方法里可以传一个类型为(ClientResponse)->Mono/Flux 的 lambda,想要啥都在那里面

go boy 能接受 if err!=null ,却不能接受泛型,看不上函数式不是很正常吗

WebClient 可以让你在一次处理请求的时候发超多的异步 RPC 调用,如果用普通的 HTTP Client 的话同时发起的异步 RPC 就受线程池数量限制了,记得最新的 Apache HttpClient 底层也是 NIO 了吧,效果应该跟 WebClient 差不多

8
#11
#13

对于怎样使用 WebClient ,我有太多疑问

1 )是否可以整个系统,所有 restful 客户端使用同一个 webClient 对象?或者是否要改为每个 restful 客户端都各自 builder 一个 webClient ?还是每次请求都 build 一个?

2 ) webClient 的 baseUrl 之类的统一设置,会不会提高性能?

3 )关于 Timeout 配置,build 的时候统一配置的 connectTimeout 、readTimeout 、writeTimeout ,跟 Mono 和 Flux 的 timeout ,两个有什么不同。

4 )写 restful 客户端时,有例子把 webClient 作为私用属性,加上 ,让 Spring 初始化后装配进去,也有例子设置 WebClient.Builder 参数。两种方式,哪个更佳?

等等……

所以想问问有没有最佳实践,或者有哪些开源项目的代码可以直接参考?

www.baeldung.com/spring-5-webclient

完全正确。
你可以看看这个 www.bilibili.com/video/BV1Gq4y1e752 ,这有用到你说的那个响应式编程 httpclient 。#6 楼 说的是非常正确的。如果你的整个项目都是同步编程开发,那么最好不要用响应式编程的工具,不然会很麻烦,除非你很清楚这个使用方式。 这个讲 NIO 的为啥那么强,最好看下这个, www.bilibili.com/video/BV1Gq4y1e752

最近刚好在用这个,也找了很多,最佳实践是否需要根据自己的场景来呢?可能也有不足的地方,希望懂的大佬提出。

我这边需要对接不同厂家,每个厂家的服务会部署在多个机器上,以下是我的配置

这是 WebClient.Builder 以下配置根据自己情况修改


public class WebFluxConfig {

 public WebClient.Builder getWebClientBuilder() {
 //配置固定大小连接池
 ConnectionProvider connectionProvider = ConnectionProvider.create("DS-connection", 20);
 //设置 ssl 信任客户端
 SslContext sslContext = null;
 try {
 sslContext = SslContextBuilder.forClient()
 .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
 } catch (SSLException e) {
 e.printStackTrace();
 }
 SslContext finalSslContext = sslContext;
 HttpClient httpClient = HttpClient.create(connectionProvider).secure(t -> t.sslContext(finalSslContext))
 .tcpConfiguration(tcpClient -> tcpClient.doOnConnect(conn ->
 //读超时 30 秒
 conn.handler(new ReadTimeoutHandler(30, TimeUnit.SECONDS))
 //写超时 30 秒
 .handler(new WriteTimeoutHandler(30, TimeUnit.SECONDS))
 )
 //连接超时 60 秒
 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000)
 .option(ChannelOption.TCP_NODELAY, true));

 return WebClient.builder().clientConnector(new ReactorClientHttpConnector( httpClient));
 }
}

不同的厂商 参数 如果是固定的 可以通过读取配置完成


public class Test {

 private WebClient.Builder webClientBuilder;

 /**
 * uri
 * parameter
 *
 */
 public Mono<HashMap> get(String url, String token, String uri, Object... parameter) throws Exception {
 return null;
 }

 /**
 * uri
 *
 */
 public Mono<HashMap> get(String url, String token, String uri) throws Exception {
 return null;
 }

 /**
 * uri
 * parameter
 *
 */
 public Mono<HashMap> post(String url, String token, String uri, Object parameter) throws Exception {
 return null;
 }
}

异常我是抛出在同一个地方统一处理

最后我觉得 还是需要根据你的实际场景来,我也是第一次用这个,RestTemplate 就向上面说的没有新增加了,推荐使用 webclient ,所以才想尝试一下用的这个,项目主要还是 springmvc ,希望能帮助到你。

我用过 > 9 种 AIO Http Client
Scala: http4s / tapir / akka-http / play ws client
Python: aio-http
Kotlin: ktor on CIO
Rust: reqwest
Javascript: axios / fetch

说实话使用体验非常流畅,Spring 新版的 WebClient 因为还是受到 Java 语言不灵活的限制,使用体验大概率是要比以上 9 种都要差一些的。

Reactive Stream 的使用体验就是,>100 TPS 的服务才占用 0.2 个 CPU 核心,以至于我开发的服务在公司里没什么存在感。而且错误处理和理论模型也比 Go 要强好多倍。

既然 Spring 新加了这功能,我觉得你不妨体验一下,至于封装嘛,建议抄一下 Axios 之类的设计,让同事们更好上手。不要固步自封,不然升级了和没升级有什么区别呢。

除非你的项目中 http 占用大量资源,否则不如用老牌 apache httpclient 想要 nio 可以用 apache async client 。不推荐用,

毕竟大多数项目的耗时大头都在数据库层面,使用堵塞 io 只是比 nio 多占几十个个线程,还不至于影响到问题的核心。
我曾将公司项目从 apache httpclient 改为 webclient ,从压测上看对 tps 提升十分有限。因为 tps 上升之后下游服务瓶颈在数据库,导致堵塞本服务,所以本服务是否用 nio 都不会对 tps 带来本质提升。

但却带来挺多坏处,比如偶尔发生的 io 异常,连接中断之类的。使用 apache httpclient 后再也没有遇到这种情况。

还有就是堵塞式的天生是线程隔离的,输出日志携带 mdc 查日志要方便很多。

我直接告诉你吧。webflux 提高性能并不明显。,但是可以可以提高并发量指标。servlet 的方式访问并发不高, 请求时间有长有短。wenflux 并发上去了,时间很稳定。 如果追求高并发,和接口时间稳定。 可以考虑上 webflux ,我们业务有一块是日志统计,用的就是 webflux. 感觉不错。 我做过压力测试 www.jianshu.com/p/77e8b64ab710