V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fox0001
V2EX  ›  Java

关于 Spring 5 的 WebClient 的困惑

  •  
  •   fox0001 · 75 天前 via Android · 2302 次点击
    这是一个创建于 75 天前的主题,其中的信息可能已经有所发展或是发生改变。

    公司新项目用上了 JDK 17 + Spring 2.6.6 。发现 Spring 5 新增了 WebClient 类,使用非阻塞 IO 、支持函数式编程等等一大堆优点。但是使用时,很多跟 Apache HttpClient 那一套有太多不同。

    想问问各位大佬,有没有关于 WebClient 的最佳实践?

    另外,如果要使用 WebClient 封装一个通用的 HttpClient 类,要注意什么?

    第 1 条附言  ·  73 天前
    感谢各位回复!

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

    WebClient 目前先不用到项目上,后面再深入研究,毕竟 Spring 官方主推,以后可能用得上。
    21 条回复    2022-04-18 01:05:02 +08:00
    zartouch
        1
    zartouch  
       75 天前   ❤️ 1
    webclient 用的是响应式编程那套设计的, 如果不是吞吐量太大 (而且 IO 占比不高的也不好说,具体数据请自行 google ,既然你问了大概率不需要), 最佳实践就是没有必要别用,还是 spring MVC blocking IO 那套好写好维护。
    monkeyWie
        2
    monkeyWie  
       75 天前   ❤️ 1
    如果你没用 spring webflux ,就没必要用 webclient
    fox0001
        3
    fox0001  
    OP
       75 天前 via Android
    @zartouch #1
    @monkeyWie #2
    那就是回归到 JDK 自带 Http Client 、Apache HttpClient 和 OKHttp 这几个选项了
    yazinnnn
        4
    yazinnnn  
       75 天前   ❤️ 1
    https://projectreactor.io/

    https://www.baeldung.com/spring-5-webclient

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

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

    貌似 reactive transaction 和 kotlin 的 suspend 函数集成还在 wip 阶段,感兴趣可以自己看看
    fox0001
        5
    fox0001  
    OP
       75 天前 via Android
    @yazinnnn #4
    就是看了太多文章,有太多困惑。
    比如:

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

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

    3 )领导说要撸一个通用的 HttpClient 类,屏蔽各种底层实现。但是 WebClient 的函数式编程太爽了,撸个通用的,还不如直接使用。况且各种 http 服务有各种要求…
    zartouch
        6
    zartouch  
       75 天前
    @fox0001 嗯随便找个顺手的就行。 说实话我觉得你对响应式编程和它的应用场景,基本概念还很模糊,最好先看看这部分知识,看点例子。 除非你处理流程都是非阻塞的,否则单独用 webclient 最后你还得 call block 拿结果,等于是阻塞模型,而且 webclient 底层是 netty 非阻塞的那套,线程设置的很少,你用阻塞来玩,性能反而更差,流量大点甚至导致线程用尽。
    chendy
        7
    chendy  
       75 天前   ❤️ 1
    WebClient 是 web flux 的,响应式那套东西,怎么说呢,没必要就别用,写起来还是累
    另外既然 spring 了就 RestTemplate 呗,底层适配多种 http 库,配合 Spring boot 的 RestTemplateBuilder 用,也是个“通用”的 HttpClient 了
    Kyle18Tang
        8
    Kyle18Tang  
       75 天前   ❤️ 1
    推荐 WebClient ,RestTemplate 已经没有新功能增加了,我记得 Spring 官方文档也有写推荐使用 WebClient ,Spring MVC 项目也可以使用 WebClient 。
    Kyle18Tang
        9
    Kyle18Tang  
       75 天前
    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.
    cheng6563
        10
    cheng6563  
       74 天前   ❤️ 1
    没事别用 aio ,会变的不幸。我完全理解不了函数式编程有什么爽的。
    让我用 aio 我不如直接重新用 go 写。
    yazinnnn
        11
    yazinnnn  
       74 天前
    配合 mvc 用那就简单了,直接 block()就行

    exchangeToMono/exchangeToFlux 方法里可以传一个类型为(ClientResponse)->Mono/Flux 的 lambda,想要啥都在那里面
    yazinnnn
        12
    yazinnnn  
       74 天前
    go boy 能接受 if err!=null ,却不能接受泛型,看不上函数式不是很正常吗
    MakHoCheung
        13
    MakHoCheung  
       74 天前   ❤️ 1
    WebClient 可以让你在一次处理请求的时候发超多的异步 RPC 调用,如果用普通的 HTTP Client 的话同时发起的异步 RPC 就受线程池数量限制了,记得最新的 Apache HttpClient 底层也是 NIO 了吧,效果应该跟 WebClient 差不多
    fox0001
        14
    fox0001  
    OP
       74 天前
    @Kyle18Tang 8
    @yazinnnn #11
    @MakHoCheung #13

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

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

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

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

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

    等等……

    所以想问问有没有最佳实践,或者有哪些开源项目的代码可以直接参考?
    cnhongwei
        15
    cnhongwei  
       74 天前   ❤️ 1
    byte10
        16
    byte10  
       74 天前   ❤️ 1
    @zartouch 完全正确。
    @fox0001 你可以看看这个 https://www.bilibili.com/video/BV1Gq4y1e752 ,这有用到你说的那个响应式编程 httpclient 。#6 楼 说的是非常正确的。如果你的整个项目都是同步编程开发,那么最好不要用响应式编程的工具,不然会很麻烦,除非你很清楚这个使用方式。 这个讲 NIO 的为啥那么强,最好看下这个,https://www.bilibili.com/video/BV1Gq4y1e752
    V2Q
        17
    V2Q  
       74 天前   ❤️ 1
    最近刚好在用这个,也找了很多,最佳实践是否需要根据自己的场景来呢?可能也有不足的地方,希望懂的大佬提出。

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

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

    ```java
    @Configuration
    @EnableWebFlux
    @Slf4j
    public class WebFluxConfig {

    @Bean
    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));
    }
    }
    ```

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

    ```java
    @Component
    public class Test {

    @Autowired
    private WebClient.Builder webClientBuilder;

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

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

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

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

    最后我觉得 还是需要根据你的实际场景来,我也是第一次用这个,RestTemplate 就向上面说的没有新增加了,推荐使用 webclient ,所以才想尝试一下用的这个,项目主要还是 springmvc ,希望能帮助到你。
    lmshl
        18
    lmshl  
       74 天前   ❤️ 1
    我用过 > 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 之类的设计,让同事们更好上手。不要固步自封,不然升级了和没升级有什么区别呢。
    git00ll
        19
    git00ll  
       74 天前   ❤️ 1
    除非你的项目中 http 占用大量资源,否则不如用老牌 apache httpclient 想要 nio 可以用 apache async client 。不推荐用,
    git00ll
        20
    git00ll  
       74 天前
    毕竟大多数项目的耗时大头都在数据库层面,使用堵塞 io 只是比 nio 多占几十个个线程,还不至于影响到问题的核心。
    我曾将公司项目从 apache httpclient 改为 webclient ,从压测上看对 tps 提升十分有限。因为 tps 上升之后下游服务瓶颈在数据库,导致堵塞本服务,所以本服务是否用 nio 都不会对 tps 带来本质提升。

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

    还有就是堵塞式的天生是线程隔离的,输出日志携带 mdc 查日志要方便很多。
    jeesk
        21
    jeesk  
       73 天前 via Android   ❤️ 1
    我直接告诉你吧。webflux 提高性能并不明显。,但是可以可以提高并发量指标。servlet 的方式访问并发不高, 请求时间有长有短。wenflux 并发上去了,时间很稳定。 如果追求高并发,和接口时间稳定。 可以考虑上 webflux ,我们业务有一块是日志统计,用的就是 webflux. 感觉不错。 我做过压力测试 https://www.jianshu.com/p/77e8b64ab710
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3131 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 04:53 · PVG 12:53 · LAX 21:53 · JFK 00:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.