我在新项目中使用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)无法连接(可能是崩溃或网络未启动),它应该直接连接到数据库并获取数据。
答案 0 :(得分:16)
我遇到了同样的问题。我正在开发一些针对数据库的数据服务,通过Spring Caching注释将Redis用作缓存存储。如果Redis服务器不可用,我希望服务继续像未缓存一样运行,而不是抛出异常。
首先,我尝试了一个自定义的CacheErrorHandler,这是Spring提供的一种机制。它没有用,因为它只处理RuntimeExceptions,但仍然让像java.net.ConnectException这样的东西搞砸了。
最后我做的是扩展RedisTemplate,覆盖一些execute()方法,以便它们记录警告而不是传播异常。这看起来有点像黑客,我可能已经覆盖了太少的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;
}
}
}
答案 1 :(得分:2)
我有同样的错误。我设法通过添加两件事来解决它:
@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 :(得分:1)
我使用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;
}
}
答案 3 :(得分:0)
不需要LettuceConnectionFactory。仅使用自定义CacheConfig即可扩展CachingConfigurerSupport。并覆盖errorHandler()方法。
您只需要实现自定义CacheErrorHandler,就像@Tan mally在他的回答中所做的那样。
答案 4 :(得分:0)
您可以按照其他答案中的建议使用 CacheErrorHandler
。但你应该确保
RedisCacheManager transactionAware
到 false
在您的 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;
}