Spring Handle with Redis Error

I am using Spring + Redis as a cache component in a new project. Spring config configuration file:

<!-- Jedis Connection --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.ip}" p:port="${redis.port}" p:use-pool="${redis.use-pool}" /> <!-- Redis Template --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> </bean> <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:template-ref="redisTemplate"/> <cache:annotation-driven mode="proxy" proxy-target-class="true" cache-manager="cacheManager" /> 

Using

  @Cacheable(value = "cacheManager", key="#userId") public User getUser(String userId) { System.out.println("execute=="); return userAdminMapper.getUser(userId); } 

My test case:

 @Test public void testCacheUser2() { String id = "test"; User user = userService.getUser(id); System.out.println(user); user.setUserCreateDate(new Date()); userService.updateUser(user); User user2 = userService.getUser(id); System.out.println(user2); User user3 = userService.getUser(id); System.out.println(user3); } 

If the Redis server is running, the code works correctly. But my question is, if I turn off the Redis server, it will throw an exception:

 org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57) at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152) at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:87) at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:297) at org.springframework.cache.interceptor.CacheAspectSupport.findInAnyCaches(CacheAspectSupport.java:287) at org.springframework.cache.interceptor.CacheAspectSupport.collectPutRequests(CacheAspectSupport.java:266) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:199) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:178) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:60) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644) at sg.infolab.common.admin.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$c7f982a7.getUser(<generated>) at sg.infolab.admin.test.RedisServiceTest.testCacheUser2(RedisServiceTest.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect at redis.clients.jedis.Connection.connect(Connection.java:150) at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:71) at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1783) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:137) ... 50 more Caused by: java.net.ConnectException: Connection refused: connect at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351) at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) at java.net.Socket.connect(Socket.java:529) at redis.clients.jedis.Connection.connect(Connection.java:144) ... 53 more 

I want to ask if the client can connect to Redis Server, why will it throw an exception? Can we configure such a scenario - if the cache server (Redis Server) cannot connect (perhaps it crashed or the network does not work), it should connect directly to the database and extract data.

+7
source share
4 answers

I had the same problem. I am developing some database data services using Redis as a cache repository through Spring Caching annotations. If the Redis server becomes unavailable, I want the services to continue to run as non-cached, rather than throwing exceptions.

First I tried a custom CacheErrorHandler, a mechanism provided by Spring. This didn't quite work, because it only handles RuntimeExceptions and still allows things like java.net.ConnectException to explode things.

In the end, I made a RedisTemplate extension, overriding several execute () methods to write warnings instead of throwing exceptions. It looks like a hack, and I could override too few execute () methods or too many, but in all my test cases it works like a charm.

However, there is an important operational aspect to this approach. If the Redis server becomes unavailable, you must clear it (clear records) before making it available again. Otherwise, it is likely that you may start retrieving cache entries with invalid data due to updates that have occurred during this time.

Below is the source. Feel free to use it. I hope this helps.

 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<K, V> extends RedisTemplate<K, V> { private static final Logger logger = LoggerFactory.getLogger(LoggingRedisTemplate.class); @Override public <T> T execute(final RedisCallback<T> 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> T execute(final RedisScript<T> script, final List<K> 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> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer, final RedisSerializer<T> resultSerializer, final List<K> 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> T execute(final SessionCallback<T> session) { try { return super.execute(session); } catch(final Throwable t) { logger.warn("Error executing cache operation: {}", t.getMessage()); return null; } } } 
+18
source

I have the same error. And I managed to solve this problem by adding two things:

  • timeout for connectionFactory
  • error handler
 @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<String, Set<String>> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Set<String>> 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()); } } } 
+2
source

I added an answer for Spring boot v2 using 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<Object, Object> redisTemplate() { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>(); 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 is below

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

LettuceConnectionFactory is not required. Just using custom CacheConfig extends CachingConfigurerSupport. And override the errorHandler () method.

You just need to implement a custom CacheErrorHandler, as @Tan does in your answer.

0
source

Source: https://habr.com/ru/post/1203315/


All Articles