如果redis连接失败,如何在运行时禁用Redis缓存

问题描述 投票:11回答:5

我们有休息api申请。我们使用redis进行API响应缓存和内部方法缓存。如果是redis连接,那么它会使我们的API失效。我们想绕过redis缓存,如果redis连接失败或任何异常,而不是让我们的API失效。有一个接口CacheErrorHandler,但它处理redis get set操作失败而不是redis连接问题。我们使用的是Spring 4.1.2。

spring-mvc redis spring-cache
5个回答
10
投票

让我们稍微熬一下吧。您的应用程序使用缓存(使用Redis实现)。如果Redis连接是陈旧/关闭或其他,那么您希望应用程序绕过缓存并且(可能)直接转到底层数据存储(例如RDBMS)。应用程序服务逻辑可能看起来类似于......

@Service
class CustomerService ... {

    @Autowired
    private CustomerRepository customerRepo;

    protected CustomerRepository getCustomerRepo() {
        Assert.notNull(customerRepo, "The CustomerRepository was not initialized!");
        return customerRepo;
    }

    @Cacheable(value = "Customers")
    public Customer getCustomer(Long customerId) {
        return getCustomerRepo().load(customerId);
    }
    ...
}

Spring核心的缓存抽象中确定缓存“未命中”的所有重要因素是返回的值为null。因此,Spring Caching Infrastructure将继续调用实际的Service方法(即getCustomer)。请记住,在返回getCustomerRepo()。load(customerId)调用时,您还需要处理Spring的缓存基础结构现在尝试缓存该值的情况。

本着保持简单的精神,我们将不使用AOP,但您也应该能够使用AOP(您的选择)实现这一目标。

您(应该)需要的是一个“自定义”RedisCacheManager扩展SDR CacheManager implementation,类似于......

package example;

import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
...

class MyCustomRedisCacheManager extends RedisCacheManager {

    public MyCustomerRedisCacheManager(RedisTemplate redisTemplate) {
        super(redisTemplate);
    }

    @Override
    public Cache getCache(String name) {
        return new RedisCacheWrapper(super.getCache(name));
    }


    protected static class RedisCacheWrapper implements Cache {

        private final Cache delegate;

        public RedisCacheWrapper(Cache redisCache) {
            Assert.notNull(redisCache, "'delegate' must not be null");
            this.delegate = redisCache;
        }

        @Override
        public Cache.ValueWrapper get(Object key) {
            try {
              delegate.get(key);
            }
            catch (Exception e) {
                return handleErrors(e);
            }
        }

        @Override
        public void put(Object key, Object value) {
            try {
                delegate.put(key, value);
            }
            catch (Exception e) {
                handleErrors(e);
            }
        }

        // implement clear(), evict(key), get(key, type), getName(), getNativeCache(), putIfAbsent(key, value) accordingly (delegating to the delegate).

        protected <T> T handleErrors(Exception e) throws Exception {
            if (e instanceof <some RedisConnection Exception type>) {
                // log the connection problem
                return null;
            }
            else if (<something different>) { // act appropriately }
            ...
            else {
                throw e;
            }
        }
    }
}

因此,如果Redis不可用,您可以做的最好的事情就是记录问题并继续让Service调用发生。显然,这会妨碍性能,但至少会提高人们对问题的认识。显然,这可以绑定到一个更强大的通知系统,但它是可能性的一个粗略的例子。重要的是,您的服务仍然可用,而应用程序服务所依赖的其他服务(例如Redis)可能已失败。

在这个实现中(与我之前的解释相比),我选择委托给底层的,实际的RedisCache实现让异常发生,然后完全了解Redis存在的问题,以便你可以适当地处理异常。但是,如果您确定Exception与检查时的连接问题有关,则可以返回“null”以使Spring Caching Infrastructure继续运行,就好像它是Cache“miss”(即Redis Connection = = Cache miss,在这种情况下)。

我知道这样的事情可以帮助你解决问题,因为我为GemFire和Pivotal的客户之一构建了一个类似的“自定义”CacheManager实现原型。在那个特定的UC中,Cache“miss”必须由应用程序域对象的“过时版本”触发,其中生产中混合了通过Spring的缓存抽象连接到GemFire的新旧应用程序客户端。例如,应用程序域对象字段将在应用程序的较新版本中更改。

无论如何,希望这有助于或给你更多的想法。

干杯!


4
投票

谢谢@John Blum。我在Spring Boot的解决方案如下。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;

import java.util.concurrent.Callable;

class CustomRedisCacheManager extends RedisCacheManager {
    private static Logger logger = LoggerFactory.getLogger(CustomRedisCacheManager.class);

    public CustomRedisCacheManager(RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    public Cache getCache(String name) {
        return new RedisCacheWrapper(super.getCache(name));
    }


    protected static class RedisCacheWrapper implements Cache {

        private final Cache delegate;

        public RedisCacheWrapper(Cache redisCache) {
            Assert.notNull(redisCache, "delegate cache must not be null");
            this.delegate = redisCache;
        }

        @Override
        public String getName() {
            try {
                return delegate.getName();
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public Object getNativeCache() {
            try {
                return delegate.getNativeCache();
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public Cache.ValueWrapper get(Object key) {
            try {
                return delegate.get(key);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public <T> T get(Object o, Class<T> aClass) {
            try {
                return delegate.get(o, aClass);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public <T> T get(Object o, Callable<T> callable) {
            try {
                return delegate.get(o, callable);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public void put(Object key, Object value) {
            try {
                delegate.put(key, value);
            } catch (Exception e) {
                handleException(e);
            }
        }

        @Override
        public ValueWrapper putIfAbsent(Object o, Object o1) {
            try {
                return delegate.putIfAbsent(o, o1);
            } catch (Exception e) {
                return handleException(e);
            }
        }

        @Override
        public void evict(Object o) {
            try {
                delegate.evict(o);
            } catch (Exception e) {
                handleException(e);
            }
        }

        @Override
        public void clear() {
            try {
                delegate.clear();
            } catch (Exception e) {
                handleException(e);
            }
        }

        private <T> T handleException(Exception e) {
            logger.error("handleException", e);
            return null;
        }
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
public class RedisConfig {
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        CustomRedisCacheManager redisCacheManager = new CustomRedisCacheManager(redisTemplate);
        redisCacheManager.setUsePrefix(true);
        return redisCacheManager;
    }
}

3
投票

因此,我正在挖掘核心的Spring Framework缓存抽象源,解决了另一个问题,看起来如果正确实现了CacheErrorHandler,那么可能有问题的Redis连接仍然可能导致所需的行为,例如:缓存“未命中”(触发返回空值)。

有关更多详细信息,请参阅AbstractCacheInvoker源代码。

由于Redis连接错误,cache.get(key)应该导致异常,因此将调用异常处理程序...

catch (RuntimeException e) {
    getErrorHandler().handleCacheGetError(e, cache, key);
    return null; // If the exception is handled, return a cache miss
}

如果CacheErrorHandler正确处理Cache“get”错误(并且不重新抛出/ an Exception),则将返回空值,指示缓存“未命中”。


1
投票

实际上我的回复是针对@Vivek Aditya先生的 - 我遇到了同样的问题:新的spring-data-redis api并没有为RedisTemplate构建RedisCacheManager。唯一的选择 - 基于@John Blum的建议 - 是使用方面。以下是我的代码。

@Aspect
@Component
public class FailoverRedisCacheAspect {

    private static class FailoverRedisCache extends RedisCache {

        protected FailoverRedisCache(RedisCache redisCache) {
            super(redisCache.getName(), redisCache.getNativeCache(), redisCache.getCacheConfiguration());
        }

        @Override
        public <T> T get(Object key, Callable<T> valueLoader) {
            try {
                return super.get(key, valueLoader);
            } catch (RuntimeException ex) {
                return valueFromLoader(key, valueLoader);
            }
        }

        private <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
            try {
                return valueLoader.call();
            } catch (Exception e) {
                throw new ValueRetrievalException(key, valueLoader, e);
            }
        }
    }

    @Around("execution(* org.springframework.cache.support.AbstractCacheManager.getCache (..))")
    public Cache beforeSampleCreation(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            Cache cache = (Cache) proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            if (cache instanceof RedisCache) {
                return new FailoverRedisCache((RedisCache) cache);
            } else {
                return cache;
            }
        } catch (Throwable ex) {
            return null;
        }
    }
}

适用于所有合理的场景:

  • 应用程序启动正常与redis下来
  • 应用程序(仍然)在(突然)redis中断期间工作
  • 当redis再次开始工作时,app会看到它

编辑:代码更像是一个poc - 仅用于“获取”,我不喜欢在每次缓存命中时重新实例化FailoverRedisCache - 应该有一个地图。


0
投票

所有核心的Spring Framework Cache abstraction annotations(例如@Cacheable)以及JSR-107 JCache annotations supported by the core SF委托给底层的CacheManager,而对于Redis,那就是RedisCacheManager

你会configure the RedisCacheManager in Spring XML configuration meta-data similar to here

一种方法是为(Redis)CacheManager编写一个AOP代理,它使用RedisConnection(间接来自RedisTemplate)来确定每个(Redis)CacheManger操作的连接状态。

如果连接失败或关闭,对于标准高速缓存操作,(Redis)CacheManager可以为RedisCache返回getCache(String name)的实例,该实例始终返回null(表示条目上的Cache未命中),从而传递到基础数据存储。

可能有更好的方法来解决这个问题,因为我不是所有Redis(或SDR)的专家,但这应该有用,也许会给你一些自己的想法。

干杯。

© www.soinside.com 2019 - 2024. All rights reserved.