У нас возникли проблемы с тайм-аутом промежуточного соединения. Я использую Java 21 и Spring Boot версии 3.1.6. Приложение внутренне вызывает API Amadeus для сбора данных. Мое приложение действует как оболочка для этих API. Мы используем более 35 API, поэтому иногда сталкиваемся с таймаутами соединения. Мы интегрировали нагрузочные тесты с API Amadeus, которые стабильно и без каких-либо проблем возвращают правильные ответы. Подобные приложения также сталкиваются с той же проблемой. Все эти приложения используют Java 21 и вместо Amadeus API используют и другие сервисы.
Кто-нибудь имеет представление об этой проблеме?
Для справки я добавил к этому вопросу конфигурацию RestTemplate. Мне было бы очень полезно найти причину этой проблемы.
@Bean(name = "restTemplate")
@Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return configureRestTemplate(builder);
}
private RestTemplate configureRestTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new LoggingRequestInterceptor());
restTemplate.setInterceptors(interceptors);
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
Код для стороны вызова API выглядит следующим образом:
@Autowired
private RestTemplate restTemplate;
. . .
private <T, U> U exchangeData(URI uri, T data, Class<U> clazz, HttpHeaders requestHeaders, HttpMethod method,
boolean... errorCustomization) {
HttpEntity<T> requestEntity = new HttpEntity<>(data, requestHeaders);
ResponseEntity<U> response = null;
try {
response = restTemplate.exchange(uri, method, requestEntity, clazz);
} catch (RestClientException e) {
List<ErrorModel> errorDetails = new ArrayList<>();
errorDetails.add(new ErrorModel(e.getClass().getName(), e.getMessage()));
LOG.error("Error while calling external service", e);
throw applicationErrorConfig.createExternalException(ApiExternalErrorType.SERVER_INTERNAL_ERROR,
errorDetails);
}
restErrorManagerUtil.handleExternalServerErrorForComposite(data, response.getBody(), errorCustomization);
return response.getBody();
}
Добавление трассировки стека также
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://xxxx**********************": Connect to https://xxxx********************** [domain/ip] failed: Operation timed out
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:888)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:868)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:646)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
at org.springframework.web.client.RestTemplate$$SpringCGLIB$$0.exchange(<generated>)
at com.ai.loyalty.common.utils.ExtRestInvocationUtil.exchangeData(ExtRestInvocationUtil.java:115)
at com.ai.loyalty.common.utils.ExtRestInvocationUtil.getData(ExtRestInvocationUtil.java:107)
at com.ai.loyalty.common.clients.impl.LoyaltyClientImpl.getAccountSummary(LoyaltyClientImpl.java:135)
at com.ai.loyalty.services.impl.LoyaltyMembershipServiceImpl.getAccountSummary(LoyaltyMembershipServiceImpl.java:104)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:699)
at com.ai.loyalty.services.impl.LoyaltyMembershipServiceImpl$$SpringCGLIB$$0.getAccountSummary(<generated>)
at com.ai.loyalty.controllers.LoyaltyMembershipController.getAccountSummaryV3(LoyaltyMembershipController.java:411)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at com.ai.loyalty.common.application.filters.JsonContentTypeValidationFilter.doFilterInternal(JsonContentTypeValidationFilter.java:44)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at com.ai.loyalty.config.CorsFilter.doFilter(CorsFilter.java:26)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at com.ai.loyalty.common.application.filters.LoggingFilter.doFilter(LoggingFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:117)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:145)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.servlet.v6_0.OpenTelemetryHandlerMappingFilter.doFilter(OpenTelemetryHandlerMappingFilter.java:69)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:735)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.apache.hc.client5.http.HttpHostConnectException: Connect to https://xxxx********************** [domain/ip] failed: Operation timed out
at java.base/sun.nio.ch.Net.pollConnect(Native Method)
at java.base/sun.nio.ch.Net.pollConnectNow(Unknown Source)
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(Unknown Source)
at java.base/sun.nio.ch.NioSocketImpl.connect(Unknown Source)
at java.base/java.net.SocksSocketImpl.connect(Unknown Source)
at java.base/java.net.Socket.connect(Unknown Source)
at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.lambda$connectSocket$0(SSLConnectionSocketFactory.java:281)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:280)
at org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:240)
at org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:189)
at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:450)
at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.connectEndpoint(InternalExecRuntime.java:162)
at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.connectEndpoint(InternalExecRuntime.java:172)
at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:142)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:113)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:116)
at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:93)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:862)
... 125 more
Привет @manuel! Также добавлены трассировки стека, пожалуйста, проверьте
Какая версия Java у вас была раньше? < 13?
Java 11 идентифицирует мою старую версию
Вы уверены, что «Amadeus API» не вызывает таймаутов? Вы пытались перейти на Java 17 только в целях тестирования? Должно быть выполнимо, поскольку 21 и 17 тоже не отличаются, если вы не полагаетесь на 21 функцию.




Похоже, что ваше приложение не может подключиться к приложению, на котором работает Amadeus, поскольку вы получаете ResourceAccessException (а не HttpClient/ServerException).
У меня есть две дикие догадки:
jdk.tls.client.enableSessionTicketExtension=false.Я попробовал вышеописанное, но проблема все еще возникла
Чтобы решить эту проблему, я реализовал механизм повтора, используя RestTemplate, чтобы корректно обрабатывать эти периодические проблемы.
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
public class RetryRestTemplateExample {
private static final int MAX_RETRIES = 3;
private static final long BACKOFF_INTERVAL = 1000; // 1 second
private RestTemplate restTemplate = new RestTemplate();
public String fetchDataWithRetries(String url) {
int retries = 0;
while (retries < MAX_RETRIES) {
try {
String result = restTemplate.getForObject(url, String.class);
return result;
} catch (HttpServerErrorException | ResourceAccessException e) {
// Handle specific exceptions that indicate transient issues
System.out.println("Attempt #" + (retries + 1) + " failed: " + e.getMessage());
retries++;
if (retries < MAX_RETRIES) {
System.out.println("Retrying in " + BACKOFF_INTERVAL + " milliseconds...");
try {
Thread.sleep(BACKOFF_INTERVAL);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
return null; // or throw exception, depending on your use case
}
public static void main(String[] args) {
RetryRestTemplateExample example = new RetryRestTemplateExample();
String url = "http://example.com/api/data";
String data = example.fetchDataWithRetries(url);
if (data != null) {
System.out.println("Data fetched successfully: " + data);
} else {
System.out.println("Failed to fetch data after retries.");
}
}
}
Объяснение: В этом примере я создал класс RetryRestTemplateExample, который инкапсулирует логику повтора в методе fetchDataWithRetries. Метод пытается получить данные по указанному URL-адресу с помощью RestTemplate. Если он обнаруживает исключение HttpServerErrorException или ResourceAccessException, он повторяет попытку до MAX_RETRIES раз с паузой BACKOFF_INTERVAL между попытками. Это помогает корректно решать временные проблемы, такие как таймауты или сбои сети.
Вывод: если вы сталкиваетесь с периодическими проблемами с RestTemplate, реализация такого механизма повторных попыток может быть полезным подходом для повышения надежности. Настройте значения MAX_RETRIES и BACKOFF_INTERVAL в зависимости от вашего конкретного варианта использования и условий сети.
Не стесняйтесь попробовать этот подход и адаптировать его по мере необходимости к требованиям вашего приложения.
Делитесь следами клиентских и серверных приложений. О каких именно таймаутах вы говорите?