我们有rest api应用程序。我们使用redis进行API响应缓存和内部方法缓存。如果是redis连接,那么它就会使我们的API宕机。如果redis连接失败或任何异常,我们希望绕过redis缓存,而不是关闭我们的API。有一个接口CacheErrorHandler,但它处理redis get set操作失败,而不是redis连接问题。我们使用的是Spring 4.1.2。
发布于 2015-03-27 01:55:46
让我们把这个问题简而言之。您的应用程序使用缓存(使用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 core的缓存抽象中,要确定Cache“未命中”,最重要的是返回的值为空。因此,Spring Caching Infrastructure将继续调用实际的服务方法(即getCustomer)。请记住,在返回getCustomerRepo().load(customerId)调用时,您还需要处理Spring的缓存基础结构现在尝试缓存值的情况。
本着保持简单的精神,我们将不使用AOP,但您也应该能够使用AOP来实现这一点(您的选择)。
所有您(应该)需要的是一个扩展SDR CacheManager implementation的“自定义”RedisCacheManager,类似于...
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不可用,也许您能做的最好的事情就是记录问题,并继续让服务调用发生。显然,这会阻碍性能,但至少会提高人们对存在问题的意识。显然,这可以与更强大的通知系统捆绑在一起,但这是一个粗略的可能性示例。重要的是,您的服务仍然可用,而应用程序服务所依赖的其他服务(例如Redis)可能已经失败。
在这个实现中(与我之前的解释相比),我选择委托给底层的实际RedisCache实现让异常发生,然后完全知道Redis存在问题,这样您就可以适当地处理异常。但是,如果您在检查时确定异常与连接问题有关,您可以返回"null“,让Spring缓存基础设施继续进行,就像缓存”未命中“一样(即,在本例中,糟糕的Redis连接==缓存未命中)。
我知道这样的东西应该可以帮助您解决问题,因为我为GemFire和Pivotal的一个客户构建了一个类似的“自定义”CacheManager实现的原型。在该特定UC中,缓存“未命中”必须由应用程序域对象的“过期版本”触发,在该应用程序域对象中,生产环境中混合了通过Spring的缓存抽象连接到GemFire的新旧应用程序客户端。例如,在较新版本的应用程序中,应用程序域对象字段会发生变化。
无论如何,希望这篇文章对你有所帮助,或者给你更多的想法。
干杯!
发布于 2015-03-31 02:30:02
所以,我今天深入研究了核心的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正确处理缓存"get“错误(并且不重新抛出/an异常),则将返回一个空值,表示缓存”未命中“。
发布于 2017-07-10 22:12:41
谢谢你@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;
}
}
https://stackoverflow.com/questions/29003786
复制相似问题