【问题标题】:Problems testing spring webflux Webclient with high load高负载测试spring webflux Webclient的问题
【发布时间】:2025-12-27 11:00:16
【问题描述】:

我正在尝试从 C# 和 NetCore 学习 Spring Webflux,我们有一个非常相似的问题,例如 this post,第三方服务提供商有一些响应时间问题。

但是用 spring-webclient 测试会使响应时间加倍,我不知道我是否遗漏了什么

我尝试创建一个类似的示例:

  • 一台运行 3 台服务器的计算机
    • 仅模拟一些随机延迟时间的演示服务器(端口 8080)
    • 使用异步调用我的“等待”服务器(端口 5000)在 C# 中测试服务器
    • 使用 spring 和 webclient 测试服务器以调用我的“等待”服务器(端口 8081)
  • 其他运行 JMeter 的计算机,有 1000 个客户端,每个客户端 10 轮

一些代码

等待服务器

简单的路线

@Configuration
class TestRouter(private val middlemanDemo: MiddlemanDemo) {

    @Bean
    fun route() = router {
        GET("/testWait", middlemanDemo::middleTestAndGetWait)
    }
}

处理程序有一个带有种子的随机生成器,因此每个测试都可以生成相同的延迟序列

@Service
class TestWaiter {

    companion object RandomManager {

        private lateinit var random: Random

        init {
            resetTimer()
        }

        @Synchronized
        fun next(): Long {
            val random = random.nextLong(0, 10)
            return random * 2
        }

        fun resetTimer() {
            random = Random(12345)
        }
    }

    private val logger = LoggerFactory.getLogger(javaClass)

    fun testAndGetWait(request: ServerRequest): Mono<ServerResponse> {
        val wait = next()
        logger.debug("Wait is: {}", wait)
        return ServerResponse
                .ok()
                .json()
                .bodyValue(wait)
                .delayElement(Duration.ofSeconds(wait))
    }

    fun reset(request: ServerRequest): Mono<ServerResponse> {
        logger.info("Random reset")
        resetTimer()
        return ServerResponse
                .ok()
                .build()
    }
}

使用 JMeter 对服务器进行负载测试,我可以看到稳定的响应时间约为 9-10 秒,最大吞吐量为 100/秒:

C# 异步演示服务器

用 C# 尝试中间人,这个服务器只是调用主演示服务器:

控制器

[HttpGet]
public async Task<string> Get()
{
    return await _waiterClient.GetWait();
}

以及带有 httpClient 的服务

    private readonly HttpClient _client;

    public WaiterClient(HttpClient client)
    {
        _client = client;
        client.BaseAddress = new Uri("http://192.168.0.121:8080");
    }

    public async Task<string> GetWait()
    {
        var response = await _client.GetAsync("/testWait");
        var waitTime = await response.Content.ReadAsStringAsync();
        return waitTime;
    }
}

测试此服务给出相同的响应时间,但开销的吞吐量略低,但这是可以理解的

spring-webclient 实现

这个客户端也很简单,只有一个路由

@Configuration
class TestRouter(private val middlemanDemo: MiddlemanDemo) {

    @Bean
    fun route() = router {
        GET("/testWait", middlemanDemo::middleTestAndGetWait)
    }
}

处理程序只是使用 webclient 调用服务

@Service
class MiddlemanDemo {

    private val client = WebClient.create("http://127.0.0.1:8080")

    fun middleTestAndGetWait(request: ServerRequest): Mono<ServerResponse> {
        return client
                .get()
                .uri("/testWait")
                .retrieve()
                .bodyToMono(Int::class.java)
                .flatMap(::processResponse)
    }

    fun processResponse(delay: Int): Mono<ServerResponse> {
        return ServerResponse
                .ok()
                .bodyValue(delay)
    }
}

但是,运行测试,吞吐量只能达到 50/秒

如果我再等待一次,响应时间会加倍,直到负载再次下降

【问题讨论】:

    标签: kotlin spring-webflux spring-webclient


    【解决方案1】:

    我标记 KL.Lee 答案是因为它指出了我正确的方式,但我会添加完整的解决方案供任何人查找:

    关键是根据我的需要创建一个连接池。 JK.Lee 提到的默认值为 500。

    @Service
    class MiddlemanDemo(webClientBuilder: WebClient.Builder) {
    
        private val client: WebClient
    
        init {
            val provider = ConnectionProvider.builder("fixed")
                    .maxConnections(2000) // This is the important part
                    .build()
            val httpClient = HttpClient
                    .create(provider)
            client = webClientBuilder
                    .clientConnector(ReactorClientHttpConnector(httpClient))
                    .baseUrl("http://localhost:8080")
                    .build()
        }
    
        fun middleTestAndGetWait(request: ServerRequest): Mono<ServerResponse> {
    
            return client
                    .get()
                    .uri("/testWait")
                    .retrieve()
                    .bodyToMono(Int::class.java)
                    .flatMap(::processResponse)
        }
    
        fun processResponse(delay: Int): Mono<ServerResponse> {
            return ServerResponse
                    .ok()
                    .bodyValue(delay)
        }
    }
    

    【讨论】:

      【解决方案2】:

      我认为这可能是由池获取时间引起的。

      我假设您的服务器获得超过 1k TPS 并且每个请求看起来需要大约 9 秒。但是默认的HTTP客户端连接池是500,请参考Projector Reactor - Connection Pool

      请检查日志是否有PoolAcquireTimeoutException 或者您的服务器是否需要一些时间来等待池获取。

      【讨论】: