WebClient 的连接池默认多大?请求排队等连接会怎样?默认有没有超时?retry 要不要加 filter?POST 能不能重试?空响应体怎么处理?这篇用一个会计数的本地服务端,在 Spring Boot 4.0.8 的 WebClient 上逐个测量。
HttpResources.get()(WebClient 默认用的共享资源)的 maxConnections() = 500
ConnectionProvider.builder("t").build().maxConnections() = 20
默认连接池:
700 个并发请求(服务端各睡 1500 ms,每 2 ms 发起一个):成功 700,失败 {},总用时 3852 ms
服务端收到 700 个请求、484 个连接
自己建连接池 maxConnections=5(等待连接的超时用默认值):
20 个并发请求(服务端各睡 300 ms,每 0 ms 发起一个):成功 20,失败 {},总用时 1387 ms
服务端收到 20 个请求、5 个连接
自己建连接池 maxConnections=5,pendingAcquireTimeout=200ms:
20 个并发请求(服务端各睡 300 ms,每 0 ms 发起一个):成功 5,失败 {WebClientRequestException ← PoolAcquireTimeoutException=15},总用时 443 ms
服务端收到 5 个请求、5 个连接默认共享池 500 个连接;自建不写就是 20;等连接可以设超时。
不配置任何超时: WebClient 读到 slow-done,用时 2149 ms Mono.timeout(500ms) 异常 ReactiveException ← TimeoutException,用时 512 ms HttpClient.responseTimeout(500ms) 异常 WebClientRequestException ← ReadTimeoutException,用时 520 ms spring.http.clients.read-timeout=500ms: WebClient 异常 WebClientRequestException ← ReadTimeoutException,用时 627 ms spring.http.clients.connect-timeout=300ms: 连 10.255.255.1:81:异常 WebClientRequestException ← ConnectTimeoutException,用时 400 ms
默认没有响应超时,有三种设置方式。
Retry.backoff(3, 100ms) 对 /flaky(前 2 次返回 503)→ 'flaky-ok',用时 553 ms
对 404 也 retry(没有 filter):异常 RetryExhaustedException ← NotFound,message=Retries exhausted: 3/3,用时 384 ms
retry 加 filter(只重试 5xx),对 404:异常 NotFound,用时 3 ms
onRetryExhaustedThrow 还原原异常:ServiceUnavailable
Retry.fixedDelay(3, 200ms) → 'flaky-ok',用时 422 ms
服务端统计各路径被调用次数:{'/flaky': 6, '/flaky?a': 3, '/s/404': 5, '/s/503': 3, '/flaky?b': 3}加 filter 只重试该重试的错误;耗尽后的异常可以还原。
POST /post-slow(服务端先写入再睡 1 秒),timeout(300ms).retry(2):最终 ReactiveException ← TimeoutException,用时 961 ms
服务端 /post-slow 被调用(并执行写入)3 次客户端超时不代表服务端没执行,重试 POST 要幂等。
retrieve() /s/200 → 'body-of-200' retrieve() /s/404 → 抛 NotFound,body='body-of-404' retrieve() /s/500 → 抛 InternalServerError,body='body-of-500' exchangeToMono /s/404 → 不抛,'404 body-of-404' onStatus 自定义 → IllegalStateException 204 → bodyToMono 为空,defaultIfEmpty 得到 (空)
retrieve() 抛异常,exchangeToMono 自己处理,空响应体用 defaultIfEmpty。