[英]Unable to configure “Keep Alive” in Camel HTTP component
我正在為HTTP組件的正確設置遇到一些麻煩。 目前,微服務從提供商處提取JSON內容,處理它並將其發送到下一個服務以進行進一步處理。 主要問題是這個微服務創建了大量的CLOSE_WAIT套接字連接。 我知道“KEEP-ALIVE”的整個概念應保持連接打開,直到我關閉它,但服務器可能會因某些原因丟棄連接並創建此CLOSE_WAIT套接字。
我已經創建了一個用於調試/測試目的的小型服務,它可以向Google發送GET調用,但即使這個連接保持打開狀態,直到我關閉程序。 我嘗試了很多不同的解決方案:
但我沒有成功。 所以也許你們中的一個可以幫助我:
謝謝大家的幫助
您可以將自己的clientConnectionManager
提供給HTTP4。 通常,您應該使用org.apache.http.impl.conn.PoolingHttpClientConnectionManager
的實例,您可以使用自己的org.apache.http.config.SocketConfig
將其配置為連接管理器的setDefaultSocketConfig
方法。
如果您使用Spring with Java config,那么您將擁有一個方法:
@Bean
PoolingHttpClientConnectionManager connectionManager() {
SocketConfig socketConfig = SocketConfig.custom()
.setSoKeepAlive(false)
.setSoReuseAddress(true)
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setDefaultSocketConfig(socketConfig);
return connectionManager;
}
然后你只需在端點定義中使用它,如下所示: clientConnectionManager=#connectionManager
如果空閑連接在配置的時間內空閑,則可以通過關閉空閑連接來完成。 您可以通過為Camel Http Component配置空閑連接超時來實現相同目的。 Camel Http提供了這樣做的界面。
將org.apache.camel.component.http4.HttpComponent轉換為PoolingHttpClientConnectionManager
PoolingHttpClientConnectionManager poolingClientConnectionManager = (PoolingHttpClientConnectionManager) httpComponent
.getClientConnectionManager();
poolingClientConnectionManager.closeIdleConnections(5000, TimeUnit.MILLISECONDS);
不幸的是,在應用程序最終關閉之前,所提出的答案都沒有解決我身邊的CLOSE_WAIT
連接狀態。
我用以下測試用例重現了這個問題:
public class HttpInvokationTest extends CamelSpringTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@EndpointInject(uri = "mock:success")
private MockEndpoint successEndpoint;
@EndpointInject(uri = "mock:failure")
private MockEndpoint failureEndpoint;
@Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext(ContextConfig.class);
}
@Configuration
@Import(HttpClientSpringTestConfig.class)
public static class ContextConfig extends CamelConfiguration {
@Override
public List<RouteBuilder> routes() {
List<RouteBuilder> routes = new ArrayList<>(1);
routes.add(new RouteBuilder() {
@Override
public void configure() {
from("direct:start")
.log(LoggingLevel.INFO, LOG, CONFIDENTIAL, "Invoking external URL: ${header[ERPEL_URL]}")
.setHeader("Connection", constant("close"))
.recipientList(header("TEST_URL"))
.log(LoggingLevel.DEBUG, "HTTP response code: ${header["+Exchange.HTTP_RESPONSE_CODE+"]}")
.bean(CopyBodyToHeaders.class)
.choice()
.when(header(Exchange.HTTP_RESPONSE_CODE).isGreaterThanOrEqualTo(300))
.to("mock:failure")
.otherwise()
.to("mock:success");
}
});
return routes;
}
}
@Test
public void testHttpInvocation() throws Exception {
successEndpoint.expectedMessageCount(1);
failureEndpoint.expectedMessageCount(0);
ProducerTemplate template = context.createProducerTemplate();
template.sendBodyAndHeader("direct:start", null, "TEST_URL", "http4://meta.stackoverflow.com");
successEndpoint.assertIsSatisfied();
failureEndpoint.assertIsSatisfied();
Exchange exchange = successEndpoint.getExchanges().get(0);
Map<String, Object> headers = exchange.getIn().getHeaders();
String body = exchange.getIn().getBody(String.class);
for (String key : headers.keySet()) {
LOG.info("Header: {} -> {}", key, headers.get(key));
}
LOG.info("Body: {}", body);
Thread.sleep(120000);
}
}
並發布netstat -ab -p tcp | grep 151.101.129.69
netstat -ab -p tcp | grep 151.101.129.69
請求,其中IP是meta.stackoverflow.com
。
這給出了如下答復:
tcp4 0 0 192.168.0.10.52183 151.101.129.69.https ESTABLISHED 37562 2118
tcp4 0 0 192.168.0.10.52182 151.101.129.69.http ESTABLISHED 885 523
在調用之后跟隨
tcp4 0 0 192.168.0.10.52183 151.101.129.69.https CLOSE_WAIT 37562 2118
tcp4 0 0 192.168.0.10.52182 151.101.129.69.http CLOSE_WAIT 885 523
應用程序關閉之前的響應由於Connection: keep-alive
標頭,即使配置如下所示:
@Configuration
@EnableConfigurationProperties(HttpClientSettings.class)
public class HttpClientSpringTestConfig {
private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Resource
private HttpClientSettings httpClientSettings;
@Resource
private CamelContext camelContext;
private SocketConfig httpClientSocketConfig() {
/*
socket timeout:
Monitors the time passed between two consecutive incoming messages over the connection and
raises a SocketTimeoutException if no message was received within the given timeout interval
*/
LOG.info("Creating a SocketConfig with a socket timeout of {} seconds", httpClientSettings.getSoTimeout());
return SocketConfig.custom()
.setSoTimeout(httpClientSettings.getSoTimeout() * 1000)
.setSoKeepAlive(false)
.setSoReuseAddress(false)
.build();
}
private RequestConfig httpClientRequestConfig() {
/*
connection timeout:
The time span the application will wait for a connection to get established. If the connection
is not established within the given amount of time a ConnectionTimeoutException will be raised.
*/
LOG.info("Creating a RequestConfig with a socket timeout of {} seconds and a connection timeout of {} seconds",
httpClientSettings.getSoTimeout(), httpClientSettings.getConTimeout());
return RequestConfig.custom()
.setConnectTimeout(httpClientSettings.getConTimeout() * 1000)
.setSocketTimeout(httpClientSettings.getSoTimeout() * 1000)
.build();
}
@Bean(name = "httpClientConfigurer")
public HttpClientConfigurer httpConfiguration() {
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
return 5 * 1000;
}
};
PoolingHttpClientConnectionManager conMgr =
new PoolingHttpClientConnectionManager();
conMgr.closeIdleConnections(5, TimeUnit.SECONDS);
return builder -> builder.setDefaultSocketConfig(httpClientSocketConfig())
.setDefaultRequestConfig(httpClientRequestConfig())
.setConnectionTimeToLive(5, TimeUnit.SECONDS)
.setKeepAliveStrategy(myStrategy)
.setConnectionManager(conMgr);
}
@PostConstruct
public void init() {
LOG.debug("Initializing HTTP clients");
HttpComponent httpComponent = camelContext.getComponent("http4", HttpComponent.class);
httpComponent.setHttpClientConfigurer(httpConfiguration());
HttpComponent httpsComponent = camelContext.getComponent("https4", HttpComponent.class);
httpsComponent.setHttpClientConfigurer(httpConfiguration());
}
}
或直接在相應的HttpComponent上定義設置。
在檢查HttpClient代碼中各自提出的方法時,很明顯這些方法是單次操作,而不是HttpClient內部每隔幾毫秒檢查一次的配置。
PoolingHttpClientConnectionManager
進一步說明:
版本4.4中更改了過時連接的處理。 以前,代碼會在重新使用之前默認檢查每個連接。 現在,代碼僅檢查連接,如果自上次使用連接以來經過的時間超過已設置的超時。 默認超時設置為2000毫秒
只有在重新使用連接時才會發生這種情況,這對連接池有意義,特別是如果通過同一連接交換多個消息。 對於單次調用,它應該更像是一個Connection: close
可能在一段時間內沒有重用該連接,使連接保持打開或半關閉,因為沒有進一步嘗試從該連接讀取並因此識別本身可以關閉連接。
我注意到我已經用傳統的HttpClients解決了這個問題,並開始將這個解決方案移植到Camel,這很容易解決。
該解決方案基本上包括使用服務注冊HttpClients,然后定期(在我的情況下為5秒)調用closeExpiredConnections()
和closeIdleConnections(...)
。
這個邏輯保存在單例枚舉中,因為它實際上位於一個應用程序使用的庫中,每個應用程序都在自己的JVM中運行。
/**
* This singleton monitor will check every few seconds for idle and stale connections and perform
* a cleanup on the connections using the registered connection managers.
*/
public enum IdleConnectionMonitor {
INSTANCE;
private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/** The execution service which runs the cleanup every 5 seconds **/
private ScheduledExecutorService executorService =
Executors.newScheduledThreadPool(1, new NamingThreadFactory());
/** The actual thread which performs the monitoring **/
private IdleConnectionMonitorThread monitorThread = new IdleConnectionMonitorThread();
IdleConnectionMonitor() {
// execute the thread every 5 seconds till the application is shutdown (or the shutdown method
// is invoked)
executorService.scheduleAtFixedRate(monitorThread, 5, 5, TimeUnit.SECONDS);
}
/**
* Registers a {@link HttpClientConnectionManager} to monitor for stale connections
*/
public void registerConnectionManager(HttpClientConnectionManager connMgr) {
monitorThread.registerConnectionManager(connMgr);
}
/**
* Request to stop the monitoring for stale HTTP connections.
*/
public void shutdown() {
executorService.shutdown();
try {
if (!executorService.awaitTermination(3, TimeUnit.SECONDS)) {
LOG.warn("Connection monitor shutdown not finished after 3 seconds!");
}
} catch (InterruptedException iEx) {
LOG.warn("Execution service was interrupted while waiting for graceful shutdown");
}
}
/**
* Upon invocation, the list of registered connection managers will be iterated through and if a
* referenced object is still reachable {@link HttpClientConnectionManager#closeExpiredConnections()}
* and {@link HttpClientConnectionManager#closeIdleConnections(long, TimeUnit)} will be invoked
* in order to cleanup stale connections.
* <p/>
* This runnable implementation holds a weakly referable list of {@link
* HttpClientConnectionManager} objects. If a connection manager is only reachable by {@link
* WeakReference}s or {@link PhantomReference}s it gets eligible for garbage collection and thus
* may return null values. If this is the case, the connection manager will be removed from the
* internal list of registered connection managers to monitor.
*/
private static class IdleConnectionMonitorThread implements Runnable {
// we store only weak-references to connection managers in the list, as the lifetime of the
// thread may extend the lifespan of a connection manager and thus allowing the garbage
// collector to collect unused objects as soon as possible
private List<WeakReference<HttpClientConnectionManager>> registeredConnectionManagers =
Collections.synchronizedList(new ArrayList<>());
@Override
public void run() {
LOG.trace("Executing connection cleanup");
Iterator<WeakReference<HttpClientConnectionManager>> conMgrs =
registeredConnectionManagers.iterator();
while (conMgrs.hasNext()) {
WeakReference<HttpClientConnectionManager> weakConMgr = conMgrs.next();
HttpClientConnectionManager conMgr = weakConMgr.get();
if (conMgr != null) {
LOG.trace("Found connection manager: {}", conMgr);
conMgr.closeExpiredConnections();
conMgr.closeIdleConnections(30, TimeUnit.SECONDS);
} else {
conMgrs.remove();
}
}
}
void registerConnectionManager(HttpClientConnectionManager connMgr) {
registeredConnectionManagers.add(new WeakReference<>(connMgr));
}
}
private static class NamingThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Connection Manager Monitor");
return t;
}
}
}
如上所述,這個單例服務產生一個自己的線程,每隔5秒調用上面提到的兩個方法。 這些調用負責關閉在一段時間內未使用的連接或在規定的時間內閑置的連接。
為了使用這個服務,可以使用EventNotifierSupport
,以便讓Camel在關閉后關閉監視器線程。
/**
* This Camel service with take care of the lifecycle management of {@link IdleConnectionMonitor}
* and invoke {@link IdleConnectionMonitor#shutdown()} once Camel is closing down in order to stop
* listening for stale connetions.
*/
public class IdleConnectionMonitorService extends EventNotifierSupport {
private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private IdleConnectionMonitor connectionMonitor;
@Override
public void notify(EventObject event) {
if (event instanceof CamelContextStartedEvent) {
LOG.info("Start listening for closable HTTP connections");
connectionMonitor = IdleConnectionMonitor.INSTANCE;
} else if (event instanceof CamelContextStoppingEvent){
LOG.info("Shutting down listener for open HTTP connections");
connectionMonitor.shutdown();
}
}
@Override
public boolean isEnabled(EventObject event) {
return event instanceof CamelContextStartedEvent || event instanceof CamelContextStoppingEvent;
}
public IdleConnectionMonitor getConnectionMonitor() {
return this.connectionMonitor;
}
}
為了利用該服務,HttpClient Camel使用的內部連接管理器需要在服務中注冊,這在下面的代碼塊中完成:
private void registerHttpClientConnectionManager(HttpClientConnectionManager conMgr) {
if (!getIdleConnectionMonitorService().isPresent()) {
// register the service with Camel so that on a shutdown the monitoring thread will be stopped
camelContext.getManagementStrategy().addEventNotifier(new IdleConnectionMonitorService());
}
IdleConnectionMonitor.INSTANCE.registerConnectionManager(conMgr);
}
private Optional<IdleConnectionMonitorService> getIdleConnectionMonitorService() {
for (EventNotifier eventNotifier : camelContext.getManagementStrategy().getEventNotifiers()) {
if (eventNotifier instanceof IdleConnectionMonitorService) {
return Optional.of((IdleConnectionMonitorService) eventNotifier);
}
}
return Optional.empty();
}
最后但並非最不重要的是,在我的情況下, HttpClientSpringTestConfig
中的httpConfiguration
定義的連接管理器需要轉到引入的寄存器函數
PoolingHttpClientConnectionManager conMgr = new PoolingHttpClientConnectionManager();
registerHttpClientConnectionManager(conMgr);
這可能不是最漂亮的解決方案,但它會關閉我機器上的半封閉連接。
@編輯
我剛剛了解到你可以使用NoConnectionReuseStrategy
將連接狀態更改為TIME_WAIT
而不是CLOSE_WAIT
,因此會在短時間后刪除連接。 不幸的是,該請求仍然發出了一個Connection: keep-alive
頭。 此策略將為每個請求創建一個新連接,即如果您有301 Moved Permanently
重定向響應,則重定向將在新連接上發生。
httpClientConfigurer
bean需要更改為以下內容才能使用上述策略:
@Bean(name = "httpClientConfigurer")
public HttpClientConfigurer httpConfiguration() {
return builder -> builder.setDefaultSocketConfig(socketConfig)
.setDefaultRequestConfig(requestConfig)
.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE);
}
首先是羅曼沃特納 ,你的回答以及你找到問題的純粹奉獻精神幫助了我一輛卡車。 我一直在與CLOSE_WAIT斗爭2天,你的回答是有幫助的。 這就是我做的。 在我的CamelConfiguration類中添加了以下代碼,該類在啟動時基本上篡改了CamelContext。
HttpComponent http4 = camelContext.getComponent("https4", HttpComponent.class);
http4.setHttpClientConfigurer(new HttpClientConfigurer() {
@Override
public void configureHttpClient(HttpClientBuilder builder) {
builder.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE);
}
});
工作就像一個魅力。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.