记录一篇Spring 5的WebClient 的 重试问题

从Spring 5开始WebClient使用Reactive HTTP Client 时有关超时和重试的说明。

验证时的库版本如下。

Spring Boot 2.1.1.RELEASE
Spring Framework 5.1.3.RELEASE
Reactor Core 3.2.3.RELEASE
Reactor Netty 0.8.3.RELEASE
Reactor Extra 3.2.0.RELEASE
Netty 4.1.31.Final

特别要注意的是,如果Reactor Netty版本发生更改,代码示例可能无法编译。

因为测试案例为超时,所以以下代码192.168.10.110是不存在的计算机/容器的IP地址。

import reactor.core.publisher.Mono;

import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.create();
        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class);
        response.log().subscribe();

        System.in.read();
    }
}

执行此操作时,将输出以下日志。

01:45:51.857 [main] INFO reactor.Mono.FlatMap.1 - | onSubscribe([Fuseable] MonoFlatMap.FlatMapMain)
01:45:51.862 [main] INFO reactor.Mono.FlatMap.1 - | request(unbounded)
01:45:51.881 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [10cf09e8] HTTP GET http://192.168.10.100:8088
01:45:51.919 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
01:45:52.096 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 89636 (auto-detected)
01:45:52.121 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
01:45:52.161 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
01:45:52.163 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
01:45:52.166 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
01:45:52.222 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x70f773a3] Created new pooled channel, now 0 active connections and 1 inactive connections
01:45:52.260 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
01:45:52.260 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
01:45:52.261 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@767628ef
01:45:52.272 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x70f773a3] Initialized pipeline DefaultChannelPipeline{(BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.left.decompressor = io.netty.handler.codec.http.HttpContentDecompressor), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
01:46:22.285 [reactor-http-nio-3] ERROR reactor.Mono.FlatMap.1 - | onError(io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088)
01:46:22.286 [reactor-http-nio-3] ERROR reactor.Mono.FlatMap.1 - 
io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe$1.run(AbstractNioChannel.java:267)
    at io.netty.util.concurrent.PromiseTask$RunnableAdapter.call(PromiseTask.java:38)
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:127)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)

我订阅了HTTP请求,但发生了连接超时。HTTP请求后30秒发生超时,并onError调用Reactive Streams 方法。发生的异常io.netty.channel.ConnectTimeoutException

可能默认的连接超时值是30秒。

设置超时属性

由于30秒很长,我想将超时设置为3秒。这里,timeout(Duration)使用Reactor静态类,使用如下。

import java.time.Duration;

import reactor.core.publisher.Mono;

import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.create();
        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .timeout(Duration.ofSeconds(3)); // <-- timeout
        response.log().subscribe();

        System.in.read();
    }
}

执行此操作时,将输出以下日志。

01:47:54.982 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
01:47:54.985 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
01:47:55.013 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [10cf09e8] HTTP GET http://192.168.10.100:8088
01:47:55.021 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
01:47:55.082 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 90005 (auto-detected)
01:47:55.088 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
01:47:55.109 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
01:47:55.109 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
01:47:55.109 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
01:47:55.144 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xfc5a4111] Created new pooled channel, now 0 active connections and 1 inactive connections
01:47:55.174 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
01:47:55.174 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
01:47:55.176 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@11df4575
01:47:55.186 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xfc5a4111] Initialized pipeline DefaultChannelPipeline{(BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.left.decompressor = io.netty.handler.codec.http.HttpContentDecompressor), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
01:47:57.998 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [10cf09e8] Cancel signal (to close connection)
01:47:57.998 [parallel-1] ERROR reactor.Mono.Timeout.1 - onError(java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured))
01:47:57.999 [parallel-1] ERROR reactor.Mono.Timeout.1 - 
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:288)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:273)
    at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:390)
    at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

订阅后3秒生成超时,并onError调用该方法。发生的异常java.util.concurrent.TimeoutException

和我们想的一样。timeout属性是设置进程的超时时间,如果处理时间超过指定时间则取消,无论目标是否是HTTP请求。
因此,很难从堆栈跟踪中确定它是连接超时还是读取超时。

使用窃听检查HTTP日志

需要打印出HTTP / TCP的建立日志。需要设置WebClient内部使用HttpClient,请执行以下操作。

import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(
                        new ReactorClientHttpConnector(HttpClient.create().wiretap(true))) // <-- 设置该属性
                .build();
        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class);
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

01:51:45.602 [main] INFO reactor.Mono.FlatMap.1 - | onSubscribe([Fuseable] MonoFlatMap.FlatMapMain)
01:51:45.606 [main] INFO reactor.Mono.FlatMap.1 - | request(unbounded)
01:51:45.657 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1f010bf0] HTTP GET http://192.168.10.100:8088
01:51:45.689 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
01:51:45.890 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 90701 (auto-detected)
01:51:45.896 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
01:51:45.920 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
01:51:45.920 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
01:51:45.920 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
01:51:45.949 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf454c985] Created new pooled channel, now 0 active connections and 1 inactive connections
01:51:45.976 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
01:51:45.976 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
01:51:45.978 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6c29d2e0
01:51:45.984 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xf454c985] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
01:51:45.987 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf454c985] REGISTERED
01:51:45.988 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf454c985] CONNECT: /192.168.10.100:8088
01:52:15.991 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf454c985] CLOSE
01:52:15.991 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf454c985] UNREGISTERED
01:52:15.993 [reactor-http-nio-3] ERROR reactor.Mono.FlatMap.1 - | onError(io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088)
01:52:15.993 [reactor-http-nio-3] ERROR reactor.Mono.FlatMap.1 - 
io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe$1.run(AbstractNioChannel.java:267)
    at io.netty.util.concurrent.PromiseTask$RunnableAdapter.call(PromiseTask.java:38)
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:127)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)
BootstrapHandlersDEBUG日志o.netty.handler.logging.LoggingHandler已添加。

据了解,在关闭HTTP之后,CLOSE进行了30秒。

接下来设置一个超时属性timeout

import java.time.Duration;

import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(
                        new ReactorClientHttpConnector(HttpClient.create().wiretap(true)))
                .build();
        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

01:56:38.507 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
01:56:38.512 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
01:56:38.556 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1f010bf0] HTTP GET http://192.168.10.100:8088
01:56:38.573 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
01:56:38.712 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 91574 (auto-detected)
01:56:38.719 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
01:56:38.770 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
01:56:38.770 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
01:56:38.770 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
01:56:38.815 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xdc9b6f12] Created new pooled channel, now 0 active connections and 1 inactive connections
01:56:38.845 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
01:56:38.845 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
01:56:38.846 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@59e95cc9
01:56:38.854 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xdc9b6f12] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
01:56:38.858 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xdc9b6f12] REGISTERED
01:56:38.858 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xdc9b6f12] CONNECT: /192.168.10.100:8088
01:56:41.531 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1f010bf0] Cancel signal (to close connection)
01:56:41.531 [parallel-1] ERROR reactor.Mono.Timeout.1 - onError(java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured))
01:56:41.532 [parallel-1] ERROR reactor.Mono.Timeout.1 - 
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:288)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:273)
    at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:390)
    at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

在CONNECT之后,可以看到处理在订阅后3秒被中断而不等待CLOSE。

Netty层中的连接超时设置

接下来,在Netty层中设置Connection Timeout。

以下代码设置连接超时值为500 ms。

import java.time.Duration;

import io.netty.channel.ChannelOption;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create()
                        .tcpConfiguration(tcpClient -> tcpClient
                                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500)) // <-- connection timeout 设置此处
                        .wiretap(true)))
                .build();
        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

02:20:31.417 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
02:20:31.419 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
02:20:31.439 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [10cf09e8] HTTP GET http://192.168.10.100:8088
02:20:31.447 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
02:20:31.782 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 95916 (auto-detected)
02:20:31.789 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
02:20:31.842 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:20:31.842 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:20:31.842 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:20:31.887 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd0d6eda8] Created new pooled channel, now 0 active connections and 1 inactive connections
02:20:31.916 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
02:20:31.916 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
02:20:31.917 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@59e95cc9
02:20:31.926 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xd0d6eda8] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:20:31.930 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd0d6eda8] REGISTERED
02:20:31.931 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd0d6eda8] CONNECT: /192.168.10.100:8088
02:20:32.437 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd0d6eda8] CLOSE
02:20:32.438 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd0d6eda8] UNREGISTERED
02:20:32.439 [reactor-http-nio-3] ERROR reactor.Mono.Timeout.1 - onError(io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088)
02:20:32.439 [reactor-http-nio-3] ERROR reactor.Mono.Timeout.1 - 
io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe$1.run(AbstractNioChannel.java:267)
    at io.netty.util.concurrent.PromiseTask$RunnableAdapter.call(PromiseTask.java:38)
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:127)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)

在HTTP CONNECT之后,在500毫秒后关闭,可以看出io.netty.channel.ConnectTimeoutException 异常已经发生了。操作员未被调用,因为它在3秒之前错误地结束timeout。

连接拒绝发生的示例

让我们看下一点,看看连接被拒绝而不是连接超时的情况会发生什么。
Connection Timeout不会返回对请求的响应,但Connection Refused会返回对请求的拒绝响应。
例如,可能存在端口号不正确或被防火墙拒绝的情况。

localhost:8088更改代码的连接目标。这里的假设是本地机器没有端口8088。

import java.time.Duration;

import io.netty.channel.ChannelOption;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create()
                        .tcpConfiguration(tcpClient -> tcpClient
                                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500))
                        .wiretap(true)))
                .build();
        Mono response = webClient.get() //
                .uri("http://localhost:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

02:02:06.047 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
02:02:06.048 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
02:02:06.068 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [10cf09e8] HTTP GET http://localhost:8088
02:02:06.077 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for localhost:8088
02:02:06.149 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 92560 (auto-detected)
02:02:06.159 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
02:02:06.195 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:02:06.195 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:02:06.195 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:02:06.248 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x345a4a73] Created new pooled channel, now 0 active connections and 1 inactive connections
02:02:06.319 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
02:02:06.320 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
02:02:06.325 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@1f38b6b1
02:02:06.344 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x345a4a73] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:02:06.362 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x345a4a73] REGISTERED
02:02:06.363 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x345a4a73] CONNECT: localhost/127.0.0.1:8088
02:02:06.379 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x345a4a73] CLOSE
02:02:06.380 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x345a4a73] UNREGISTERED
02:02:06.381 [reactor-http-nio-3] ERROR reactor.Mono.Timeout.1 - onError(io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088)
02:02:06.381 [reactor-http-nio-3] ERROR reactor.Mono.Timeout.1 - 
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088
    at java.base/sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:779)
    at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:327)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:340)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:636)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:583)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:500)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:462)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.net.ConnectException: Connection refused
    ... 10 common frames omitted

在HTTP CONNECT之后,在几毫秒后关闭,已经发生java.net.ConnectException 异常。
未发生连接超时。这是另一个问题。

重试过程

连接超时和连接拒绝可能会随着时间的推移而改善。
例如,当容器恢复或iptable设置时。
我想尝试重试,这样我就可以在这种情况下自主恢复。

可以设置 Reactor retry(long)和retry(Predicate),
如果要精确的控制重试可以使用Reactor的retryWhen 函数

首先,我们尝试从连接拒绝的情况。
以下代码最多重试2次,并设置500 ms的间隔。

import java.time.Duration;

import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(
                        new ReactorClientHttpConnector(HttpClient.create().wiretap(true)))
                .build();

        Retry retry = Retry.any() //
                .retryMax(2) //
                .backoff(Backoff.fixed(Duration.ofMillis(500)));

        Mono response = webClient.get() //
                .uri("http://localhost:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .retryWhen(retry) // <-- retry
                .timeout(Duration.ofSeconds(3));

        System.in.read();
    }
}

retryWhentimeout因为外面有一个操作员,无论怎么重试都会在3秒内中断。

执行时输出以下日志。

02:11:02.622 [main] INFO reactor.Mono.RetryWhen.1 - onSubscribe(SerializedSubscriber)
02:11:02.623 [main] INFO reactor.Mono.RetryWhen.1 - request(unbounded)
02:11:02.655 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://localhost:8088
02:11:02.665 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for localhost:8088
02:11:02.779 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 94192 (auto-detected)
02:11:02.786 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
02:11:02.814 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:11:02.814 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:11:02.814 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:11:02.852 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x9b8d0dc4] Created new pooled channel, now 0 active connections and 1 inactive connections
02:11:02.945 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
02:11:02.945 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
02:11:02.947 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@4cf2f770
02:11:02.957 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x9b8d0dc4] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:11:02.964 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x9b8d0dc4] REGISTERED
02:11:02.965 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x9b8d0dc4] CONNECT: localhost/127.0.0.1:8088
02:11:02.972 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x9b8d0dc4] CLOSE
02:11:02.973 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x9b8d0dc4] UNREGISTERED
02:11:02.973 [reactor-http-nio-3] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=1 exception=io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088 backoff={500ms}
02:11:03.481 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://localhost:8088
02:11:03.483 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf20df237] Created new pooled channel, now 0 active connections and 1 inactive connections
02:11:03.483 [reactor-http-nio-6] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xf20df237] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:11:03.483 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf20df237] REGISTERED
02:11:03.484 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf20df237] CONNECT: localhost/127.0.0.1:8088
02:11:03.484 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf20df237] CLOSE
02:11:03.485 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf20df237] UNREGISTERED
02:11:03.484 [reactor-http-nio-5] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=2 exception=io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088 backoff={500ms}
02:11:03.988 [parallel-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://localhost:8088
02:11:03.989 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x4e2e8307] Created new pooled channel, now 0 active connections and 1 inactive connections
02:11:03.989 [reactor-http-nio-8] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x4e2e8307] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:11:03.989 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x4e2e8307] REGISTERED
02:11:03.989 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x4e2e8307] CONNECT: localhost/127.0.0.1:8088
02:11:03.990 [reactor-http-nio-7] DEBUG reactor.retry.DefaultRetry - Retries exhausted, retry context: iteration=3 exception=io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088 backoff={EXHAUSTED}
02:11:03.991 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x4e2e8307] CLOSE
02:11:03.992 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x4e2e8307] UNREGISTERED
02:11:03.994 [reactor-http-nio-7] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] Cancel signal (to close connection)
02:11:03.994 [reactor-http-nio-7] ERROR reactor.Mono.RetryWhen.1 - onError(reactor.retry.RetryExhaustedException: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088)
02:11:03.994 [reactor-http-nio-7] ERROR reactor.Mono.RetryWhen.1 - 
reactor.retry.RetryExhaustedException: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088
    at reactor.retry.DefaultRetry.retry(DefaultRetry.java:130)
    at reactor.retry.DefaultRetry.lambda$apply$1(DefaultRetry.java:115)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:368)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:244)
    at reactor.core.publisher.FluxIndex$IndexSubscriber.onNext(FluxIndex.java:96)
    at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:333)
    at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142)
    at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:89)
    at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:160)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:165)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onError(Operators.java:1718)
    at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:126)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:87)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onError(MonoFlatMapMany.java:193)
    at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onError(FluxRetryPredicate.java:100)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:167)
    at reactor.netty.http.client.HttpClientConnect$MonoHttpConnect$TcpClientSubscriber.onError(HttpClientConnect.java:344)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:167)
    at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.operationComplete(PooledConnectionProvider.java:598)
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511)
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:485)
    at io.netty.util.concurrent.DefaultPromise.access$000(DefaultPromise.java:33)
    at io.netty.util.concurrent.DefaultPromise$1.run(DefaultPromise.java:435)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8088
    at java.base/sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:779)
    at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:327)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:340)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:636)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:583)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:500)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:462)
    ... 2 common frames omitted
Caused by: java.net.ConnectException: Connection refused
    ... 10 common frames omitted

可以看出该过程如下。

CONNECT -> [2-10 ms] -> CLOSE -> [500 ms] -> CONNECT -> [2-10 ms] -> CLOSE -> [500 ms] -> CONNECT -> [2-10 ms] -> CLOSE -> onError(RetryExhaustedException)

重试两次后,reactor.retry.RetryExhaustedException 异常已经发生了。经过的时间约为1.2秒且小于超时时间。

接下来,尝试连接超时案例。在这种情况下,我没有故意设置Netty的连接超时。

import java.time.Duration;

import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(
                        new ReactorClientHttpConnector(HttpClient.create().wiretap(true)))
                .build();

        Retry retry = Retry.any() //
                .retryMax(2) //
                .backoff(Backoff.fixed(Duration.ofMillis(500)));

        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .retryWhen(retry) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

02:15:44.339 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
02:15:44.341 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
02:15:44.390 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://192.168.10.100:8088
02:15:44.398 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
02:15:44.469 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 95045 (auto-detected)
02:15:44.481 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
02:15:44.509 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:15:44.509 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:15:44.509 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:15:44.537 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x8284305f] Created new pooled channel, now 0 active connections and 1 inactive connections
02:15:44.585 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
02:15:44.585 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
02:15:44.589 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@36c3f70c
02:15:44.605 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x8284305f] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:15:44.614 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x8284305f] REGISTERED
02:15:44.614 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x8284305f] CONNECT: /192.168.10.100:8088
02:15:47.360 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] Cancel signal (to close connection)
02:15:47.362 [parallel-1] ERROR reactor.Mono.Timeout.1 - onError(java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'retryWhen' (and no fallback has been configured))
02:15:47.363 [parallel-1] ERROR reactor.Mono.Timeout.1 - 
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'retryWhen' (and no fallback has been configured)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:288)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:273)
    at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:390)
    at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

可以看出该过程如下。

CONNECT -> [3 ms] -> onError(TimeoutException)

这timeout与第一次引入操作员的结果相同。应注意,即使包括重试控制,也可获得相同的结果。

假设在Connect失败后执行重试,timeout但是处理被中断而不重试,因为在第一个Connect中超出了高级操作员时间。

**现在retryWhen并timeout试图改变的顺序。 这里顺序很重要 **

import java.time.Duration;

import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
            .clientConnector(
                new ReactorClientHttpConnector(HttpClient.create().wiretap(true)))
            .build();

        Retry retry = Retry.any() //
            .retryMax(2) //
            .backoff(Backoff.fixed(Duration.ofMillis(500)));

        Mono response = webClient.get() //
            .uri("http://192.168.10.100:8088") //
            .retrieve() //
            .bodyToMono(String.class) //
            .timeout(Duration.ofSeconds(3)) // <--- !!!
            .retryWhen(retry);
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

03:29:26.174 [main] INFO reactor.Mono.RetryWhen.1 - onSubscribe(SerializedSubscriber)
03:29:26.175 [main] INFO reactor.Mono.RetryWhen.1 - request(unbounded)
03:29:26.231 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://192.168.10.100:8088
03:29:26.238 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
03:29:26.308 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 8541 (auto-detected)
03:29:26.314 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
03:29:26.354 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
03:29:26.354 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
03:29:26.354 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
03:29:26.435 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x7c5211f4] Created new pooled channel, now 0 active connections and 1 inactive connections
03:29:26.474 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
03:29:26.475 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
03:29:26.476 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@dd79880
03:29:26.484 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x7c5211f4] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
03:29:26.489 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x7c5211f4] REGISTERED
03:29:26.489 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x7c5211f4] CONNECT: /192.168.10.100:8088
03:29:29.217 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] Cancel signal (to close connection)
03:29:29.218 [parallel-1] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=1 exception=java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured) backoff={500ms}
03:29:29.722 [parallel-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://192.168.10.100:8088
03:29:29.723 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x34119841] Created new pooled channel, now 0 active connections and 2 inactive connections
03:29:29.723 [reactor-http-nio-6] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x34119841] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
03:29:29.724 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0x34119841] REGISTERED
03:29:29.724 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0x34119841] CONNECT: /192.168.10.100:8088
03:29:32.725 [parallel-3] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] Cancel signal (to close connection)
03:29:32.725 [parallel-3] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=2 exception=java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured) backoff={500ms}
03:29:33.228 [parallel-4] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] HTTP GET http://192.168.10.100:8088
03:29:33.229 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x88d2cad9] Created new pooled channel, now 0 active connections and 3 inactive connections
03:29:33.230 [reactor-http-nio-8] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x88d2cad9] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
03:29:33.230 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x88d2cad9] REGISTERED
03:29:33.230 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x88d2cad9] CONNECT: /192.168.10.100:8088
03:29:36.233 [parallel-5] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [1a6c1270] Cancel signal (to close connection)
03:29:36.233 [parallel-5] DEBUG reactor.retry.DefaultRetry - Retries exhausted, retry context: iteration=3 exception=java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured) backoff={EXHAUSTED}
03:29:36.235 [parallel-5] ERROR reactor.Mono.RetryWhen.1 - onError(reactor.retry.RetryExhaustedException: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured))
03:29:36.236 [parallel-5] ERROR reactor.Mono.RetryWhen.1 - 
reactor.retry.RetryExhaustedException: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured)
    at reactor.retry.DefaultRetry.retry(DefaultRetry.java:130)
    at reactor.retry.DefaultRetry.lambda$apply$1(DefaultRetry.java:115)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:368)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:244)
    at reactor.core.publisher.FluxIndex$IndexSubscriber.onNext(FluxIndex.java:96)
    at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:333)
    at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142)
    at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:89)
    at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:160)
    at reactor.core.publisher.SerializedSubscriber.onError(SerializedSubscriber.java:114)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:288)
    at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:273)
    at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:390)
    at reactor.core.publisher.StrictSubscriber.onNext(StrictSubscriber.java:89)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
    at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'flatMap' (and no fallback has been configured)
    ... 13 common frames omitted

可以看出该过程如下。

CONNECT -> [3 s] -> cancel -> [500 ms] -> CONNECT -> [3 s] -> cancel -> [500 ms] -> CONNECT -> [3 s] -> cancel -> onError(RetryExhaustedException)

timeout除了上述之外,retryWhen总共执行3次超时3秒,并且可以看出完成该过程大约需要10秒。

最后,恢复最后一个的顺序retryWhen并timeout重新设置Netty的连接超时设置。

import java.time.Duration;

import io.netty.channel.ChannelOption;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create()
                        .tcpConfiguration(tcpClient -> tcpClient
                                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 500))
                        .wiretap(true)))
                .build();

        Retry retry = Retry.any() //
                .retryMax(2) //
                .backoff(Backoff.fixed(Duration.ofMillis(500)));

        Mono response = webClient.get() //
                .uri("http://192.168.10.100:8088") //
                .retrieve() //
                .bodyToMono(String.class) //
                .retryWhen(retry) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

02:18:30.424 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
02:18:30.427 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
02:18:30.480 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [18a136ac] HTTP GET http://192.168.10.100:8088
02:18:30.488 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for /192.168.10.100:8088
02:18:30.602 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 95546 (auto-detected)
02:18:30.609 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
02:18:30.638 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:18:30.639 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:18:30.639 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:18:30.697 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x62943ed1] Created new pooled channel, now 0 active connections and 1 inactive connections
02:18:30.732 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
02:18:30.732 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
02:18:30.733 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6d6af5d2
02:18:30.741 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x62943ed1] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:18:30.745 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x62943ed1] REGISTERED
02:18:30.745 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x62943ed1] CONNECT: /192.168.10.100:8088
02:18:31.252 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x62943ed1] CLOSE
02:18:31.253 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0x62943ed1] UNREGISTERED
02:18:31.254 [reactor-http-nio-3] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=1 exception=io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088 backoff={500ms}
02:18:31.759 [parallel-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [18a136ac] HTTP GET http://192.168.10.100:8088
02:18:31.760 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xc1752cf7] Created new pooled channel, now 0 active connections and 1 inactive connections
02:18:31.760 [reactor-http-nio-6] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xc1752cf7] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:18:31.761 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xc1752cf7] REGISTERED
02:18:31.761 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xc1752cf7] CONNECT: /192.168.10.100:8088
02:18:32.265 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xc1752cf7] CLOSE
02:18:32.266 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xc1752cf7] UNREGISTERED
02:18:32.266 [reactor-http-nio-5] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=2 exception=io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088 backoff={500ms}
02:18:32.770 [parallel-3] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [18a136ac] HTTP GET http://192.168.10.100:8088
02:18:32.771 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0x1962ceda] Created new pooled channel, now 0 active connections and 1 inactive connections
02:18:32.772 [reactor-http-nio-8] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0x1962ceda] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
02:18:32.772 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x1962ceda] REGISTERED
02:18:32.772 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x1962ceda] CONNECT: /192.168.10.100:8088
02:18:33.278 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x1962ceda] CLOSE
02:18:33.278 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0x1962ceda] UNREGISTERED
02:18:33.279 [reactor-http-nio-7] DEBUG reactor.retry.DefaultRetry - Retries exhausted, retry context: iteration=3 exception=io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088 backoff={EXHAUSTED}
02:18:33.282 [reactor-http-nio-7] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [18a136ac] Cancel signal (to close connection)
02:18:33.282 [reactor-http-nio-7] ERROR reactor.Mono.Timeout.1 - onError(reactor.retry.RetryExhaustedException: io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088)
02:18:33.283 [reactor-http-nio-7] ERROR reactor.Mono.Timeout.1 - 
reactor.retry.RetryExhaustedException: io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088
    at reactor.retry.DefaultRetry.retry(DefaultRetry.java:130)
    at reactor.retry.DefaultRetry.lambda$apply$1(DefaultRetry.java:115)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:368)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:244)
    at reactor.core.publisher.FluxIndex$IndexSubscriber.onNext(FluxIndex.java:96)
    at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:333)
    at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142)
    at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:89)
    at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:160)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:165)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onError(Operators.java:1718)
    at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:126)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:87)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onError(MonoFlatMapMany.java:193)
    at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onError(FluxRetryPredicate.java:100)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:167)
    at reactor.netty.http.client.HttpClientConnect$MonoHttpConnect$TcpClientSubscriber.onError(HttpClientConnect.java:344)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:167)
    at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.operationComplete(PooledConnectionProvider.java:598)
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511)
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:485)
    at io.netty.util.concurrent.DefaultPromise.access$000(DefaultPromise.java:33)
    at io.netty.util.concurrent.DefaultPromise$1.run(DefaultPromise.java:435)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.10.100:8088
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe$1.run(AbstractNioChannel.java:267)
    at io.netty.util.concurrent.PromiseTask$RunnableAdapter.call(PromiseTask.java:38)
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:127)
    ... 5 common frames omitted

可以看出该过程如下。

CONNECT -> [500 ms] -> CLOSE -> [500 ms] -> CONNECT -> [500 ms] -> CLOSE -> [500 ms] -> CONNECT -> [500 ms] -> CLOSE -> onError(RetryExhaustedException)

与连接拒绝的情况一样,经过两次重试reactor.retry.RetryExhaustedException。
但是,由于每个进程等待500毫秒的连接超时,因此经过的时间约为2.8秒。它不会发生,timeout因为它小于操作员的超时时间TimeoutException。

也许我认为这个例子接近通常的超时设置。

不要设置Netty的连接超时,而是在之前和之后设置timeout运算符retryWhen

     .timeout(Duration.ofMillis(500))
     .retryWhen(retry) //
     .timeout(Duration.ofSeconds(3));

通过这样做可以实现相同的超时,但是在易于理解堆栈跟踪方面,前面的代码更容易理解。

为了使重试按预期工作,有必要了解内部和外部超时之间的差异。

在那之后,把断路器放在这之外是好的,但如果那次谈话是一个机会。

(附加)在Netty层中设置读取超时/写入超时

此外,在创建Connection之后,我还将描述如何设置接收响应的超时和发送请求主体的超时。
这是Netty层中的设置。

处理程序建立后CONECTION设定的Netty的TcpClient在doOnConnected可以被添加到该方法。

以下代码故意将Read Timeout设置为small,并访问需要一秒钟才能返回响应的服务。

package am.ik.blog;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.retry.Backoff;
import reactor.retry.Retry;

import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;

public class Demoo {

    public static void main(String[] args) throws Exception {
        WebClient webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create()
                        .tcpConfiguration(tcpClient -> tcpClient
                                .bootstrap(bootstrap -> bootstrap.option(
                                        ChannelOption.CONNECT_TIMEOUT_MILLIS, 500))
                                .doOnConnected(connection -> connection
                                        .addHandler(new ReadTimeoutHandler(100,
                                                TimeUnit.MILLISECONDS))
                                        .addHandler(new WriteTimeoutHandler(100,
                                                TimeUnit.MILLISECONDS))))
                        .wiretap(true)))
                .build();

        Retry retry = Retry.any() //
                .retryMax(2) //
                .backoff(Backoff.fixed(Duration.ofMillis(500)));

        Mono response = webClient.get() //
                .uri("http://httpbin.org/delay/1") //
                .retrieve() //
                .bodyToMono(String.class) //
                .retryWhen(retry) //
                .timeout(Duration.ofSeconds(3));
        response.log().subscribe();

        System.in.read();
    }
}

执行时输出以下日志。

19:05:33.719 [main] INFO reactor.Mono.Timeout.1 - onSubscribe(SerializedSubscriber)
19:05:33.721 [main] INFO reactor.Mono.Timeout.1 - request(unbounded)
19:05:33.798 [main] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [66ac5762] HTTP GET http://httpbin.org/delay/1
19:05:33.802 [main] DEBUG reactor.netty.resources.PooledConnectionProvider - Creating new client pool [http] for httpbin.org:80
19:05:33.892 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 39209 (auto-detected)
19:05:33.897 [main] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: ac:de:48:ff:fe:00:11:22 (auto-detected)
19:05:33.915 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
19:05:33.915 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
19:05:33.915 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
19:05:33.950 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc] Created new pooled channel, now 0 active connections and 1 inactive connections
19:05:34.007 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkAccessible: true
19:05:34.007 [reactor-http-nio-4] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
19:05:34.010 [reactor-http-nio-4] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@444ca454
19:05:34.035 [reactor-http-nio-4] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xf59eb6bc] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:34.055 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc] REGISTERED
19:05:34.057 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc] CONNECT: httpbin.org/54.165.51.142:80
19:05:34.258 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] ACTIVE
19:05:34.258 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] onStateChange(PooledConnection{channel=[id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80]}, [connected])
19:05:34.271 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/, connection=PooledConnection{channel=[id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80]}}, [configured])
19:05:34.273 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Registering pool release on close event for channel
19:05:34.274 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClientConnect - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Handler is being applied: {uri=http://httpbin.org/delay/1, method=GET}
19:05:34.284 [reactor-http-nio-4] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Writing object DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
GET /delay/1 HTTP/1.1
user-agent: ReactorNetty/0.8.3.RELEASE
host: httpbin.org
accept: */*
19:05:34.291 [reactor-http-nio-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096
19:05:34.291 [reactor-http-nio-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
19:05:34.291 [reactor-http-nio-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
19:05:34.291 [reactor-http-nio-4] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
19:05:34.306 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] WRITE: 97B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 64 65 6c 61 79 2f 31 20 48 54 54 |GET /delay/1 HTT|
|00000010| 50 2f 31 2e 31 0d 0a 75 73 65 72 2d 61 67 65 6e |P/1.1..user-agen|
|00000020| 74 3a 20 52 65 61 63 74 6f 72 4e 65 74 74 79 2f |t: ReactorNetty/|
|00000030| 30 2e 38 2e 33 2e 52 45 4c 45 41 53 45 0d 0a 68 |0.8.3.RELEASE..h|
|00000040| 6f 73 74 3a 20 68 74 74 70 62 69 6e 2e 6f 72 67 |ost: httpbin.org|
|00000050| 0d 0a 61 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d |..accept: */*...|
|00000060| 0a                                              |.               |
+--------+-------------------------------------------------+----------------+
19:05:34.307 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:34.313 [reactor-http-nio-4] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Writing object EmptyLastHttpContent
19:05:34.313 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] WRITE: 0B
19:05:34.313 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:34.313 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/delay/1, connection=PooledConnection{channel=[id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80]}}, [request_sent])
19:05:34.319 [reactor-http-nio-4] DEBUG reactor.netty.ReactorNetty - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Added encoder [ReadTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:34.319 [reactor-http-nio-4] DEBUG reactor.netty.ReactorNetty - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Added encoder [WriteTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, WriteTimeoutHandler, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:34.319 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Channel connected, now 1 active connections and 0 inactive connections
19:05:34.420 [reactor-http-nio-4] ERROR reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] Pooled connection observed an error
io.netty.handler.timeout.ReadTimeoutException: null
19:05:34.422 [reactor-http-nio-4] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=1 exception=io.netty.handler.timeout.ReadTimeoutException backoff={500ms}
19:05:34.423 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 - R:httpbin.org/54.165.51.142:80] CLOSE
19:05:34.424 [reactor-http-nio-4] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80] Channel cleaned, now 0 active connections and 1 inactive connections
19:05:34.424 [reactor-http-nio-4] DEBUG reactor.netty.ReactorNetty - [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: ReadTimeoutHandler, context: ChannelHandlerContext(ReadTimeoutHandler, [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:34.424 [reactor-http-nio-4] DEBUG reactor.netty.ReactorNetty - [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: WriteTimeoutHandler, context: ChannelHandlerContext(WriteTimeoutHandler, [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:34.425 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80] INACTIVE
19:05:34.425 [reactor-http-nio-4] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf59eb6bc, L:/192.168.11.38:61666 ! R:httpbin.org/54.165.51.142:80] UNREGISTERED
19:05:34.927 [parallel-2] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [66ac5762] HTTP GET http://httpbin.org/delay/1
19:05:34.928 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9] Created new pooled channel, now 0 active connections and 2 inactive connections
19:05:34.928 [reactor-http-nio-6] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xf2bc38c9] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:34.929 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9] REGISTERED
19:05:34.929 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9] CONNECT: httpbin.org/54.165.51.142:80
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] ACTIVE
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] onStateChange(PooledConnection{channel=[id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80]}, [connected])
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/, connection=PooledConnection{channel=[id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80]}}, [configured])
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Registering pool release on close event for channel
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClientConnect - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Handler is being applied: {uri=http://httpbin.org/delay/1, method=GET}
19:05:35.087 [reactor-http-nio-6] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Writing object DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
GET /delay/1 HTTP/1.1
user-agent: ReactorNetty/0.8.3.RELEASE
host: httpbin.org
accept: */*
19:05:35.099 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] WRITE: 97B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 64 65 6c 61 79 2f 31 20 48 54 54 |GET /delay/1 HTT|
|00000010| 50 2f 31 2e 31 0d 0a 75 73 65 72 2d 61 67 65 6e |P/1.1..user-agen|
|00000020| 74 3a 20 52 65 61 63 74 6f 72 4e 65 74 74 79 2f |t: ReactorNetty/|
|00000030| 30 2e 38 2e 33 2e 52 45 4c 45 41 53 45 0d 0a 68 |0.8.3.RELEASE..h|
|00000040| 6f 73 74 3a 20 68 74 74 70 62 69 6e 2e 6f 72 67 |ost: httpbin.org|
|00000050| 0d 0a 61 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d |..accept: */*...|
|00000060| 0a                                              |.               |
+--------+-------------------------------------------------+----------------+
19:05:35.099 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Writing object EmptyLastHttpContent
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] WRITE: 0B
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/delay/1, connection=PooledConnection{channel=[id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80]}}, [request_sent])
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.ReactorNetty - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Added encoder [ReadTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.ReactorNetty - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Added encoder [WriteTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, WriteTimeoutHandler, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:35.100 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Channel connected, now 1 active connections and 1 inactive connections
19:05:35.203 [reactor-http-nio-6] ERROR reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] Pooled connection observed an error
io.netty.handler.timeout.ReadTimeoutException: null
19:05:35.205 [reactor-http-nio-6] DEBUG reactor.retry.DefaultRetry - Scheduling retry attempt, retry context: iteration=2 exception=io.netty.handler.timeout.ReadTimeoutException backoff={500ms}
19:05:35.206 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 - R:httpbin.org/54.165.51.142:80] CLOSE
19:05:35.207 [reactor-http-nio-6] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80] Channel cleaned, now 0 active connections and 2 inactive connections
19:05:35.208 [reactor-http-nio-6] DEBUG reactor.netty.ReactorNetty - [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: ReadTimeoutHandler, context: ChannelHandlerContext(ReadTimeoutHandler, [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:35.210 [reactor-http-nio-6] DEBUG reactor.netty.ReactorNetty - [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: WriteTimeoutHandler, context: ChannelHandlerContext(WriteTimeoutHandler, [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:35.210 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80] INACTIVE
19:05:35.210 [reactor-http-nio-6] DEBUG reactor.netty.http.client.HttpClient - [id: 0xf2bc38c9, L:/192.168.11.38:61667 ! R:httpbin.org/54.165.51.142:80] UNREGISTERED
19:05:35.706 [parallel-3] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [66ac5762] HTTP GET http://httpbin.org/delay/1
19:05:35.708 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5] Created new pooled channel, now 0 active connections and 3 inactive connections
19:05:35.708 [reactor-http-nio-8] DEBUG reactor.netty.channel.BootstrapHandlers - [id: 0xd44acee5] Initialized pipeline DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (BootstrapHandlers$BootstrapInitializerHandler#0 = reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler), (SimpleChannelPool$1#0 = io.netty.channel.pool.SimpleChannelPool$1), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:35.708 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5] REGISTERED
19:05:35.708 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5] CONNECT: httpbin.org/54.165.51.142:80
19:05:35.865 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] ACTIVE
19:05:35.866 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] onStateChange(PooledConnection{channel=[id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80]}, [connected])
19:05:35.866 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/, connection=PooledConnection{channel=[id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80]}}, [configured])
19:05:35.866 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Registering pool release on close event for channel
19:05:35.866 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClientConnect - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Handler is being applied: {uri=http://httpbin.org/delay/1, method=GET}
19:05:35.866 [reactor-http-nio-8] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Writing object DefaultHttpRequest(decodeResult: success, version: HTTP/1.1)
GET /delay/1 HTTP/1.1
user-agent: ReactorNetty/0.8.3.RELEASE
host: httpbin.org
accept: */*
19:05:35.878 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] WRITE: 97B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 64 65 6c 61 79 2f 31 20 48 54 54 |GET /delay/1 HTT|
|00000010| 50 2f 31 2e 31 0d 0a 75 73 65 72 2d 61 67 65 6e |P/1.1..user-agen|
|00000020| 74 3a 20 52 65 61 63 74 6f 72 4e 65 74 74 79 2f |t: ReactorNetty/|
|00000030| 30 2e 38 2e 33 2e 52 45 4c 45 41 53 45 0d 0a 68 |0.8.3.RELEASE..h|
|00000040| 6f 73 74 3a 20 68 74 74 70 62 69 6e 2e 6f 72 67 |ost: httpbin.org|
|00000050| 0d 0a 61 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d |..accept: */*...|
|00000060| 0a                                              |.               |
+--------+-------------------------------------------------+----------------+
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.channel.ChannelOperationsHandler - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Writing object EmptyLastHttpContent
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] WRITE: 0B
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] FLUSH
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] onStateChange(GET{uri=/delay/1, connection=PooledConnection{channel=[id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80]}}, [request_sent])
19:05:35.879 [reactor-http-nio-8] DEBUG reactor.netty.ReactorNetty - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Added encoder [ReadTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:35.880 [reactor-http-nio-8] DEBUG reactor.netty.ReactorNetty - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Added encoder [WriteTimeoutHandler] at the beginning of the user pipeline, full pipeline: [reactor.left.loggingHandler, reactor.left.httpCodec, WriteTimeoutHandler, ReadTimeoutHandler, reactor.right.reactiveBridge, DefaultChannelPipeline$TailContext#0]
19:05:35.880 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Channel connected, now 1 active connections and 2 inactive connections
19:05:35.984 [reactor-http-nio-8] ERROR reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] Pooled connection observed an error
io.netty.handler.timeout.ReadTimeoutException: null
19:05:35.985 [reactor-http-nio-8] DEBUG reactor.retry.DefaultRetry - Retries exhausted, retry context: iteration=3 exception=io.netty.handler.timeout.ReadTimeoutException backoff={EXHAUSTED}
19:05:35.988 [reactor-http-nio-8] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [66ac5762] Cancel signal (to close connection)
19:05:35.989 [reactor-http-nio-8] ERROR reactor.Mono.Timeout.1 - onError(reactor.retry.RetryExhaustedException: io.netty.handler.timeout.ReadTimeoutException)
19:05:35.989 [reactor-http-nio-8] ERROR reactor.Mono.Timeout.1 - 
reactor.retry.RetryExhaustedException: io.netty.handler.timeout.ReadTimeoutException
    at reactor.retry.DefaultRetry.retry(DefaultRetry.java:130)
    at reactor.retry.DefaultRetry.lambda$apply$1(DefaultRetry.java:115)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:368)
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:244)
    at reactor.core.publisher.FluxIndex$IndexSubscriber.onNext(FluxIndex.java:96)
    at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:333)
    at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142)
    at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:89)
    at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:160)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:165)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onError(Operators.java:1718)
    at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:126)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onError(FluxPeek.java:214)
    at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:87)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onError(MonoFlatMapMany.java:193)
    at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onError(FluxRetryPredicate.java:100)
    at reactor.core.publisher.MonoCreate$DefaultMonoSink.error(MonoCreate.java:167)
    at reactor.netty.http.client.HttpClientConnect$HttpObserver.onUncaughtException(HttpClientConnect.java:376)
    at reactor.netty.ReactorNetty$CompositeConnectionObserver.onUncaughtException(ReactorNetty.java:350)
    at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.onUncaughtException(PooledConnectionProvider.java:493)
    at reactor.netty.resources.PooledConnectionProvider$PooledConnection.onUncaughtException(PooledConnectionProvider.java:402)
    at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:176)
    at reactor.netty.channel.FluxReceive.onInboundError(FluxReceive.java:364)
    at reactor.netty.channel.ChannelOperations.onInboundError(ChannelOperations.java:396)
    at reactor.netty.channel.ChannelOperationsHandler.exceptionCaught(ChannelOperationsHandler.java:185)
    at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:285)
    at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:264)
    at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:256)
    at io.netty.handler.timeout.ReadTimeoutHandler.readTimedOut(ReadTimeoutHandler.java:98)
    at io.netty.handler.timeout.ReadTimeoutHandler.channelIdle(ReadTimeoutHandler.java:90)
    at io.netty.handler.timeout.IdleStateHandler$ReaderIdleTimeoutTask.run(IdleStateHandler.java:494)
    at io.netty.handler.timeout.IdleStateHandler$AbstractIdleTask.run(IdleStateHandler.java:466)
    at io.netty.util.concurrent.PromiseTask$RunnableAdapter.call(PromiseTask.java:38)
    at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:127)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:466)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: io.netty.handler.timeout.ReadTimeoutException: null
19:05:35.989 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 - R:httpbin.org/54.165.51.142:80] CLOSE
19:05:35.989 [reactor-http-nio-8] DEBUG reactor.netty.resources.PooledConnectionProvider - [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80] Channel cleaned, now 0 active connections and 3 inactive connections
19:05:35.989 [reactor-http-nio-8] DEBUG reactor.netty.ReactorNetty - [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: ReadTimeoutHandler, context: ChannelHandlerContext(ReadTimeoutHandler, [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:35.990 [reactor-http-nio-8] DEBUG reactor.netty.ReactorNetty - [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80] Non Removed handler: WriteTimeoutHandler, context: ChannelHandlerContext(WriteTimeoutHandler, [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80]), pipeline: DefaultChannelPipeline{(reactor.left.loggingHandler = io.netty.handler.logging.LoggingHandler), (reactor.left.httpCodec = io.netty.handler.codec.http.HttpClientCodec), (WriteTimeoutHandler = io.netty.handler.timeout.WriteTimeoutHandler), (ReadTimeoutHandler = io.netty.handler.timeout.ReadTimeoutHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
19:05:35.990 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80] INACTIVE
19:05:35.990 [reactor-http-nio-8] DEBUG reactor.netty.http.client.HttpClient - [id: 0xd44acee5, L:/192.168.11.38:61668 ! R:httpbin.org/54.165.51.142:80] UNREGISTERED
io.netty.handler.timeout.ReadTimeoutException重试两次后发生RetryExhaustedException。

(附加)弹簧配置自动配置

在Spring Boot的情况下,ClientHttpConnector如果进行了Bean定义,则会WebClient.Builer自动设置它。

https://github.com/spring-projects/spring-boot/blob/v2.1.1.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/ClientHttpConnectorAutoConfiguration.java

ReactorClientHttpConnector定义

@Bean
public ReactorClientHttpConnector reactorClientHttpConnector() {
    return new ReactorClientHttpConnector(HttpClient.create()
            .tcpConfiguration(tcpClient -> tcpClient
                    .bootstrap(bootstrap -> bootstrap
                            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000))
                    .doOnConnected(connection -> connection
                            .addHandler(new ReadTimeoutHandler(3, TimeUnit.SECONDS))
                            .addHandler(
                                    new WriteTimeoutHandler(3, TimeUnit.SECONDS)))));
}

WebClient你可以使用WebClient.Builder注入使用。

private final WebClient webClient;

public FooService(WebClient.Builder webClientBuilder) {
    this.webClient = webClientBuilder.build();
}

转载自 BLOG.IK.AM

你可能感兴趣的:(Spring,5)