当前位置: 首页 > news >正文

多级缓存解决方案

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

🏗️ 整体架构设计

命中
未命中
命中
未命中
客户端请求
Controller层
Service层
查询本地缓存
返回数据
查询Redis缓存
回写本地缓存
查询数据库
回写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;}
}

💡 核心优势总结

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

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

http://www.dtcms.com/a/597019.html

相关文章:

  • C++ 二分查找(Binary Search):从原理到实战的全面解析
  • Synbo Protocol 受邀出席ETHShanghai 2025,以共识机制重构链上融资生态
  • 软考 系统架构设计师历年真题集萃(198)—— 2025年11月系统架构设计师真题1
  • 专业网站开发服务电商网站建设哪个好
  • 哈希表和unordered_map和unordered_set
  • HTTP报文格式
  • 厦门网页建站申请费用怎么找出网站的备案号
  • maven专题
  • 渭南市住房和城乡建设局官方网站定制网站和模板网站及仿站的区别
  • Data Agent业务场景方案分析
  • AWS ALB 和目标组异常事件监控实战
  • python中模拟浏览器操作之playwright使用说明以及打包浏览器驱动问题
  • pnpm环境下防止误使用npm的方法
  • 服务器证书与网站不符2020中国企业500强榜单
  • 医疗电子试验箱 生物电子实验箱 生物医学教学平台 嵌入式生物医学电子实验箱
  • 网易云音乐解析(无损音乐均可下载)
  • android StateFlow和sharedflow
  • 幽冥大陆(十八)手机摄像头注册到电脑源码——东方仙盟炼气期
  • 2025年渗透测试面试题总结-240(题目+回答)
  • 防滑齿构型与牙体组织损伤风险的相关性分析
  • C#7、如何处理异常
  • 网站建设培训公司番禺厂家关键词优化
  • Oracle19c单机部署(本地)
  • 【vscode】vscode上plantuml安装和使用
  • Prompt 提示词工程
  • html5做网站总结邢台 网站建设
  • 黑马JAVAWeb -Vue工程化 - Element Plus
  • [Android] AR绘画素描1.0版(AR Draw - Sketch Anime Cartoon 1.0)
  • 微信网站用什么语言开发金融软件网站建设公司
  • 一个静态网站怎么做南宁网站建设怎么样