Configuring Spring WebFlux WebClient to use a custom thread pool
Asked Answered
P

1

7

Is it possible to configure WebClient to use a custom thread pool other than the reactor-http-nio thread pool (When using Netty)? If it is possible , can we somehow restrict that custom thread pool to run only on a particular processor core?

Provoke answered 26/6, 2019 at 3:34 Comment(5)
Could you please clarify your motivation?Almagest
@Almagest hmm ok, if we use Netty and webClient in a webFLux application they share the resources according to the spring documentation. We would like to use a different thread pool for webClient (instead of sharing Netty's reactor-http-nio thread pool). Can this be done? If so, we would also like to allocate one processor core just for using webClient hoping that caching of that processor core will become optimized for solely webClient's requirements. Hope that make sense!Provoke
TBH it doesn't make a lot of sense. In fact, in may make things even worse. If you need to run blocking operations after you receive a response from WebClient, you should use publishOn operator. But moving WebClient off reactor-http-nio pool will only increase the amount of context switching. Reactor's and Reactor Netty's team is doing a wonderful job already optimizing things and abstracting away the threads.Almagest
@Almagest I agree with what you say! But seems like when still if we need we can do that by configuring a ReactorResourceFactory bean (setGlobalResources=false) and then use it to create a ClientHttpConnector object and then use that created ClientHttpConnector object with WebClientBuilder to create a customized webClient which uses a thread pool different from Netty. Not sure though! If possible your input on this (does it really create a new thread pool) is highly appreciated!Provoke
@Almagest references which gave the above insight, docs.spring.io/spring-boot/docs/current/reference/html/… , docs.spring.io/spring/docs/5.1.8.RELEASE/…Provoke
R
11

Yes. You can.

  1. Create some where your own Thread Pool and EventLoopGroup (or create NioEventLoopGroup bean). For example:

    {
     Intger THREADS = 10;
    
     BasicThreadFactory THREADFACTORY = new BasicThreadFactory.Builder()
            .namingPattern("HttpThread-%d")
            .daemon(true)
            .priority(Thread.MAX_PRIORITY)
            .build();
    
     EXECUTOR = new ThreadPoolExecutor(
            THREADS,
            THREADS,
            0L,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(),
            THREADFACTORY,
            new ThreadPoolExecutor.AbortPolicy());
    
     NioEventLoopGroup RESOURCE= new NioEventLoopGroup(THREADS,EXECUTOR);
    }
    
  2. Register your own ReactorResourceFactory. And provide your own EventLoopGrooup based on custom thread Executor

    @Bean
    public ReactorResourceFactory reactorResourceFactory(NioEventLoopGroup RESOURCE) {
        ReactorResourceFactory f= new ReactorResourceFactory();
        f.setLoopResources(new LoopResources() {
            @Override
             public EventLoopGroup onServer(boolean b) {
                 return RESOURCE;
                }
            });
        f.setUseGlobalResources(false);
        return f;
    }
    
  3. Then register ReactorClientHttpConnector. In example below it is used custom SSL Context

    @Bean
    public ReactorClientHttpConnector reactorClientHttpConnector(ReactorResourceFactory r) throws SSLException {
        SslContext sslContext = SslContextBuilder
            .forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE)
            .build();
        return new ReactorClientHttpConnector(r, m -> m.secure(t -> t.sslContext(sslContext)));
    }
    
  4. Finally build WebClient

    @Bean
    public WebClient webClient(ReactorClientHttpConnector r) {
        return WebClient.builder().clientConnector(r).build();
    }
    

If you want to use same for WebServer. Do same configuration for ReactiveWebServerFactory.

    @Bean
    public ReactiveWebServerFactory reactiveWebServerFactory(NioEventLoopGroup RESOURCE) {
        NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
        factory.addServerCustomizers(hs->hs.tcpConfiguration(s->s.runOn(RESOURCE)));
        return factory;
    }

Imports:

    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.handler.ssl.SslContext;
    import io.netty.handler.ssl.SslContextBuilder;
    import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.http.client.reactive.ReactorClientHttpConnector;
    import org.springframework.http.client.reactive.ReactorResourceFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.netty.resources.LoopResources;
    import org.apache.commons.lang3.concurrent.BasicThreadFactory;
    import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
    import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
    import java.util.concurrent.*;
Ranchman answered 23/1, 2020 at 11:49 Comment(1)
Little late ;) but thanks anyway! Tried it in the similar fashion a while ago!Provoke

© 2022 - 2024 — McMap. All rights reserved.