Solución a la inestabilidad en llamadas con Feign y configuración de tiempos de espera en Spring Cloud

En aplicaciones Spring Cloud que utilizan Feign para realizar llamadas a otros microservicios, es frecuente experimentar inestabilidad, donde las peticiones fallan de manera intermitente. Un eror típico que puede observarse es una excepción de socket relacionada con la conexión:

java.net.SocketException: Software caused connection abort: recv failed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    ... (otros marcos de pila)

Este fallo suele deberse a limitaciones en la configuración del cliente HTTP subyacente, como la falta de un pool de conexiones adecuado o tiempos de espera no optimizados.

Para mitigar la inestabilidad, se puede sobrescribir la configuración predeterminada de HttpClient en Feign. A continuación, se muestra una clase de configuración que define un pool de conexiones con parámetros ajustados:

import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

@Component
public class HttpClientConfig {

    private static final int MAX_TOTAL_CONNECTIONS = 3000;
    private static final int MAX_CONNECTIONS_PER_ROUTE = 500;
    private static final int INACTIVITY_CHECK_MS = 1000;

    @Bean(name = "httpClient", destroyMethod = "close")
    public CloseableHttpClient createHttpClient() throws KeyManagementException {
        return initializeHttpClient();
    }

    private CloseableHttpClient initializeHttpClient() throws KeyManagementException {
        SSLContext context = SSLContexts.createDefault();
        context.init(null, new TrustManager[]{new UniversalTrustManager()}, null);
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);

        Registry<connectionsocketfactory> registry = RegistryBuilder.<connectionsocketfactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", socketFactory)
                .build();

        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
        manager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
        manager.setValidateAfterInactivity(INACTIVITY_CHECK_MS);

        return HttpClients.custom()
                .setConnectionManager(manager)
                .setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE)
                .build();
    }

    private static class UniversalTrustManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }
}</connectionsocketfactory></connectionsocketfactory>

Esta configuración incrementa el número máximo de conexiones, establece un límite por ruta y habilita la verificación de inactividad para mantener el pool saludable.

Feign integra Ribbon y Hystrix, lo que puede causar timeouts rápidos si no se configuran adecuadamente. Para evitar errores de tiempo de espera, es neecsario ajustar varios parámetros.

Primero, configura los tiempos de conexión y lectura para el cliente Feign. Reemplaza nombreDelServicio con el nombre del microsrevicio objetivo:

feign.client.config.nombreDelServicio.connectTimeout=60000
feign.client.config.nombreDelServicio.readTimeout=60000

Desactivar Hystrix puede ser útil si se prefiere un manejo de errores más directo, aunque se pierden las capacidades de circuit breaker:

feign.hystrix.enabled=false

Además, si Hystrix está habilitado, su tiempo de timeout debe ser mayor o igual al de Feign; de lo contrario, las configuraciones de Feign no tendrán efecto:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

Para configuraciones más granulares, se pueden establecer tiempos de espera específicos mediante Ribbon. Ejemplo global:

ribbon.ConnectTimeout=1000
ribbon.ReadTimeout=1000

O por servicio, con reintentos habilitados:

nombreDelServicio.ribbon.ConnectTimeout=10000
nombreDelServicio.ribbon.ReadTimeout=10000
nombreDelServicio.ribbon.OkToRetryOnAllOperations=true
nombreDelServicio.ribbon.MaxAutoRetriesNextServer=2
nombreDelServicio.ribbon.MaxAutoRetries=1

Estos ajustes permiten mayor resiliencia en las llamadas, manejando fallos transitorios mediante reintentos y tiempos de espera extendidos.

Etiquetas: SpringCloud Feign Ribbon Hystrix ApacheHttpClient

Publicado el 6-10 08:07