news 2026/5/10 6:39:50

Spring Boot 缓存优化:从入门到精通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 缓存优化:从入门到精通

Spring Boot 缓存优化:从入门到精通

核心概念

缓存是提高应用性能的重要手段,Spring Boot 提供了强大的缓存支持。通过合理配置和使用缓存,可以显著减少数据库访问次数,提高响应速度。

Spring Boot 缓存抽象

Spring Boot 提供了统一的缓存抽象层,支持多种缓存实现:

  1. ConcurrentHashMap:默认缓存实现,适合开发环境
  2. Redis:分布式缓存,适合生产环境
  3. Caffeine:高性能本地缓存
  4. EhCache:成熟的缓存解决方案

缓存配置

// 缓存配置类 @Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); List<Cache> caches = Arrays.asList( new ConcurrentMapCache("users"), new ConcurrentMapCache("products"), new ConcurrentMapCache("orders") ); cacheManager.setCaches(caches); return cacheManager; } } // 使用 Redis 作为缓存 @Configuration @EnableCaching public class RedisCacheConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .build(); } } // 使用 Caffeine 作为缓存 @Configuration @EnableCaching public class CaffeineCacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .initialCapacity(100) .maximumSize(500) .expireAfterWrite(Duration.ofMinutes(30)) .recordStats()); cacheManager.setCacheNames(Arrays.asList("users", "products", "orders")); return cacheManager; } }

缓存注解使用

// 缓存服务示例 @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } @Cacheable(value = "users", key = "#id") public User findById(Long id) { // 只有第一次调用时会执行此方法,后续调用直接从缓存获取 return userRepository.findById(id).orElse(null); } @Cacheable(value = "users", key = "#email") public User findByEmail(String email) { return userRepository.findByEmail(email).orElse(null); } @CachePut(value = "users", key = "#user.id") public User save(User user) { // 更新缓存 return userRepository.save(user); } @CacheEvict(value = "users", key = "#id") public void deleteById(Long id) { // 删除缓存 userRepository.deleteById(id); } @CacheEvict(value = "users", allEntries = true) public void clearCache() { // 清除所有用户缓存 } @Caching( put = { @CachePut(value = "users", key = "#user.id"), @CachePut(value = "users", key = "#user.email") } ) public User update(User user) { // 更新多个缓存条目 return userRepository.save(user); } }

缓存条件

@Service public class ProductService { private final ProductRepository productRepository; public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; } @Cacheable(value = "products", key = "#id", condition = "#id > 0") public Product findById(Long id) { // 只有 id > 0 时才使用缓存 return productRepository.findById(id).orElse(null); } @Cacheable(value = "products", key = "#name", unless = "#result == null") public Product findByName(String name) { // 只有结果不为 null 时才缓存 return productRepository.findByName(name).orElse(null); } @Cacheable(value = "products", key = "#category", condition = "#category != 'ALL'") public List<Product> findByCategory(String category) { // 只有 category 不是 'ALL' 时才使用缓存 return productRepository.findByCategory(category); } }

缓存与事务

@Service @Transactional public class OrderService { private final OrderRepository orderRepository; private final ProductService productService; public OrderService(OrderRepository orderRepository, ProductService productService) { this.orderRepository = orderRepository; this.productService = productService; } @Cacheable(value = "orders", key = "#id") public Order findById(Long id) { return orderRepository.findById(id).orElse(null); } @CachePut(value = "orders", key = "#order.id") public Order create(Order order) { // 在事务内创建订单,缓存会在事务提交后更新 return orderRepository.save(order); } @CacheEvict(value = "orders", key = "#id") public void cancel(Long id) { Order order = findById(id); if (order != null) { order.setStatus("CANCELLED"); orderRepository.save(order); } } }

缓存穿透解决方案

// 缓存穿透:查询不存在的数据 @Service public class CachePenetrationService { private static final String NULL_VALUE = "NULL_VALUE"; @Autowired private UserRepository userRepository; @Cacheable(value = "users", key = "#id") public User findById(Long id) { User user = userRepository.findById(id).orElse(null); if (user == null) { // 返回一个特殊的空值标记,防止缓存穿透 throw new UserNotFoundException("User not found"); } return user; } } // 使用布隆过滤器防止缓存穿透 @Component public class BloomFilterCache { private final BloomFilter<Long> userBloomFilter; public BloomFilterCache() { // 预计插入 1000000 条数据,误判率 0.01% this.userBloomFilter = BloomFilter.create( Funnels.longFunnel(), 1000000, 0.0001 ); } public void add(Long id) { userBloomFilter.put(id); } public boolean mightContain(Long id) { return userBloomFilter.mightContain(id); } } @Service public class UserServiceWithBloomFilter { private final BloomFilterCache bloomFilterCache; private final UserRepository userRepository; public UserServiceWithBloomFilter(BloomFilterCache bloomFilterCache, UserRepository userRepository) { this.bloomFilterCache = bloomFilterCache; this.userRepository = userRepository; } @Cacheable(value = "users", key = "#id") public User findById(Long id) { // 先检查布隆过滤器 if (!bloomFilterCache.mightContain(id)) { throw new UserNotFoundException("User not found"); } return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found")); } }

缓存击穿解决方案

// 缓存击穿:热点数据过期时大量请求同时访问数据库 @Service public class HotProductService { private final ProductRepository productRepository; private final ReentrantLock lock = new ReentrantLock(); @Cacheable(value = "products", key = "#id") public Product getHotProduct(Long id) { return getProductFromDb(id); } private Product getProductFromDb(Long id) { // 使用双重检查锁防止缓存击穿 String cacheKey = "products::" + id; // 尝试获取锁 if (lock.tryLock()) { try { // 再次检查缓存 Product cached = getFromCache(cacheKey); if (cached != null) { return cached; } // 从数据库获取 Product product = productRepository.findById(id).orElse(null); // 更新缓存 if (product != null) { updateCache(cacheKey, product); } return product; } finally { lock.unlock(); } } else { // 等待其他线程更新缓存 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 再次尝试从缓存获取 return getHotProduct(id); } } private Product getFromCache(String key) { // 从缓存获取 return null; } private void updateCache(String key, Product product) { // 更新缓存 } }

缓存雪崩解决方案

// 缓存雪崩:大量缓存同时过期 @Configuration @EnableCaching public class CacheAvalancheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .initialCapacity(100) .maximumSize(500) .expireAfterWrite(Duration.ofMinutes(30)) .expireAfterAccess(Duration.ofMinutes(10)) .recordStats()); return cacheManager; } } @Service public class ProductServiceWithRandomExpire { private final ProductRepository productRepository; public ProductServiceWithRandomExpire(ProductRepository productRepository) { this.productRepository = productRepository; } @Cacheable(value = "products", key = "#id") public Product findById(Long id) { Product product = productRepository.findById(id).orElse(null); if (product != null) { // 设置随机过期时间,避免缓存雪崩 int randomMinutes = new Random().nextInt(10) + 25; // 25-35 分钟 updateCacheWithExpire("products::" + id, product, randomMinutes); } return product; } private void updateCacheWithExpire(String key, Product product, int minutes) { // 更新缓存并设置过期时间 } }

缓存监控

// 缓存监控服务 @Service public class CacheMetricsService { private final CacheManager cacheManager; public CacheMetricsService(CacheManager cacheManager) { this.cacheManager = cacheManager; } public Map<String, CacheStats> getCacheStats() { Map<String, CacheStats> stats = new HashMap<>(); cacheManager.getCacheNames().forEach(cacheName -> { Cache cache = cacheManager.getCache(cacheName); if (cache instanceof CaffeineCache) { CaffeineCache caffeineCache = (CaffeineCache) cache; com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = caffeineCache.getNativeCache(); CacheStats cacheStats = nativeCache.stats(); stats.put(cacheName, cacheStats); } }); return stats; } public void printStats() { Map<String, CacheStats> stats = getCacheStats(); stats.forEach((cacheName, cacheStats) -> { System.out.println("Cache: " + cacheName); System.out.println(" Hits: " + cacheStats.hitCount()); System.out.println(" Misses: " + cacheStats.missCount()); System.out.println(" Hit Rate: " + String.format("%.2f%%", cacheStats.hitRate() * 100)); System.out.println(" Evictions: " + cacheStats.evictionCount()); System.out.println(); }); } } // 缓存统计端点 @RestController @RequestMapping("/cache") public class CacheController { private final CacheMetricsService cacheMetricsService; public CacheController(CacheMetricsService cacheMetricsService) { this.cacheMetricsService = cacheMetricsService; } @GetMapping("/stats") public ResponseEntity<Map<String, Object>> getCacheStats() { Map<String, Object> result = new HashMap<>(); Map<String, CacheStats> stats = cacheMetricsService.getCacheStats(); stats.forEach((cacheName, cacheStats) -> { Map<String, Object> cacheInfo = new HashMap<>(); cacheInfo.put("hits", cacheStats.hitCount()); cacheInfo.put("misses", cacheStats.missCount()); cacheInfo.put("hitRate", String.format("%.2f%%", cacheStats.hitRate() * 100)); cacheInfo.put("evictions", cacheStats.evictionCount()); cacheInfo.put("averageLoadPenalty", cacheStats.averageLoadPenalty()); result.put(cacheName, cacheInfo); }); return ResponseEntity.ok(result); } @DeleteMapping("/clear") public ResponseEntity<Void> clearAllCache() { cacheMetricsService.getCacheStats().keySet().forEach(cacheName -> { Cache cache = cacheMetricsService.cacheManager.getCache(cacheName); if (cache != null) { cache.clear(); } }); return ResponseEntity.noContent().build(); } }

最佳实践

  1. 选择合适的缓存策略:根据业务场景选择本地缓存或分布式缓存
  2. 设置合理的过期时间:根据数据更新频率设置过期时间
  3. 处理缓存与数据库一致性:使用 @CachePut 更新缓存
  4. 防止缓存穿透:使用布隆过滤器或空值缓存
  5. 防止缓存击穿:使用互斥锁或分布式锁
  6. 防止缓存雪崩:设置随机过期时间
  7. 监控缓存性能:定期检查缓存命中率
  8. 合理规划缓存键:使用有意义的缓存键命名规范

实际应用场景

  • 高频查询场景:如商品详情、用户信息
  • 热点数据缓存:如首页推荐、热门商品
  • 会话缓存:如用户登录状态
  • 计算结果缓存:如复杂查询结果

总结

Spring Boot 的缓存抽象层提供了强大而灵活的缓存支持。通过合理配置和使用缓存,可以显著提高应用性能。在实际应用中,需要根据业务场景选择合适的缓存策略,并注意处理缓存穿透、击穿和雪崩等问题。

别叫我大神,叫我 Alex 就好。这其实可以更优雅一点,合理的缓存配置让应用性能变得更加出色和高效。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 6:39:01

Kong网关智能运维代理:策略驱动自动化与实战部署指南

1. 项目概述&#xff1a;一个面向Kong网关的智能运维代理 最近在搞微服务网关的自动化运维&#xff0c;发现了一个挺有意思的开源项目—— KtKID/kongming-agent 。这名字起得挺有韵味&#xff0c;“孔明”嘛&#xff0c;一听就是冲着“智能”和“运筹帷幄”去的。简单来说&a…

作者头像 李华
网站建设 2026/5/10 6:30:59

PrompTrek:统一AI编程助手配置,实现一次编写、处处运行

1. 项目概述&#xff1a;告别AI编辑器配置的“巴别塔”如果你和我一样&#xff0c;日常开发需要在GitHub Copilot、Cursor、Continue、Claude Code这些AI编程助手之间来回切换&#xff0c;那你一定体会过那种“配置地狱”的痛苦。每个编辑器都有自己的一套提示词&#xff08;Pr…

作者头像 李华
网站建设 2026/5/10 6:27:37

终极指南:在macOS上轻松解锁QQ音乐加密文件的完整解决方案

终极指南&#xff1a;在macOS上轻松解锁QQ音乐加密文件的完整解决方案 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac&#xff0c;qmc0,qmc3转mp3, mflac,mflac0等转flac)&#xff0c;仅支持macOS&#xff0c;可自动识别到QQ音乐下载目录&#xff0c;默…

作者头像 李华
网站建设 2026/5/10 6:27:15

Windows系统光标自定义:从原理到实践,打造个性化交互体验

1. 项目概述&#xff1a;从“默认”到“自定义”的交互革命在数字世界里&#xff0c;鼠标指针是我们与计算机交互最直接的物理延伸。每天&#xff0c;我们的视线无数次地追随那个小小的箭头或手形图标&#xff0c;点击、拖拽、悬停。然而&#xff0c;绝大多数用户终其一生都在使…

作者头像 李华
网站建设 2026/5/10 6:23:38

AWS生成式AI企业应用实战:30+场景化方案与RAG架构深度解析

1. 项目概述&#xff1a;当生成式AI遇见企业级应用蓝图最近在GitHub上闲逛&#xff0c;又发现了一个宝藏仓库&#xff1a;aws-samples/generative-ai-use-cases。这可不是一个简单的代码合集&#xff0c;而是一份由亚马逊云科技官方出品的、面向企业级生成式AI应用的“实战百科…

作者头像 李华
网站建设 2026/5/10 6:23:35

OpenFang开源语音助手框架:模块化设计与实战开发指南

1. 项目概述&#xff1a;一个开源的AI语音助手框架最近在折腾智能家居和语音交互&#xff0c;发现市面上的语音助手要么是闭源的“黑盒”&#xff0c;要么就是功能定制性太差&#xff0c;想自己加点功能或者改改唤醒词都无从下手。直到我发现了RightNow-AI团队开源的OpenFang项…

作者头像 李华