一文打尽Spring Cloud线程和超时配置

本文基于Spring Boot 2.6.6和Spring Cloud 2021.0.1

一、Spring Cloud Gateway

1.1 client部分

gateway向其他服务发起请求所用的配置,使用的是reactor.netty.http.client.HttpClient。

相关配置都在org.springframework.cloud.gateway.config.HttpClientProperties类中

# 连接超时时间(毫秒)
spring.cloud.gateway.httpclient.connect-timeout=45000
# 请求响应等待时间
spring.cloud.gateway.httpclient.response-timeout=null

# 线程池类型,除了默认的elastic,还支持fixed和disabled
# elastic:无限扩展的线程池
# fixed:固定数量线程池
# disabled:不使用线程池——只有一个线程
spring.cloud.gateway.httpclient.pool.type=elastic
# 最大线程数,只在类型为fixed时有效
spring.cloud.gateway.httpclient.pool.max-connections=CPU核数*2(最小为16)
# 从线程池获取连接的最大等待时间(毫秒)
spring.cloud.gateway.httpclient.pool.acquire-timeout=45000
# 线程空闲多久会被回收(毫秒)
spring.cloud.gateway.httpclient.pool.max-idle-time=null
# 线程最大存活时间(毫秒)
spring.cloud.gateway.httpclient.pool.max-life-time=null
# 定时检测周期(毫秒),检测哪些线程需要被回收
# (1)只有空闲的线程会被回收
# (2)即使未达到max-idle-time,当达到max-life-time时也会被回收
spring.cloud.gateway.httpclient.pool.eviction-interval=null

1.2 server部分

gateway接收请求的部分(请求一般来自于前端),使用的是reactor.netty.http.server.HttpServer。

对应配置org.springframework.boot.autoconfigure.web.ServerProperties#Netty

# 连接超时时间(毫秒)
server.netty.connection-timeout=30000
# 连接等待时间(毫秒),超时会被自动关闭。为空表示永远不关闭,全靠请求方
server.netty.idle-timeout=null

注意,spring boot没有把netty-web-server的线程参数配置开放出来,个人猜测是因为netty调高线程数其实并没有什么作用,如果需要修改的话可以通过以下两种方式:

(1)自行声明ReactorResourceFactory

@Bean
public ReactorResourceFactory reactorServerResourceFactory() {
    ReactorResourceFactory reactorResourceFactory = new ReactorResourceFactory();
    reactorResourceFactory.setUseGlobalResources(false);
    // 设置event loop线程池
    reactorResourceFactory.setLoopResources(LoopResources.create("my-http1", 16, true));
    // 设置连接池
    reactorResourceFactory.setConnectionProvider(ConnectionProvider.create("my-http2", 16));
    return reactorResourceFactory;
}

(2)通过设置系统参数或者环境变量的方式

详见reactor.netty.resources.ConnectionProvider和reactor.netty.resources.LoopResources类

# 设置最大连接数,最小为16
-Dreactor.netty.pool.maxConnections=xxx
# 设置IO线程数,最小为4
-Dreactor.netty.ioWorkerCount=xxx
# 设置selector线程数,最小为4
-Dreactor.netty.ioSelectCount=xxx

二、Spring WebFlux

spring cloud gateway server部分本质上就是一个webflux应用,所以相关配置可以直接参考gateway的。

当然有个不同的地方就是WebFlux是可以使用tomcat的,不过既然选择了使用WebFlux的编程模型,一般都会使用netty或者undertow这些,这里就不扩展了。

三、Spring MVC(Tomcat)

# 最大线程数
server.tomcat.threads.max=200
# 最小线程数
# tomcat没有暴露idletime的配置,默认就是60s
# 详情查看org.apache.tomcat.util.net.AbstractEndpoint#createExecutor方法
server.tomcat.threads.min-spare=10
# 对应JDK nio-server的backlog参数,可以简单认为是等待队列的大小
server.tomcat.accept-count=100
# 最大连接数,因为是NIO模型,所以这个值远远大于线程数
server.tomcat.max-connections=8192
# org.apache.coyote.Processor的缓存大小,被缓存的processor会复用之前的request和response对象
server.tomcat.processor-cache=200
# 对应socket的SO_TIMEOUT(毫秒),其实就是连接等待时间
server.tomcat.connection-timeout=20000
# 指的是HTTP中的keep-alive功能(毫秒),在一定时间内http请求会复用tcp连接
server.tomcat.keep-alive-timeout=20000
# 允许keep-alive的最大请求数
server.tomcat.max-keep-alive-requests=200

四、Spring Cloud LoadBalancer

Spring Cloud在2020版本的时候废弃了Netflix-ribbon,改为了自研的Spring Cloud LoadBalancer。

LoadBalancer本身只负责多个服务之间负载均衡的处理,具体的请求是通过对接各种第三方客户端实现的,比如Apache HttpComponents、Okhttp、Reactor Netty等等,具体的配置跟使用LoadBalancer的模块有关(参考下边几个小节)。

五、Feign With LoadBalancer

底层基于Apache HttpComponents 4.x(可以自行切换成其他HTTP框架)。

相关配置见org.springframework.cloud.openfeign.support.FeignHttpClientProperties和org.springframework.cloud.openfeign.FeignClientProperties类

# 建立连接的超时时间(毫秒)
feign.httpclient.connection-timeout=2000
# 定时检测任务周期(毫秒),用来定期清理连接池中过期的连接
feign.httpclient.connection-timer-repeat=3000
# 连接池最大数量
feign.httpclient.max-connections=200
# 单个url的最大连接数量
feign.httpclient.max-connections-per-route=50
# 连接的存活时间(秒)
feign.httpclient.time-to-live=900

# 全局连接超时时间(毫秒)
feign.client.config.default.connect-timeout=10000
# 全局读写超时时间(毫秒)
feign.client.config.default.read-timeout=60000

六、RestTemplate With LoadBalancer

底层基于Apache HttpComponents 4.x(可以自行切换成其他HTTP框架)。

spring自带的org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration配置类是无法对各种超时时间进行配置的,可以通过以下方法修改。

注意:虽然创建CloseableHttpClient 的时候有一个连接超时时间,但是在配置Feign和RestTemplate的时候这个时间会被覆盖掉

@Configuration(proxyBeanMethods = false)
static class RestTemplateConfiguration {

    private final Timer connectionManagerTimer = new Timer("HttpClientConfiguration.connectionManagerTimer", true);

    private CloseableHttpClient httpClient;

    @Autowired(required = false)
    private RegistryBuilder registryBuilder;

    @Bean
    public CloseableHttpClient httpClient(FeignHttpClientProperties httpClientConfig,
                                          ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                                          ApacheHttpClientFactory httpClientFactory) {

        HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
                httpClientConfig.isDisableSslValidation(), httpClientConfig.getMaxConnections(),
                httpClientConfig.getMaxConnectionsPerRoute(), httpClientConfig.getTimeToLive(),
                httpClientConfig.getTimeToLiveUnit(), registryBuilder
        );
        // 取代HttpComponents自带的过期检查,因为配置不够灵活,详见IdleConnectionEvictor
        this.connectionManagerTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                connectionManager.closeExpiredConnections();
            }
        }, 30000, httpClientConfig.getConnectionTimerRepeat());

        RequestConfig defaultRequestConfig = RequestConfig.custom()
                .setConnectTimeout(httpClientConfig.getConnectionTimeout())
                .setRedirectsEnabled(httpClientConfig.isFollowRedirects())
                .build();
        this.httpClient = httpClientFactory.createBuilder()
                .setDefaultRequestConfig(defaultRequestConfig)
                .setConnectionManager(connectionManager)
                .build();
        return this.httpClient;
    }

    @PreDestroy
    public void destroy() {
        this.connectionManagerTimer.cancel();
        if (this.httpClient != null) {
            try {
                this.httpClient.close();
            } catch (IOException e) {
                log.error("Could not correctly close httpClient.");
            }
        }
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        requestFactory.setConnectTimeout(5 * 1000);
        requestFactory.setConnectionRequestTimeout(60 * 1000);
        requestFactory.setReadTimeout(30 * 60 * 1000);
        return requestFactory;
    }

    @Bean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer,
                                                   ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplateBuilder builder = new RestTemplateBuilder();
        builder = builder.requestFactory(() -> clientHttpRequestFactory);
        return restTemplateBuilderConfigurer.configure(builder);
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

七、WebClient With LoadBalancer

参考gateway的server部分自行声明ReactorResourceFactory,不过要保证你的声明早于org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorConfiguration类

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章