多级缓存解决方案
在 Spring Boot 中实现"本地缓存 → Redis → 数据库"的多级缓存架构是一种非常有效的性能优化方案。下面我将详细展示完整的实现方案。
🏗️ 整体架构设计
📦 第一步:添加依赖
<!-- 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>
⚙️ 第二步:配置类
应用配置
# application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/test_dbusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: trueredis:host: localhostport: 6379password: database: 0timeout: 3000mslettuce:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0# 多级缓存配置
multi-level-cache:local:# 本地缓存配置maximum-size: 10000expire-after-write: 10m # 10分钟redis:# Redis缓存配置 default-expiration: 30m # 30分钟
Caffeine 配置类
@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 本地缓存*/@Beanpublic 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 模板配置*/@Beanpublic 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;}
}
🎯 第三步:核心服务实现
多级缓存服务
@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@AllArgsConstructorpublic static class CacheStats {private long hitCount;private long missCount;private long loadSuccessCount;private long loadFailureCount;private long totalLoadTime;}
}
业务服务实现
@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);});}/*** 更新用户信息 - 同时更新缓存*/@Transactionalpublic 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());}
}
🗃️ 第四步:数据层配置
实体类
@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;@PrePersistprotected void onCreate() {createdAt = LocalDateTime.now();updatedAt = LocalDateTime.now();}@PreUpdateprotected void onUpdate() {updatedAt = LocalDateTime.now();}
}
Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByEmail(String email);List<User> findByIdIn(List<Long> ids);
}
🎮 第五步:控制器层
@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();}
}
🔧 第六步:高级特性
缓存预热
@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;}@Overridepublic 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());}
}
监控端点
@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;}
}
💡 核心优势总结
- 性能极致:本地缓存提供纳秒级访问,Redis提供分布式缓存
- 高可用:任何一级缓存故障都不会导致系统完全不可用
- 可扩展:易于添加新的缓存层级或替换缓存实现
- 监控完善:提供详细的缓存命中率统计
- 容错性强:每级缓存都有异常处理,避免级联失败
这种架构特别适合读多写少、数据变化不频繁但访问量巨大的业务场景。
