多级缓存解决方案

在 Spring Boot 中实现"本地缓存 → Redis → 数据库"的多级缓存架构是一种非常有效的性能优化方案。下面我将详细展示完整的实现方案。

🏗️ 整体架构设计

命中 未命中 命中 未命中 客户端请求 Controller层 Service层 查询本地缓存 返回数据 查询Redis缓存 回写本地缓存 查询数据库 回写Redis和本地缓存

📦 第一步:添加依赖

xml 复制代码
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- Caffeine 本地缓存 -->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
        <version>3.1.8</version>
    </dependency>
    
    <!-- 数据库相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

⚙️ 第二步:配置类

应用配置

yaml 复制代码
# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

# 多级缓存配置
multi-level-cache:
  local:
    # 本地缓存配置
    maximum-size: 10000
    expire-after-write: 10m  # 10分钟
  redis:
    # Redis缓存配置  
    default-expiration: 30m  # 30分钟

Caffeine 配置类

java 复制代码
@Configuration
@EnableCaching
public class CacheConfig {

    @Value("${multi-level-cache.local.maximum-size:10000}")
    private long maximumSize;
    
    @Value("${multi-level-cache.local.expire-after-write:10m}")
    private Duration expireAfterWrite;

    /**
     * 配置 Caffeine 本地缓存
     */
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(maximumSize)
                .expireAfterWrite(expireAfterWrite)
                .recordStats()  // 开启统计
                .removalListener((key, value, cause) -> 
                    log.debug("本地缓存移除: key={}, cause={}", key, cause))
        );
        return cacheManager;
    }

    /**
     * Redis 模板配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用 Jackson 序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(), 
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(mapper);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        
        return template;
    }
}

🎯 第三步:核心服务实现

多级缓存服务

java 复制代码
@Service
@Slf4j
public class MultiLevelCacheService {
    
    private final CacheManager cacheManager;
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 缓存名称常量
    private static final String LOCAL_CACHE_NAME = "userCache";
    private static final Duration REDIS_EXPIRE_TIME = Duration.ofMinutes(30);
    
    public MultiLevelCacheService(CacheManager cacheManager, 
                                RedisTemplate<String, Object> redisTemplate) {
        this.cacheManager = cacheManager;
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 多级缓存查询:本地缓存 → Redis → 数据库
     */
    public <T> T get(String key, Class<T> type, Function<String, T> loader) {
        // 1. 查询本地缓存
        T value = getFromLocalCache(key, type);
        if (value != null) {
            log.debug("本地缓存命中 key: {}", key);
            return value;
        }
        
        // 2. 查询Redis缓存
        value = getFromRedis(key, type);
        if (value != null) {
            log.debug("Redis缓存命中 key: {}", key);
            // 回写到本地缓存
            putToLocalCache(key, value);
            return value;
        }
        
        // 3. 查询数据库
        log.debug("缓存未命中,查询数据库 key: {}", key);
        value = loader.apply(key);
        if (value != null) {
            // 同时写入Redis和本地缓存
            putToRedis(key, value);
            putToLocalCache(key, value);
        }
        
        return value;
    }
    
    /**
     * 从本地缓存获取数据
     */
    @SuppressWarnings("unchecked")
    private <T> T getFromLocalCache(String key, Class<T> type) {
        try {
            Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
            if (cache != null) {
                Cache.ValueWrapper wrapper = cache.get(buildLocalKey(key));
                if (wrapper != null) {
                    return (T) wrapper.get();
                }
            }
        } catch (Exception e) {
            log.warn("本地缓存查询失败 key: {}, error: {}", key, e.getMessage());
        }
        return null;
    }
    
    /**
     * 从Redis获取数据
     */
    private <T> T getFromRedis(String key, Class<T> type) {
        try {
            String redisKey = buildRedisKey(key);
            Object value = redisTemplate.opsForValue().get(redisKey);
            if (value != null) {
                return type.cast(value);
            }
        } catch (Exception e) {
            log.warn("Redis缓存查询失败 key: {}, error: {}", key, e.getMessage());
        }
        return null;
    }
    
    /**
     * 写入本地缓存
     */
    private void putToLocalCache(String key, Object value) {
        try {
            Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
            if (cache != null) {
                cache.put(buildLocalKey(key), value);
            }
        } catch (Exception e) {
            log.warn("本地缓存写入失败 key: {}, error: {}", key, e.getMessage());
        }
    }
    
    /**
     * 写入Redis缓存
     */
    private void putToRedis(String key, Object value) {
        try {
            String redisKey = buildRedisKey(key);
            redisTemplate.opsForValue().set(redisKey, value, REDIS_EXPIRE_TIME);
        } catch (Exception e) {
            log.warn("Redis缓存写入失败 key: {}, error: {}", key, e.getMessage());
        }
    }
    
    /**
     * 删除缓存(双删策略)
     */
    public void evict(String key) {
        // 先删除本地缓存
        evictLocalCache(key);
        // 再删除Redis缓存
        evictRedisCache(key);
        
        log.debug("缓存删除完成 key: {}", key);
    }
    
    private void evictLocalCache(String key) {
        try {
            Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
            if (cache != null) {
                cache.evict(buildLocalKey(key));
            }
        } catch (Exception e) {
            log.warn("本地缓存删除失败 key: {}, error: {}", key, e.getMessage());
        }
    }
    
    private void evictRedisCache(String key) {
        try {
            redisTemplate.delete(buildRedisKey(key));
        } catch (Exception e) {
            log.warn("Redis缓存删除失败 key: {}, error: {}", key, e.getMessage());
        }
    }
    
    private String buildLocalKey(String key) {
        return "local:" + key;
    }
    
    private String buildRedisKey(String key) {
        return "redis:" + key;
    }
    
    /**
     * 获取缓存统计信息(用于监控)
     */
    public CacheStats getStats() {
        com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = 
            (com.github.benmanes.caffeine.cache.Cache<Object, Object>) 
            cacheManager.getCache(LOCAL_CACHE_NAME).getNativeCache();
        
        com.github.benmanes.caffeine.cache.stats.CacheStats stats = nativeCache.stats();
        return new CacheStats(
            stats.hitCount(),
            stats.missCount(),
            stats.loadSuccessCount(),
            stats.loadFailureCount(),
            stats.totalLoadTime()
        );
    }
    
    @Data
    @AllArgsConstructor
    public static class CacheStats {
        private long hitCount;
        private long missCount;
        private long loadSuccessCount;
        private long loadFailureCount;
        private long totalLoadTime;
    }
}

业务服务实现

java 复制代码
@Service
@Slf4j
public class UserService {
    
    private final UserRepository userRepository;
    private final MultiLevelCacheService cacheService;
    
    private static final String USER_CACHE_PREFIX = "user:";
    
    public UserService(UserRepository userRepository, MultiLevelCacheService cacheService) {
        this.userRepository = userRepository;
        this.cacheService = cacheService;
    }
    
    /**
     * 根据ID查询用户 - 使用多级缓存
     */
    public User getUserById(Long id) {
        String cacheKey = USER_CACHE_PREFIX + id;
        
        return cacheService.get(cacheKey, User.class, key -> {
            // 数据库查询函数
            log.info("查询数据库用户信息,ID: {}", id);
            return userRepository.findById(id).orElse(null);
        });
    }
    
    /**
     * 更新用户信息 - 同时更新缓存
     */
    @Transactional
    public User updateUser(User user) {
        // 1. 更新数据库
        User updatedUser = userRepository.save(user);
        
        // 2. 删除缓存(采用双删策略)
        String cacheKey = USER_CACHE_PREFIX + user.getId();
        cacheService.evict(cacheKey);
        
        log.info("用户信息更新完成,ID: {}", user.getId());
        return updatedUser;
    }
    
    /**
     * 批量获取用户(演示批量操作)
     */
    public List<User> getUsersBatch(List<Long> ids) {
        return ids.stream()
                .map(this::getUserById)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
}

🗃️ 第四步:数据层配置

实体类

java 复制代码
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(unique = true)
    private String email;
    
    private Integer age;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Repository

java 复制代码
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    Optional<User> findByEmail(String email);
    
    List<User> findByIdIn(List<Long> ids);
}

🎮 第五步:控制器层

java 复制代码
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
    
    private final UserService userService;
    private final MultiLevelCacheService cacheService;
    
    public UserController(UserService userService, MultiLevelCacheService cacheService) {
        this.userService = userService;
        this.cacheService = cacheService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return user != null ? 
            ResponseEntity.ok(user) : 
            ResponseEntity.notFound().build();
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        User updatedUser = userService.updateUser(user);
        return ResponseEntity.ok(updatedUser);
    }
    
    @GetMapping("/cache/stats")
    public ResponseEntity<MultiLevelCacheService.CacheStats> getCacheStats() {
        return ResponseEntity.ok(cacheService.getStats());
    }
    
    @DeleteMapping("/{id}/cache")
    public ResponseEntity<Void> clearUserCache(@PathVariable Long id) {
        userService.clearCache(id);
        return ResponseEntity.ok().build();
    }
}

🔧 第六步:高级特性

缓存预热

java 复制代码
@Component
@Slf4j
public class CacheWarmUpRunner implements ApplicationRunner {
    
    private final UserRepository userRepository;
    private final MultiLevelCacheService cacheService;
    
    public CacheWarmUpRunner(UserRepository userRepository, MultiLevelCacheService cacheService) {
        this.userRepository = userRepository;
        this.cacheService = cacheService;
    }
    
    @Override
    public void run(ApplicationArguments args) {
        log.info("开始缓存预热...");
        
        // 预热热点数据
        List<User> hotUsers = userRepository.findAll(PageRequest.of(0, 100)).getContent();
        
        hotUsers.forEach(user -> {
            String cacheKey = "user:" + user.getId();
            cacheService.putToRedis(cacheKey, user);
        });
        
        log.info("缓存预热完成,共预热 {} 条用户数据", hotUsers.size());
    }
}

监控端点

java 复制代码
@RestControllerEndpoint(id = "multicache")
@Slf4j
public class MultiCacheEndpoint {
    
    private final MultiLevelCacheService cacheService;
    
    public MultiCacheEndpoint(MultiLevelCacheService cacheService) {
        this.cacheService = cacheService;
    }
    
    @GetMapping("/stats")
    public Map<String, Object> getCacheStats() {
        MultiLevelCacheService.CacheStats stats = cacheService.getStats();
        
        Map<String, Object> result = new HashMap<>();
        result.put("hitCount", stats.getHitCount());
        result.put("missCount", stats.getMissCount());
        result.put("loadSuccessCount", stats.getLoadSuccessCount());
        result.put("hitRate", calculateHitRate(stats));
        result.put("timestamp", LocalDateTime.now());
        
        return result;
    }
    
    private double calculateHitRate(MultiLevelCacheService.CacheStats stats) {
        long totalRequests = stats.getHitCount() + stats.getMissCount();
        return totalRequests > 0 ? 
            (double) stats.getHitCount() / totalRequests : 0.0;
    }
}

💡 核心优势总结

  1. 性能极致:本地缓存提供纳秒级访问,Redis提供分布式缓存
  2. 高可用:任何一级缓存故障都不会导致系统完全不可用
  3. 可扩展:易于添加新的缓存层级或替换缓存实现
  4. 监控完善:提供详细的缓存命中率统计
  5. 容错性强:每级缓存都有异常处理,避免级联失败

这种架构特别适合读多写少数据变化不频繁访问量巨大的业务场景。

相关推荐
橘子海全栈攻城狮1 小时前
【源码+文档+调试讲解】基于Spring Boot的考务管理系统设计与实现 085
java·spring boot·后端·spring
⑩-2 小时前
Redis(1)
数据库·redis·缓存
東雪木4 小时前
Spring Boot 2.x 集成 Knife4j (OpenAPI 3) 完整操作指南
java·spring boot·后端·swagger·knife4j·java异常处理
陈果然DeepVersion5 小时前
Java大厂面试真题:从Spring Boot到AI微服务的三轮技术拷问(二)
spring boot·redis·spring cloud·微服务·ai·java面试·rag
Q_Q19632884756 小时前
python+django/flask的医院财务管理系统
spring boot·python·django·flask·node.js
半旧夜夏6 小时前
【Gateway】服务调用和网关配置攻略
java·spring boot·spring cloud·gateway
一 乐6 小时前
个人博客|博客app|基于Springboot+微信小程序的个人博客app系统设计与实现(源码+数据库+文档)
java·前端·数据库·spring boot·后端·小程序·论文
m0_639817158 小时前
基于springboot个人云盘管理系统【带源码和文档】
java·spring boot·后端