【详解】@Cacheable注解Redis时,Redis宕机或其他原因连不上,继续调用原方法的解决方案
【摘要】 @Cacheable注解Redis时,Redis宕机或其他原因连不上,继续调用原方法的解决方案在Spring Boot应用中,我们经常使用@Cacheable注解来缓存数据,以提高应用的性能。当选择Redis作为缓存存储时,如果Redis服务因某种原因不可用(如宕机、网络问题等),默认情况下,@Cacheable注解会抛出异常,导致整个请求失败。本文将探讨如何在Redis不可...
@Cacheable注解Redis时,Redis宕机或其他原因连不上,继续调用原方法的解决方案
在Spring Boot应用中,我们经常使用@Cacheable
注解来缓存数据,以提高应用的性能。当选择Redis作为缓存存储时,如果Redis服务因某种原因不可用(如宕机、网络问题等),默认情况下,@Cacheable
注解会抛出异常,导致整个请求失败。本文将探讨如何在Redis不可用时,让@Cacheable
注解继续调用原方法,确保服务的可用性和稳定性。
1. 问题背景
1.1 @Cacheable
注解的基本使用
@Cacheable
是Spring框架提供的一个注解,用于标识一个方法的结果需要被缓存。当该方法被调用时,Spring会先检查缓存中是否存在对应的数据,如果存在,则直接返回缓存中的数据;如果不存在,则执行方法并将结果存入缓存。
1.2 Redis宕机的影响
当Redis服务宕机或网络连接出现问题时,@Cacheable
注解尝试访问Redis时会抛出异常,例如org.springframework.data.redis.RedisConnectionFailureException
。这会导致方法调用失败,影响用户体验和系统稳定性。
2. 解决方案
2.1 使用自定义异常处理器
可以通过自定义异常处理器来捕获Redis连接异常,并在捕获到异常时继续调用原方法。具体步骤如下:
2.1.1 创建自定义异常处理器
首先,创建一个自定义异常处理器类,用于处理Redis连接异常。
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
public class CustomCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// 处理读取缓存时的异常
System.out.println("Cache get error: " + exception.getMessage());
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
// 处理写入缓存时的异常
System.out.println("Cache put error: " + exception.getMessage());
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
// 处理清除缓存时的异常
System.out.println("Cache evict error: " + exception.getMessage());
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
// 处理清空缓存时的异常
System.out.println("Cache clear error: " + exception.getMessage());
}
}
2.1.2 配置自定义异常处理器
在Spring Boot配置文件中,配置自定义的异常处理器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.annotation.EnableCaching;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CustomCacheErrorHandler customCacheErrorHandler() {
return new CustomCacheErrorHandler();
}
}
2.2 使用@Cacheable
的unless
属性
@Cacheable
注解提供了一个unless
属性,可以在缓存操作成功后决定是否将结果存入缓存。虽然这个属性不能直接解决Redis宕机的问题,但可以结合其他逻辑来实现类似的效果。
2.3 使用@Cacheable
的cache-null-values
属性
设置@Cacheable
注解的cache-null-values
属性为false
,这样即使Redis不可用,也不会将null
值存入缓存。
@Cacheable(value = "myCache", cacheNullValues = false)
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
2.4 使用降级策略
在Redis不可用时,可以采用降级策略,例如从数据库中直接获取数据。这可以通过自定义的缓存管理器来实现。
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
@Configuration
public class CustomCacheManager {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("myCache") {
@Override
public Cache getCache(String name) {
Cache cache = super.getCache(name);
if (cache == null) {
// 如果Redis不可用,使用本地缓存
cache = new ConcurrentMapCache(name);
}
return cache;
}
};
}
}
我们可以在Redis不可用时,确保@Cacheable
注解继续调用原方法,从而提高系统的稳定性和可用性。具体实现方式包括自定义异常处理器、使用unless
和cache-null-values
属性、以及降级策略。在使用Spring框架结合Redis实现缓存功能时,如果Redis宕机或由于其他原因导致连接不上Redis,可以通过配置CacheManager
来实现当缓存不可用时自动回退到原始方法的调用。这样可以保证系统的可用性和稳定性。
以下是一个具体的实现示例:
- 添加依赖:首先确保你的项目中已经添加了Spring Boot和Redis的相关依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 配置Redis连接:在
application.properties
中配置Redis连接信息。
spring.redis.host=localhost
spring.redis.port=6379
- 自定义CacheManager:创建一个自定义的
CacheManager
,在其中处理Redis不可用的情况。
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 设置默认过期时间
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("myCache", config);
return new FallbackRedisCacheManager(redisConnectionFactory, config, cacheConfigurations);
}
}
- 实现FallbackRedisCacheManager:创建一个自定义的
CacheManager
,在Redis不可用时回退到内存缓存。
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFailureException;
import java.util.Arrays;
import java.util.List;
public class FallbackRedisCacheManager extends RedisCacheManager {
private final CacheManager fallbackCacheManager;
public FallbackRedisCacheManager(RedisConnectionFactory connectionFactory, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
super(connectionFactory, defaultCacheConfiguration, initialCacheConfigurations);
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("fallbackCache")));
simpleCacheManager.afterPropertiesSet();
this.fallbackCacheManager = simpleCacheManager;
}
@Override
public Cache getCache(String name) {
try {
return super.getCache(name);
} catch (RedisConnectionFailureException e) {
return fallbackCacheManager.getCache(name);
}
}
}
- 使用@Cacheable注解:在需要缓存的方法上使用
@Cacheable
注解。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Cacheable(value = "myCache", key = "#id")
public String getData(String id) {
// 模拟数据获取过程
System.out.println("Fetching data from database for ID: " + id);
return "Data for ID: " + id;
}
}
通过上述配置,当Redis不可用时,FallbackRedisCacheManager
会捕获到RedisConnectionFailureException
异常,并回退到内存缓存。这样可以确保即使Redis宕机,系统仍然能够正常运行并返回数据。在使用Spring Cache与Redis结合时,如果Redis出现宕机或连接问题,可以通过配置CacheManager
和实现自定义的CacheErrorHandler
来确保即使缓存不可用,业务逻辑也能正常运行。以下是一个详细的解决方案示例:
1. 添加依赖
首先,确保你的项目中已经添加了Spring Boot Starter Cache和Spring Boot Starter Data Redis的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
2. 配置RedisTemplate
配置RedisTemplate
以使用JSON序列化方式存储对象:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
3. 配置CacheManager
配置CacheManager
以使用Redis作为缓存存储:
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60)) // 设置缓存过期时间为60分钟
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
4. 实现自定义的CacheErrorHandler
实现自定义的CacheErrorHandler
,以便在缓存操作失败时进行处理:
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueException;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.CacheErrorHandler;
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// 处理缓存读取错误
System.out.println("Cache get error: " + exception.getMessage());
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
// 处理缓存写入错误
System.out.println("Cache put error: " + exception.getMessage());
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
// 处理缓存删除错误
System.out.println("Cache evict error: " + exception.getMessage());
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
// 处理缓存清除错误
System.out.println("Cache clear error: " + exception.getMessage());
}
};
}
}
5. 使用@Cacheable注解
在需要缓存的方法上使用@Cacheable
注解:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(value = "users", key = "#userId")
public User getUserById(String userId) {
// 模拟从数据库获取用户信息
System.out.println("Fetching user from database: " + userId);
return new User(userId, "John Doe");
}
}
6. 测试
你可以通过模拟Redis宕机或断开连接来测试上述配置是否生效。例如,可以临时关闭Redis服务,然后调用getUserById
方法,查看是否能够正常返回数据。
总结
通过以上配置,当Redis不可用时,CacheErrorHandler
会捕获到缓存操作的异常,并打印错误信息。同时,由于@Cacheable
注解的默认行为是当缓存不可用时直接调用原方法,因此业务逻辑不会受到影响。这样可以确保系统的高可用性和稳定性。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)