16

我们有 rest api 应用程序。我们使用 redis 进行 API 响应缓存和内部方法缓存。如果 redis 连接,那么它会使我们的 API 关闭。如果 redis 连接失败或出现任何异常,我们希望绕过 redis 缓存,而不是关闭我们的 API。有一个接口 CacheErrorHandler 但它处理 redis 获取设置操作失败而不是 redis 连接问题。我们使用的是 Spring 4.1.2。

4

7 回答 7

16

让我们稍微简化一下。您的应用程序使用缓存(使用 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 核心的缓存抽象中确定缓存“未命中”的所有重要因素是返回的值为空。因此,Spring Caching Infrastructure 将继续调用实际的 Service 方法(即 getCustomer)。请记住在 getCustomerRepo().load(customerId) 调用的返回上,您还需要处理 Spring 的 Caching Infrastructure 现在尝试缓存该值的情况。

本着保持简单的精神,我们将不使用 AOP,但您也应该能够使用 AOP 来实现这一点(您的选择)。

您(应该)需要的只是一个扩展SDR CacheManager 实现的“自定义” 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 存在问题,以便您可以适当地处理异常。但是,如果您在检查时确定 Exception 与连接问题有关,您可以返回“null”,让 Spring Caching Infrastructure 像缓存“未命中”一样继续进行(即错误的 Redis 连接 == 缓存未命中,在这种情况下)。

我知道这样的事情应该可以帮助您解决问题,因为我为 GemFire 和 Pivotal 的一位客户构建了一个类似的“自定义”CacheManager 实现原型。在那个特定的 UC 中,缓存“未命中”必须由应用程序域对象的“过期版本”触发,其中生产环境中混合了新旧应用程序客户端通过 Spring 的缓存抽象连接到 GemFire。例如,应用程序域对象字段会在较新版本的应用程序中发生变化。

无论如何,希望这有助于或给你更多的想法。

干杯!

于 2015-03-26T17:55:45.513 回答
7

所以,我今天正在挖掘核心 Spring 框架缓存抽象源来解决另一个问题,如果 CacheErrorHandler 正确实现,那么可能有问题的 Redis 连接仍可能导致所需的行为,例如缓存“未命中”(由返回一个空值)。

有关更多详细信息,请参阅AbstractCacheInvoker源代码。

由于cache.get(key)Redis连接错误,应该导致异常,因此将调用异常处理程序......

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

如果 CacheErrorHandler 正确处理了缓存“get”错误(并且不重新抛出/异常),则将返回一个空值,指示缓存“未命中”。

于 2015-03-30T18:30:01.643 回答
5

谢谢@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;
    }
}
于 2017-07-10T14:12:40.560 回答
4

实际上我的回复是针对@Vivek Aditya 先生 - 我遇到了同样的问题:新的 spring-data-redis api 并且没有为每个 RedisTemplate 构建 RedisCacheManager。唯一的选择——基于@John Blum 的建议——是使用方面。下面是我的代码。

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

适用于所有合理的场景:

  • 应用程序在 redis 关闭时启动正常
  • 应用程序(仍然)在(突然)redis 中断期间工作
  • 当 redis 再次开始工作时,应用程序会看到它

编辑:代码更像是一个 poc - 仅用于“获取”,我不喜欢每次缓存命中都重新实例化 FailoverRedisCache - 应该有一个映射。

于 2019-01-06T22:01:14.380 回答
0

所有核心Spring Framework Cache 抽象注释(例如 @Cacheable)以及由核心 SF 委托支持的 JSR-107 JCache 注释到底层CacheManager的底层,对于 Redis,即RedisCacheManager

您将在类似于此处的 Spring XML 配置元数据中配置 RedisCacheManager

一种方法是为 (Redis)CacheManager 编写一个 AOP 代理,该代理使用RedisConnection(间接来自RedisTemplate)来确定每个 (Redis)CacheManger 操作的连接状态。

如果标准缓存操作的连接失败或关闭,则 (Redis)CacheManager 可以为getCache(String name)返回一个RedisCache实例,该实例始终返回 null(表示条目上的缓存未命中),从而传递到底层数据存储。

可能有更好的方法来处理这个问题,因为我不是 Redis(或 SDR)所有方面的专家,但这应该可以工作,也许会给你一些你自己的想法。

干杯。

于 2015-03-12T16:37:30.023 回答
0

我有同样的问题,但不幸的是,上述解决方案都不适合我。我检查了问题,发现如果没有连接到 Redis,执行的命令永远不会超时。所以我开始研究生菜库以寻求解决方案。我通过在没有连接时拒绝命令来解决问题:

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

}
于 2019-05-26T12:55:24.947 回答
0

您可以使用CacheErrorHandler. 但是你应该确保 RedisCacheManager transactionAwarefalse你的 Redis 缓存配置中进行(以确保在执行缓存部分时尽早提交事务并且错误被捕获CacheErrorHandler并且不要等到跳过CacheErrorHandler部分的执行结束)。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;
    }
于 2021-02-02T12:59:46.153 回答