【问题标题】:Spring Redis Error HandleSpring Redis 错误处理
【发布时间】:2014-11-19 05:56:02
【问题描述】:

我在新项目中使用 Spring + Redis 作为我的缓存组件。 spring config xml文件为:

<!-- 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" />

用法是

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

我的测试用例是:

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

如果 Redis 服务器正在运行,则代码运行正常。但我的问题是,如果我关闭 Redis 服务器,它会抛出异常:

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

我想问一下,如果客户端连接不上Redis Server,为什么会抛出异常?我们可以这样配置场景吗-如果缓存层(Redis Server)无法连接(可能是崩溃或网络不上),它应该直接连接到数据库并获取数据。

【问题讨论】:

    标签: error-handling redis spring-data jedis


    【解决方案1】:

    我遇到了同样的问题。我正在开发一些针对数据库的数据服务,通过 Spring Caching 注释使用 Redis 作为缓存存储。如果 Redis 服务器不可用,我希望服务像未缓存一样继续运行,而不是抛出异常。

    一开始我尝试了一个自定义的CacheErrorHandler,这是Spring提供的一种机制。它不太好用,因为它只处理 RuntimeExceptions,而且仍然让 java.net.ConnectException 之类的东西搞砸了。

    最后我所做的是扩展 RedisTemplate,覆盖一些 execute() 方法,以便它们记录警告而不是传播异常。这似乎有点 hack,我可能覆盖了太少或太多的 execute() 方法,但它在我所有的测试用例中都像一个魅力。

    不过,这种方法有一个重要的操作方面。如果 Redis 服务器变得不可用,您必须在使其再次可用之前刷新它(清除条目)。否则,您可能会因为同时发生的更新而开始检索具有不正确数据的缓存条目。

    以下是来源。随意使用它。希望对你有帮助。

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

    【讨论】:

    • 这可能是一个很好的特性,可以引入 spring-data。有没有人开过票或 PR 与团队讨论这个问题?
    • 谢谢你,@KevinCrowell。我可能会把它作为一个自己的项目放在那里,因为回想起来,它可能需要改进。上面的代码假定 Redis 完全关闭,并且没有持久化,因此当它可用时将是空的。如果 Redis 只是暂时无法访问,或者如果 Redis 出现故障但缓存仍然存在,则对数据库的更新仍然会进行,并且缓存条目可能是陈旧的。也许它应该“记住”在 Redis 不可用时收到的缓存键,并在 Redis 可用时将它们驱逐。
    【解决方案2】:

    我已经使用 LettuceConnectionFactory 添加了 Spring boot v2 的答案

    @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

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

    【讨论】:

    • 我遇到了 redis 连接超时问题,大约需要 10-20 秒。这很有帮助。谢谢
    【解决方案3】:

    我有同样的错误。我设法通过添加两件事来解决它:

    • connectionFactory 超时
    • 错误处理程序
    @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());
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      LettuceConnectionFactory 不是必需的。只需使用自定义 CacheConfig 即可扩展 CachingConfigurerSupport。并重写errorHandler()方法。

      您只需要实现一个自定义的 CacheErrorHandler,就像 @Tan mally 在他的回答中所做的那样。

      【讨论】:

        【解决方案5】:

        您可以按照其他答案中的建议使用CacheErrorHandler。但你应该确保 RedisCacheManager transactionAwarefalse 在你的 Redis 缓存配置中(以确保在执行缓存部分时尽早提交事务并且错误被 CacheErrorHandler 捕获,不要等到跳过 @987654325 的执行结束@ 部分)。将transactionAware 设置为false 的函数如下所示:

            @Bean
            public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
                JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());
        
                RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofHours(redisDataTTL))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
        
                redisCacheConfiguration.usePrefix();
        
                RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
                        .cacheDefaults(redisCacheConfiguration)
                        .build();
        
                redisCacheManager.setTransactionAware(false);
                return redisCacheManager;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-01
          • 2016-02-12
          • 2023-03-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-17
          • 2017-06-13
          相关资源
          最近更新 更多