SpringBoot + 多活缓存 + 本地缓存:Redis 故障时自动降级,保障核心接口可用性

相信很多小伙伴都遇到过这样的问题:线上系统运行得好好的,突然Redis挂了,结果整个系统都跟着瘫痪,用户体验直线下降。那么,有没有什么办法能让系统在Redis故障时依然保持稳定运行呢?答案就是多级缓存架构!

为什么需要多级缓存?

先来说说我们面临的现实问题。在高并发系统中,缓存是必不可少的组件,它能显著提升系统性能。但单点的缓存服务存在风险,一旦缓存服务宕机,大量请求会直接打到数据库上,造成数据库压力剧增,甚至可能导致整个系统崩溃。

举个例子,如果你的电商系统中,商品详情页的访问量很大,平时都走Redis缓存,一旦Redis不可用,所有请求都会直接访问数据库,很可能瞬间就把数据库拖垮了。

这时候,多级缓存就派上用场了。通过构建多级缓存体系,我们可以实现缓存的高可用性,即使某一级缓存出现问题,其他层级的缓存依然可以提供服务,从而保障核心接口的可用性。

多级缓存架构设计

我们的解决方案是构建一个包含Redis远程缓存和本地缓存的多级缓存架构:

  1. L1缓存(本地缓存):使用Caffeine作为本地缓存,速度快,访问延迟极低
  2. 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不可用时,我们的多级缓存系统会自动执行以下降级策略:

  1. 读取降级:当从Redis读取数据失败时,系统会直接返回本地缓存中的数据
  2. 写入降级:当向Redis写入数据失败时,系统会仅将数据写入本地缓存
  3. 删除降级:当从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());
            // 可以在这里添加额外的处理逻辑,如发送告警等
        }
    }
}

最佳实践建议

  1. 合理设置缓存大小:根据内存资源合理设置本地缓存的最大容量,避免内存溢出
  2. 设置合适的过期时间:既要保证缓存的有效性,又要避免数据陈旧
  3. 监控缓存命中率:定期检查各级缓存的命中率,优化缓存策略
  4. 实现缓存预热:在系统启动时预加载热点数据到缓存中
  5. 优雅降级和恢复:当Redis恢复后,逐步同步数据,避免缓存击穿

总结

通过构建多级缓存架构,我们能够在Redis故障时实现自动降级,保障核心接口的可用性。这种方案不仅提高了系统的容错能力,还能在一定程度上提升系统的整体性能。

在实际生产环境中,这种多级缓存架构已经被广泛应用于各种高并发系统中,是一个经过验证的有效解决方案。当然,具体实施时还需要根据业务特点进行适当调整和优化。

希望这篇文章能对你有所帮助,如果你觉得有用,欢迎关注"服务端技术精选",我会持续分享更多实用的技术干货。也欢迎访问我的个人技术博客:www.jiangyi.space,获取更多技术文章。


标题:SpringBoot + 多活缓存 + 本地缓存:Redis 故障时自动降级,保障核心接口可用性
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/25/1769340903764.html

    0 评论
avatar