How to disable redis caching at runtime if a redis connection fails

We have an api application for relaxation. We use redis to cache API responses and internal method caching. If the redis connection, this will reduce our API. We want to bypass redis caching if this redis connection fails or some exception occurs, instead of lowering our API. There is a CacheErrorHandler interface, but it handles redis get set errors, not problems with reconnection. We are using Spring 4.1.2.

+11
spring-cache spring-mvc redis
source share
6 answers

Let it boil a little. Your application uses caching (implemented using Redis). If the Redis connection is out of date / closed or otherwise, you want the application to bypass caching and (presumably) go directly to the underlying data store (e.g. RDBMS). The logic of a Service application might look just like ...

@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); } ... } 

All that matters in the Spring Caching Abstraction kernel to determine a cache miss is that the return value is null. Thus, the Spring Caching Infrastructure will act when the actual method of the service is called (i.e. GetCustomer). Keep in mind that when you return the call getCustomerRepo (). Load (customerId) You also need to handle the case where Spring Caching infrastructure is now trying to cache the value.

In the spirit of maintaining simplicity, we can do without AOP, but you can also achieve this using AOP (of your choice).

All you need (should) is a “customizable” RedisCacheManager that extends the SDR CacheManager implementation , something like ...

 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; } } } } 

So, if Redis is unavailable, perhaps the best thing you can do is to register the problem and go to the service call. Obviously, this will hinder performance, but at least it will increase awareness of the existence of the problem. Obviously, this may be due to a more reliable notification system, but this is a rough example of the possibilities. The important thing is that your Service remains available, while other services (such as Redis), on which the application service depends, may have failed.

In this implementation (compared to my previous explanation), I decided to delegate the base, actual implementation of RedisCache to throw an Exception, and then I know well that the problem with Redis exists and that you can handle the Exception correctly, However, if you are sure that the exception is due to the connection problem during verification, you can return "null" so that the Spring Caching Infrastructure continues as if it were a "Error" error (i.e., skip the bad Redis Connection == Cache, in this case).

I know that something like this should help your problem, because I built a similar prototype of the “custom” implementation of CacheManager for GemFire ​​and one of the Pivotal clients. In this particular UC, the “skip” cache should have been called by the “outdated version” of the application domain object, where in production there was a combination of older and older applications connecting to GemFire ​​via Spring Caching Abstraction. For example, the fields of the application domain objects will change in new versions of the application.

Anyway, hope this helps or gives you more ideas.

Hooray!

+10
source share

So, I was digging the Spring Framework Caching Abstraction source kernel today, addressing another question, and it seems that if the CacheErrorHandler is implemented correctly, perhaps a problematic Redis Connection may lead to the desired behavior, for example. cache "miss" (triggered with zero return).

See AbstractCacheInvoker for more details.

cache.get(key) should throw an exception due to a Redis Connection malfunction and therefore the Exception handler will be called ...

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

If the CacheErrorHandler correctly handles the "get" cache error (and does not throw / throw), then a null value is returned, indicating a cache miss.

+4
source share

Thanks @ John Blum. My solution in Spring Boot as follows.

 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; } } 
+4
source share

actually my answer is directed to Mr. @Vivek Aditya - I faced the same problem: the new Spring-data-Redis API, and not creating the RedisCacheManager for RedisTemplate. The only option - based on @John Blum's suggestions - is to use aspects. And below is my code.

 @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; } } } 

works great for all reasonable scenarios:

  • the application starts normally with Redis Down
  • the application (still) runs during a (sudden) shutdown of Redis
  • when Redis starts working again, the application sees this

Edit: the code is more like poc - only for "get", and I don’t like re-creating FailoverRedisCache every time I hit the cache - there should be a map.

+1
source share

All major Spring Framework Annotations cache abstractions (for example, @Cacheable), along with JSR-107 JCache annotations supported by the SF kernel , delegate the underlying CacheManager under the hood, and for Redis, i.e. RedisCacheManager .

You would configure RedisCacheManager in the Spring XML configuration metadata similar to here .

One approach would be to write an AOP proxy for (Redis) CacheManager, which uses RedisConnection (indirectly from RedisTemplate ) to determine the connection status for each (Redis) CacheManger operation.

If the connection is unsuccessful or is closed for standard caching operations, (Redis) CacheManager can return a RedisCache instance for getCache (line name) , which always returns null (indicating missing cache), thus passing through the underlying data store.

There may be better ways to handle this, as I'm not an expert on all things Redis (or SDR), but this should work and maybe give you some of your own.

Greetings.

0
source share

I had the same problem, but, unfortunately, none of the above solutions work for me. I checked for a problem and found that the executed command never expired if there was no connection to Redis. So I'm starting to study the salad library for a solution. I solve the problem by rejecting the command when there is no connection:

 @Bean public LettuceConnectionFactory lettuceConnectionFactory() { final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).build(); ClientOptions clientOptions = ClientOptions.builder() .socketOptions(socketOptions) .autoReconnect(true) .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) .build(); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(10)) .clientOptions(clientOptions).build(); RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(this.host, this.port); return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig); } 
0
source share

All Articles