7

I have a spring boot application with the following properties:

spring.cache.type: redis
spring.redis.host: <hostname>
spring.redis.port: <hostport>

Right now if the remote host fails the application also fails with a connection error. As in this case my cache is not core to my application, but it is used only for performance, I'd like for spring to simply bypass it and go to the database retrieving its data.

I saw that this could be attained by defining a custom errorHandler method, but in order to do so I have to implement the CachingConfigurer bean...but this also forces me to override every method (for example cache manager, cache resolver, ecc.).

@Configuration
public class CacheConfiguration implements CachingConfigurer{

@Override
public CacheManager cacheManager() {
    // TODO Auto-generated method stub
    return null;
}

@Override
public CacheResolver cacheResolver() {
    // TODO Auto-generated method stub
    return null;
}
...
@Override
public CacheErrorHandler errorHandler() {
    // the only method I need, maybe
    return null;
}

I would like to avoid that...I simply need a way to tell spring "the cache crashed but it's ok: just pretend you have no cache at all"

Marko Previsic
  • 1,820
  • 16
  • 30
Phate
  • 6,066
  • 15
  • 73
  • 138

3 Answers3

4

@Phate - Absolutely! I just answered a related question (possibly) using Apache Geode or Pivotal GemFire as the caching provider in a Spring Boot application with Spring's Cache Abstraction.

In that posting, rather than disabling the cache completely, I switched GemFire/Geode to run in a local-only mode (a possible configuration with GemFire/Geode). However, the same techniques can be applied to disable caching entirely if that is what is desired.

In essence, you need a pre-processing step, before Spring Boot and Spring in general start to evaluate the configuration of your application.

In my example, I implemented a custom Spring Condition that checked the availability of the cluster (i.e. servers). I then applied the Condition to my @Configuration class.

In the case of Spring Boot, Spring Boot applies auto-configuration for Redis (as a store and a caching provider) when it effectively sees (as well as see here) Redis and Spring Data Redis on the classpath of your application. So, essentially, Redis is only enabled as a caching provider when the "conditions" are true, primarily that a RedisConnectionFactory bean was declared by your application configuration, your responsibility.

So, what would this look like?

Like my Apache Geode & Pivotal GemFire custom Spring Condition, you could implement a similar Condition for Redis, such as:

static RedisAvailableCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, 
            AnnotatedTypeMetadata annotatedTypeMetadata) {

        // Check the available of the Redis server, such as by opening a Socket
        // connection to the server node.
        // NOTE: There might be other, more reliable/robust means of checking 
        // the availability of a Redis server in the Redis community.
        Socket redisServer;

        try {

            Environment environment = conditionContext.getEnvironment();

            String host = environment.getProperty("spring.redis.host");
            Integer port = environment.getProperty("spring.redis.port", Integer.class);

            SocketAddress redisServerAddress = new InetSocketAddress(host, port);

            redisServer = new Socket();
            redisServer.connect(redisServerAddress);

            return true;
        }
        catch (Throwable ignore) {

            System.setProperty("spring.cache.type", "none");

            return false;
        }
        finally {
            // TODO: You need to implement this method yourself.
            safeCloseSocket(redisServer);
        }
    }
}

Additionally, I also set the spring.cache.type to NONE, to ensure that caching is rendered as a no-op in the case that Redis is not available. NONE is explained in more detail here.

Of course, you could also use a fallback caching option, using some other caching provider (like a simple ConcurrentHashMap, but I leave that as an exercise for you). Onward...

Then, in your Spring Boot application configuration class where you have defined your RedisConnectionFactory bean (as expected by Spring Boot's auto-configuration), you add this custom Condition using Spring's @Conditiional annotation, like so:

@Confgiuration
@Conditional(RedisAvailableCondition.class);
class MyRedisConfiguration {

    @Bean
    RedisConnectionFactory redisConnectionFactory() {
        // Construct and return new RedisConnectionFactory
    }
}

This should effectively handle the case when Redis is not available.

DISCLAIMER: I did not test this myself, but is based on my Apache Geode/Pivotal GemFire example that does work. So, perhaps, with some tweaks this will address your needs. It should also serve to give you some ideas.

Hope this helps!

Cheers!

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • will this only work at boot? I.e. it will disable caching at boot time but not runtime? – GabeV Nov 20 '19 at 12:58
  • Could you please elaborate on how to implement a fallback option above like ConcurrentHashMap in catch block. – marios390 Jul 01 '20 at 20:11
  • Regarding whether this will only work at startup... yes, that is how the above code is currently configured. However, it is possible to disable caching at runtime, too. For that, you would need to effectively make the `CacheManager` implementation a bit more intelligent. Using the Decorator Software Design Pattern would be appropriate here. Essentially, the `CacheManager` and returned `Cache` implementation for the "cache" enabled method would need to treat an unavailable caching provider as a cache miss. – John Blum Jul 01 '20 at 21:55
  • @marios390 - The fallback logic does not necessarily need to be in the `catch` block. Again, you could use the Decorator, or even the Composite Software Design Pattern to use a default, fallback option for caching when the primary caching provider is not online. Spring Caching Demarcation is actually made possible by AOP. Therefore, you could include another Aspect in the chain of Interceptors to fallback when the primary caching provider is not online. The secondary caching provider options would necessarily be later in the Interceptor chain... – John Blum Jul 01 '20 at 21:58
  • ... after the primary caching provider has had a chance to attempts its work. – John Blum Jul 01 '20 at 21:59
3

We can use any of Circuit breaker implementation to use database as fallback option in case of any cache failure. The advantage of going with circuit breaker pattern is that once your cache is up, the request will be automatically routed back to your cache, so the switch happens seamlessly.

Also you configure how many times you want to retry before falling back to database and how frequently you want to check if your cache is back up online.

Spring cloud provides out of the box support for hystrix and Resilience4j circuit breaker implementation and its easy to integrate with spring boot applications.

https://spring.io/projects/spring-cloud-circuitbreaker
https://resilience4j.readme.io/docs/circuitbreaker

Kumar V
  • 1,570
  • 1
  • 12
  • 19
0

I have to implement the CachingConfigurer bean...but this also forces me to override every method (for example cache manager, cache resolver, ecc.)

Instead of this, you can simply extend CachingConfigurerSupport and only override the errorHandler() method, returning a custom CacheErrorHandler whose method implementations are no-ops. See https://stackoverflow.com/a/68072419/1527469

awgtek
  • 1,483
  • 16
  • 28