Saltar al contenido

Identificador de error de Spring Redis

Al fin luego de tanto trabajar ya hallamos el arreglo de este inconveniente que muchos lectores de esta web tienen. Si tienes alguna información que compartir no dudes en aportar tu información.

Solución:

Tuve el mismo problema. Estoy desarrollando algunos servicios de datos contra una base de datos, usando Redis como el almacén de caché a través de las anotaciones de Spring Caching. Si el servidor de Redis deja de estar disponible, quiero que los servicios continúen funcionando como si no estuvieran en caché, en lugar de lanzar excepciones.

Al principio probé un CacheErrorHandler personalizado, un mecanismo proporcionado por Spring. No funcionó del todo, porque solo maneja RuntimeExceptions, y aún permite que cosas como java.net.ConnectException exploten las cosas.

Al final, lo que hice fue extender RedisTemplate, anulando algunos métodos execute () para que registren advertencias en lugar de propagar excepciones. Parece un truco, y es posible que haya anulado muy pocos métodos execute () o demasiados, pero funciona como un encanto en todos mis casos de prueba.

Sin embargo, hay un aspecto operativo importante en este enfoque. Si el servidor Redis deja de estar disponible, debe vaciarlo (limpiar las entradas) antes de que esté disponible nuevamente. De lo contrario, existe la posibilidad de que comience a recuperar entradas de caché que tengan datos incorrectos debido a las actualizaciones que se produjeron mientras tanto.

A continuación se muestra la fuente. Sientase libre de usarlo. Espero que ayude.

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;


/**
 * An extension of RedisTemplate that logs exceptions instead of letting them propagate.
 * If the Redis server is unavailable, cache operations are always a "miss" and data is fetched from the database.
 */
public class LoggingRedisTemplate extends RedisTemplate 

    private static final Logger logger = LoggerFactory.getLogger(LoggingRedisTemplate.class);


    @Override
    public  T execute(final RedisCallback action, final boolean exposeConnection, final boolean pipeline) 
        try 
            return super.execute(action, exposeConnection, pipeline);
        
        catch(final Throwable t) 
            logger.warn("Error executing cache operation: ", t.getMessage());
            return null;
        
    


    @Override
    public  T execute(final RedisScript script, final List keys, final Object... args) 
        try 
            return super.execute(script, keys, args);
        
        catch(final Throwable t) 
            logger.warn("Error executing cache operation: ", t.getMessage());
            return null;
        
    


    @Override
    public  T execute(final RedisScript script, final RedisSerializer argsSerializer, final RedisSerializer resultSerializer, final List keys, final Object... args) 
        try 
            return super.execute(script, argsSerializer, resultSerializer, keys, args);
        
        catch(final Throwable t) 
            logger.warn("Error executing cache operation: ", t.getMessage());
            return null;
        
    


    @Override
    public  T execute(final SessionCallback session) 
        try 
            return super.execute(session);
        
        catch(final Throwable t) 
            logger.warn("Error executing cache operation: ", t.getMessage());
            return null;
        
    

He agregado la respuesta para Spring boot v2 usando LettuceConnectionFactory

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer {

    @Value("$redis.hostname:localhost")
    private String redisHost;

    @Value("$redis.port:6379")
    private int redisPort;

    @Value("$redis.timeout.secs:1")
    private int redisTimeoutInSecs;

    @Value("$redis.socket.timeout.secs:1")
    private int redisSocketTimeoutInSecs;

    @Value("$redis.ttl.hours:1")
    private int redisDataTTL;

    // @Autowired
    // private ObjectMapper objectMapper;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() 
        // LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
        // .commandTimeout(Duration.ofSeconds(redisConnectionTimeoutInSecs)).shutdownTimeout(Duration.ZERO).build();
        //
        // return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort), clientConfig);

        final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(redisSocketTimeoutInSecs)).build();

        final ClientOptions clientOptions = ClientOptions.builder().socketOptions(socketOptions).build();

        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(redisTimeoutInSecs)).clientOptions(clientOptions).build();
        RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(redisHost, redisPort);

        final LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(serverConfig, clientConfig);
        lettuceConnectionFactory.setValidateConnection(true);
        return lettuceConnectionFactory;

    

    @Bean
    public RedisTemplate redisTemplate() 
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    

    @Bean
    public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) 

        /**
         * If we want to use JSON Serialized with own object mapper then use the below config snippet
         */
        // RedisCacheConfiguration redisCacheConfiguration =
        // RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
        // .entryTtl(Duration.ofHours(redisDataTTL)).serializeValuesWith(RedisSerializationContext.SerializationPair
        // .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
                .entryTtl(Duration.ofHours(redisDataTTL))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.java()));

        redisCacheConfiguration.usePrefix();

        RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
                .cacheDefaults(redisCacheConfiguration).build();

        redisCacheManager.setTransactionAware(true);
        return redisCacheManager;
    


    @Override
    public CacheErrorHandler errorHandler() 
        return new RedisCacheErrorHandler();
    

RedisCacheErrorHandler.java se proporciona a continuación

public class RedisCacheErrorHandler implements CacheErrorHandler 

    private static final Logger log = LoggerFactory.getLogger(RedisCacheErrorHandler.class);

    @Override
    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) 
        handleTimeOutException(exception);
        log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage());
    

    @Override
    public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) 
        handleTimeOutException(exception);
        log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage());
    

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) 
        handleTimeOutException(exception);
        log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage());
    

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) 
        handleTimeOutException(exception);
        log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage());
    

    /**
     * We handle redis connection timeout exception , if the exception is handled then it is treated as a cache miss and
     * gets the data from actual storage
     * 
     * @param exception
     */
    private void handleTimeOutException(RuntimeException exception) 

        if (exception instanceof RedisCommandTimeoutException)
            return;
    

Yo tengo el mismo error. Y logré resolverlo agregando dos cosas:

  • tiempo de espera para connectionFactory
  • manejador de errores
@Configuration
@ConditionalOnProperty(name = "redis.enabled", havingValue = "true")
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer 

    @Value("$redis.host")
    private String host;

    @Value("$redis.port")
    private Integer port;

    @Value("$redis.expiration.timeout")
    private Integer expirationTimeout;

    @Bean
    public JedisConnectionFactory redisConnectionFactory() 
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        redisConnectionFactory.setHostName(host);
        redisConnectionFactory.setPort(port);
        redisConnectionFactory.setTimeout(10);
        return redisConnectionFactory;
    

    @Bean
    public RedisTemplate> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) 
        RedisTemplate> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    

    @Bean
    public CacheManager cacheManager(@Autowired RedisTemplate redisTemplate) 
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setDefaultExpiration(expirationTimeout);
        return cacheManager;
    

    @Override
    public CacheErrorHandler errorHandler() 
        return new RedisCacheErrorHandler();
    

    @Slf4j
    public static class RedisCacheErrorHandler implements CacheErrorHandler 

        @Override
        public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) 
            log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage());
        

        @Override
        public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) 
            log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage());
        

        @Override
        public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) 
            log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage());
        

        @Override
        public void handleCacheClearError(RuntimeException exception, Cache cache) 
            log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage());
        
    

Te invitamos a patrocinar nuestro cometido fijando un comentario y puntuándolo te damos la bienvenida.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *