SpringBoot + 多活缓存 + 本地缓存:Redis 故障时自动降级,保障核心接口可用性
相信很多小伙伴都遇到过这样的问题:线上系统运行得好好的,突然Redis挂了,结果整个系统都跟着瘫痪,用户体验直线下降。那么,有没有什么办法能让系统在Redis故障时依然保持稳定运行呢?答案就是多级缓存架构!
为什么需要多级缓存?
先来说说我们面临的现实问题。在高并发系统中,缓存是必不可少的组件,它能显著提升系统性能。但单点的缓存服务存在风险,一旦缓存服务宕机,大量请求会直接打到数据库上,造成数据库压力剧增,甚至可能导致整个系统崩溃。
举个例子,如果你的电商系统中,商品详情页的访问量很大,平时都走Redis缓存,一旦Redis不可用,所有请求都会直接访问数据库,很可能瞬间就把数据库拖垮了。
这时候,多级缓存就派上用场了。通过构建多级缓存体系,我们可以实现缓存的高可用性,即使某一级缓存出现问题,其他层级的缓存依然可以提供服务,从而保障核心接口的可用性。
多级缓存架构设计
我们的解决方案是构建一个包含Redis远程缓存和本地缓存的多级缓存架构:
- L1缓存(本地缓存):使用Caffeine作为本地缓存,速度快,访问延迟极低
- L2缓存(远程缓存):使用Redis作为分布式缓存,容量大,可共享
当Redis正常工作时,系统会优先从本地缓存获取数据,如果本地缓存未命中,则从Redis获取数据,并同步更新本地缓存。当Redis出现故障时,系统会自动降级到本地缓存,虽然可能命中率有所下降,但至少能保证核心接口的可用性。
核心实现代码
让我们看看如何在SpringBoot中实现这个多级缓存架构:
1. 添加依赖
首先在pom.xml中添加必要的依赖:
<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>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2. Redis缓存配置
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
@Bean
@Primary
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
3. 多级缓存管理器
这是核心组件,实现了Redis故障时的自动降级:
@Component
@Slf4j
public class MultiLevelCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存,使用Caffeine
private Cache<String, Object> localCache;
@PostConstruct
public void init() {
// 初始化本地缓存
this.localCache = Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存1000个条目
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
.recordStats() // 记录统计信息
.build();
}
/**
* 获取缓存值
* 优先从本地缓存获取,如果本地缓存没有,则从Redis获取
* Redis故障时,直接使用本地缓存
*/
public Object get(String key) {
// 首先尝试从本地缓存获取
Object value = localCache.getIfPresent(key);
if (value != null) {
log.debug("从本地缓存获取数据,key: {}", key);
return value;
}
// 本地缓存未命中,尝试从Redis获取
try {
value = redisTemplate.opsForValue().get(key);
if (value != null) {
log.debug("从Redis获取数据,key: {}", key);
// 同步到本地缓存
localCache.put(key, value);
}
return value;
} catch (Exception e) {
log.warn("Redis访问失败,使用本地缓存,key: {}", key, e);
// Redis故障时,仅使用本地缓存
return localCache.getIfPresent(key);
}
}
/**
* 设置缓存值
* 同时设置到Redis和本地缓存
*/
public void put(String key, Object value) {
// 设置到本地缓存
localCache.put(key, value);
// 设置到Redis,如果Redis故障则只保留本地缓存
try {
redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS);
log.debug("设置缓存到Redis和本地,key: {}", key);
} catch (Exception e) {
log.warn("Redis设置失败,仅保存到本地缓存,key: {}", key, e);
}
}
/**
* 删除缓存
*/
public void evict(String key) {
// 从本地缓存删除
localCache.invalidate(key);
// 从Redis删除,忽略异常
try {
redisTemplate.delete(key);
log.debug("从Redis和本地删除缓存,key: {}", key);
} catch (Exception e) {
log.warn("Redis删除失败,仅从本地缓存删除,key: {}", key, e);
}
}
}
4. 业务服务层应用
在业务服务中使用多级缓存,还可以结合缓存工具类增强功能:
@Component
@Slf4j
public class CacheUtil {
@Autowired
private MultiLevelCacheManager cacheManager;
private Executor asyncExecutor;
@PostConstruct
public void init() {
// 初始化异步执行器
this.asyncExecutor = Executors.newFixedThreadPool(5);
}
/**
* 异步设置缓存值
* 用于不影响主线程性能的缓存预热等场景
*/
public void putAsync(String key, Object value) {
CompletableFuture.runAsync(() -> {
try {
cacheManager.put(key, value);
log.debug("异步设置缓存成功,key: {}", key);
} catch (Exception e) {
log.error("异步设置缓存失败,key: {}", key, e);
}
}, asyncExecutor);
}
/**
* 安全获取缓存值,带类型转换
*/
@SuppressWarnings("unchecked")
public <T> T getTyped(String key, Class<T> type) {
Object value = cacheManager.get(key);
if (value == null) {
return null;
}
try {
return (T) value;
} catch (ClassCastException e) {
log.warn("缓存值类型转换失败,key: {}, expected: {}, actual: {}",
key, type.getName(), value.getClass().getName(), e);
return null;
}
}
}
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserRepository userRepository;
@Autowired
private MultiLevelCacheManager cacheManager;
private static final String USER_CACHE_PREFIX = "user:";
@Transactional(readOnly = true)
public Optional<User> getUserById(Long id) {
String cacheKey = USER_CACHE_PREFIX + id;
// 尝试从多级缓存获取
User cachedUser = cacheUtil.getTyped(cacheKey, User.class);
if (cachedUser != null) {
log.info("从缓存获取用户,ID: {}", id);
return Optional.of(cachedUser);
}
// 缓存未命中,从数据库查询
Optional<User> userOpt = userRepository.findById(id);
if (userOpt.isPresent()) {
// 查询到用户后,放入多级缓存
cacheManager.put(cacheKey, userOpt.get());
log.info("从数据库获取用户并存入缓存,ID: {}", id);
}
return userOpt;
}
@Transactional
public User createUser(String username, String email, String fullName) {
User user = new User();
user.setUsername(username);
user.setEmail(email);
user.setFullName(fullName);
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
User savedUser = userRepository.save(user);
// 更新缓存
String userCacheKey = USER_CACHE_PREFIX + savedUser.getId();
String usernameCacheKey = USER_CACHE_PREFIX + "username:" + username;
cacheManager.put(userCacheKey, savedUser);
cacheManager.put(usernameCacheKey, savedUser);
log.info("创建用户并更新缓存,ID: {}", savedUser.getId());
return savedUser;
}
}
降级策略详解
当Redis不可用时,我们的多级缓存系统会自动执行以下降级策略:
- 读取降级:当从Redis读取数据失败时,系统会直接返回本地缓存中的数据
- 写入降级:当向Redis写入数据失败时,系统会仅将数据写入本地缓存
- 删除降级:当从Redis删除数据失败时,系统会仅从本地缓存中删除数据
这种降级策略确保了即使Redis完全不可用,系统的核心读写功能仍然能够继续运行,只是可能面临一些数据一致性的问题(这通常是可以接受的,特别是对于非关键业务)。
监控和运维
为了更好地管理和运维这套多级缓存系统,我们还提供了监控接口:
@RestController
@RequestMapping("/health")
public class HealthCheckController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private MultiLevelCacheManager cacheManager;
@GetMapping("/cache-status")
public ResponseEntity<Map<String, Object>> cacheStatus() {
Map<String, Object> status = new HashMap<>();
// 测试Redis是否可用
boolean redisAvailable = false;
try {
redisTemplate.opsForValue().set("cache_test", "test_value", 5);
redisAvailable = true;
status.put("redis_connection", "SUCCESS");
} catch (Exception e) {
status.put("redis_connection", "FAILED: " + e.getMessage());
}
// 返回缓存状态
status.put("redis_available", redisAvailable);
status.put("cache_strategy", redisAvailable ?
"PRIMARY_REDIS_WITH_LOCAL_FALLBACK" : "LOCAL_CACHE_ONLY");
status.put("local_cache_stats", cacheManager.getLocalCacheStats());
return ResponseEntity.ok(status);
}
}
通过这个接口,我们可以实时监控缓存系统的状态,及时发现Redis故障并采取相应措施。
此外,我们还可以通过定时任务来主动检测Redis连接状态:
@Configuration
@EnableScheduling
@Slf4j
public class FallbackConfig {
@Autowired
private MultiLevelCacheManager cacheManager;
/**
* 定时检测Redis连接状态
* 可以根据实际需要调整检测频率
*/
@Scheduled(fixedRate = 30000) // 每30秒检测一次
public void checkRedisConnection() {
try {
// 尝试执行一个简单的Redis操作来检测连接
cacheManager.get("health_check_key");
log.debug("Redis连接正常");
} catch (Exception e) {
log.warn("Redis连接异常: {}", e.getMessage());
// 可以在这里添加额外的处理逻辑,如发送告警等
}
}
}
最佳实践建议
- 合理设置缓存大小:根据内存资源合理设置本地缓存的最大容量,避免内存溢出
- 设置合适的过期时间:既要保证缓存的有效性,又要避免数据陈旧
- 监控缓存命中率:定期检查各级缓存的命中率,优化缓存策略
- 实现缓存预热:在系统启动时预加载热点数据到缓存中
- 优雅降级和恢复:当Redis恢复后,逐步同步数据,避免缓存击穿
总结
通过构建多级缓存架构,我们能够在Redis故障时实现自动降级,保障核心接口的可用性。这种方案不仅提高了系统的容错能力,还能在一定程度上提升系统的整体性能。
在实际生产环境中,这种多级缓存架构已经被广泛应用于各种高并发系统中,是一个经过验证的有效解决方案。当然,具体实施时还需要根据业务特点进行适当调整和优化。
希望这篇文章能对你有所帮助,如果你觉得有用,欢迎关注"服务端技术精选",我会持续分享更多实用的技术干货。也欢迎访问我的个人技术博客:www.jiangyi.space,获取更多技术文章。
标题:SpringBoot + 多活缓存 + 本地缓存:Redis 故障时自动降级,保障核心接口可用性
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/25/1769340903764.html