diff --git a/inspect-main/inspect-main-agg/pom.xml b/inspect-main/inspect-main-agg/pom.xml index 3fb006b..483a17c 100644 --- a/inspect-main/inspect-main-agg/pom.xml +++ b/inspect-main/inspect-main-agg/pom.xml @@ -31,6 +31,11 @@ org.springframework.boot spring-boot-starter-web + + + org.apache.httpcomponents + httpclient + com.inspect inspect-base-core diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java index 7575605..37f4fc3 100644 --- a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java +++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/AggProperties.java @@ -93,6 +93,14 @@ public class AggProperties { public static class Http { private int connectTimeoutMs = 10_000; private int readTimeoutMs = 30_000; + /** + * 连接池总连接上限。 + */ + private int poolMaxTotal = 100; + /** 同一站端的连接上限;一次九画面并发起流按 3 路一批,留足余量。 */ + private int poolMaxPerRoute = 40; + /** 从连接池取空闲连接的等待上限(毫秒),避免池满时无限等待。 */ + private int connectionRequestTimeoutMs = 3_000; } @Data diff --git a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java index 6686efe..8113a3a 100644 --- a/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java +++ b/inspect-main/inspect-main-agg/src/main/java/com/inspect/agg/config/RestTemplateConfig.java @@ -1,19 +1,27 @@ package com.inspect.agg.config; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.client.RestTemplate; /** - * HTTP 客户端配置:RestTemplate + 超时 + UTF-8(站点接口返回中文,必须 UTF-8 解码)。 + * HTTP 客户端配置:连接池 + 超时 + UTF-8(站点接口返回中文,必须 UTF-8 解码)。 + * + *

聚合层向站端发起的起流/停流请求在打开多画面时会同时出现十几个,串行新建 TCP + * 连接会明显放大延迟并让部分请求打满页面 8 秒超时;这里改为复用连接的连接池。

*/ @Configuration public class RestTemplateConfig { @@ -27,10 +35,41 @@ public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { + AggProperties.Http http = aggProperties.getHttp(); + + // 连接池:总连接数与单站端连接数都要覆盖“同时在飞的站端请求”。 + PoolingHttpClientConnectionManager connectionManager = + new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(Math.max(1, http.getPoolMaxTotal())); + connectionManager.setDefaultMaxPerRoute(Math.max(1, http.getPoolMaxPerRoute())); + + // 超时与池等待:取连接不能无限等,否则池满时请求会一直挂到页面超时。 + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(http.getConnectTimeoutMs()) + .setSocketTimeout(http.getReadTimeoutMs()) + .setConnectionRequestTimeout(http.getConnectionRequestTimeoutMs()) + .build(); + + CloseableHttpClient httpClient = HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(requestConfig) + // 定期清理失效空闲连接,避免站端重启后一直复用坏连接。 + .evictExpiredConnections() + .evictIdleConnections(30L, TimeUnit.SECONDS) + .build(); + + // 只在这里指定请求工厂;连接与读取超时统一由上面的 RequestConfig 控制, + // 避免与 RestTemplateBuilder 的超时设置叠加产生歧义。 RestTemplate restTemplate = builder - .setConnectTimeout(Duration.ofMillis(aggProperties.getHttp().getConnectTimeoutMs())) - .setReadTimeout(Duration.ofMillis(aggProperties.getHttp().getReadTimeoutMs())) + .requestFactory(() -> { + HttpComponentsClientHttpRequestFactory factory = + new HttpComponentsClientHttpRequestFactory(httpClient); + factory.setConnectTimeout(http.getConnectTimeoutMs()); + factory.setReadTimeout(http.getReadTimeoutMs()); + return factory; + }) .build(); + // 默认 StringHttpMessageConverter 用 ISO-8859-1,会破坏站点返回的中文 List> converters = restTemplate.getMessageConverters(); for (HttpMessageConverter converter : converters) { @@ -41,4 +80,4 @@ public class RestTemplateConfig { } return restTemplate; } -} \ No newline at end of file +} diff --git a/inspect-main/inspect-main-agg/src/main/resources/application.yml b/inspect-main/inspect-main-agg/src/main/resources/application.yml index 934f631..898a62c 100644 --- a/inspect-main/inspect-main-agg/src/main/resources/application.yml +++ b/inspect-main/inspect-main-agg/src/main/resources/application.yml @@ -41,3 +41,16 @@ agg: # 是否在 /agg/proxy 通用代理的 JSON 响应中递归改写图片/文件地址 rewrite-proxy-response: true + # 访问站端接口的 HTTP 客户端;打开多画面时会同时发起多路起流/停流请求。 + http: + # 建立连接超时(毫秒) + connect-timeout-ms: ${AGG_HTTP_CONNECT_TIMEOUT_MS:10000} + # 读取响应超时(毫秒),需大于站端起流最慢耗时 + read-timeout-ms: ${AGG_HTTP_READ_TIMEOUT_MS:30000} + # 连接池总连接上限 + pool-max-total: ${AGG_HTTP_POOL_MAX_TOTAL:100} + # 同一站端的连接上限 + pool-max-per-route: ${AGG_HTTP_POOL_MAX_PER_ROUTE:40} + # 从池中取空闲连接的等待上限(毫秒) + connection-request-timeout-ms: ${AGG_HTTP_CONNECTION_REQUEST_TIMEOUT_MS:3000} + diff --git a/pom.xml b/pom.xml index 4575d65..5f73bbb 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,8 @@ 3.4.0 + UTF-8 + UTF-8 8 8 1.8