3446 字
约 11 分钟
1
🚀 SpringBoot Cache
2026-09-19
无标签

🚀 第15章 SpringBoot Cache

掌握缓存技术,提升应用性能和用户体验 💻 查看完整代码 - 在线IDE体验 学习目标

📋 学习目标

  • 理解缓存概念- 缓存原理与应用场景
  • 掌握Spring Cache- 注解式缓存使用
  • 集成Redis缓存- 分布式缓存方案
  • 缓存策略配置- 过期时间、淘汰策略
  • 缓存最佳实践- 缓存穿透、雪崩处理
  • 性能优化技巧- 缓存预热、监控

缓存核心概念

🔍 缓存核心概念

  • 缓存命中- 数据在缓存中找到
  • 缓存穿透- 查询不存在的数据
  • 缓存雪崩- 大量缓存同时失效
  • 缓存击穿- 热点数据缓存失效
  • 缓存预热- 提前加载热点数据
  • 缓存更新- 数据一致性保证

缓存依赖配置

📦 缓存依赖配置

<!-- pom.xml 缓存依赖配置 -->
<dependencies>
    <!-- SpringBoot Cache启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
    <!-- SpringBoot Redis启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- SpringBoot Web启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

# application.yml 缓存配置
spring:
  # Redis配置
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
  
  # 缓存配置
  cache:
    type: redis
    redis:
      time-to-live: 600000  # 10分钟
      cache-null-values: true
      use-key-prefix: true
      key-prefix: "cache:"

# 日志配置
logging:
  level:
    cn.bugstack.springframework.boot.cache: DEBUG
    org.springframework.cache: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

缓存执行流程

🔄 缓存执行流程

1请求到达 客户端发起数据查询请求 2缓存查找 检查缓存中是否存在数据 3缓存命中 如果命中,直接返回缓存数据 4缓存未命中 查询数据库获取数据 5更新缓存 将查询结果存入缓存 Spring Cache注解

🎯 Spring Cache注解使用

// CacheConfig.java - 缓存配置类
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
    
    /**
     * 配置Redis缓存管理器
     */
    @Bean
    @Primary
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 设置缓存过期时间为10分钟
                .entryTtl(Duration.ofMinutes(10))
                // 设置key序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                // 设置value序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()))
                // 不缓存null值
                .disableCachingNullValues();
        
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
    }
    
    /**
     * 配置本地缓存管理器(Caffeine)
     */
    @Bean("caffeineCacheManager")
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                // 设置最大缓存数量
                .maximumSize(1000)
                // 设置写入后过期时间
                .expireAfterWrite(Duration.ofMinutes(5))
                // 设置缓存移除监听器
                .removalListener((key, value, cause) -> 
                    log.info("缓存移除 - key: {}, value: {}, cause: {}", key, value, cause))
        );
        return cacheManager;
    }
}

// UserService.java - 用户服务类
@Service
@Slf4j
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * @Cacheable - 缓存查询结果
     * value: 缓存名称
     * key: 缓存key,支持SpEL表达式
     * condition: 缓存条件
     * unless: 排除条件
     */
    @Cacheable(value = "users", key = "#id", condition = "#id > 0")
    public User getUserById(Long id) {
        log.info("从数据库查询用户信息,ID: {}", id);
        
        // 模拟数据库查询耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        User user = userRepository.findById(id).orElse(null);
        if (user == null) {
            log.warn("用户不存在,ID: {}", id);
        }
        return user;
    }
    
    /**
     * @CachePut - 更新缓存
     * 无论缓存是否存在,都会执行方法并更新缓存
     */
    @CachePut(value = "users", key = "#user.id")
    public User saveUser(User user) {
        log.info("保存用户信息: {}", user);
        
        if (user.getId() == null) {
            user.setId(System.currentTimeMillis());
        }
        user.setUpdateTime(LocalDateTime.now());
        
        User savedUser = userRepository.save(user);
        log.info("用户保存成功,更新缓存: {}", savedUser);
        return savedUser;
    }
    
    /**
     * @CacheEvict - 清除缓存
     * allEntries: 是否清除所有缓存条目
     * beforeInvocation: 是否在方法执行前清除缓存
     */
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        log.info("删除用户,ID: {}", id);
        userRepository.deleteById(id);
        log.info("用户删除成功,清除缓存: {}", id);
    }
    
    /**
     * @CacheEvict - 清除所有用户缓存
     */
    @CacheEvict(value = "users", allEntries = true)
    public void clearAllUserCache() {
        log.info("清除所有用户缓存");
    }
    
    /**
     * @Caching - 组合多个缓存操作
     */
    @Caching(
        cacheable = @Cacheable(value = "userStats", key = "#userId"),
        put = @CachePut(value = "userLastAccess", key = "#userId")
    )
    public UserStats getUserStats(Long userId) {
        log.info("获取用户统计信息,ID: {}", userId);
        
        // 模拟复杂统计计算
        UserStats stats = new UserStats();
        stats.setUserId(userId);
        stats.setLoginCount(100);
        stats.setLastLoginTime(LocalDateTime.now());
        stats.setTotalPoints(1500);
        
        return stats;
    }
}

// User.java - 用户实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User implements Serializable {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String email;
    
    private String nickname;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @PrePersist
    protected void onCreate() {
        createTime = LocalDateTime.now();
        updateTime = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void onUpdate() {
        updateTime = LocalDateTime.now();
    }
}

// UserStats.java - 用户统计实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserStats implements Serializable {
    private Long userId;
    private Integer loginCount;
    private LocalDateTime lastLoginTime;
    private Integer totalPoints;
}

缓存架构图示

🎨 缓存架构图示

                L1 Cache本地缓存
            →
                L2 CacheRedis缓存
            →
                Database数据库

多级缓存架构:本地缓存 → Redis缓存 → 数据库

提供更好的性能和可用性保障 缓存注解

📍 缓存注解

  • @Cacheable- 缓存查询结果
  • @CachePut- 更新缓存数据
  • @CacheEvict- 清除缓存数据
  • @Caching- 组合多个缓存操作
  • @CacheConfig- 类级别缓存配置
  • @EnableCaching- 启用缓存功能

缓存策略

📢 缓存策略

  • Cache-Aside- 旁路缓存模式
  • Read-Through- 读穿透模式
  • Write-Through- 写穿透模式
  • Write-Behind- 写回模式
  • Refresh-Ahead- 预刷新模式
  • TTL策略- 时间过期策略

自定义缓存管理

🔧 自定义缓存管理

// CustomCacheManager.java - 自定义缓存管理器
@Component
@Slf4j
public class CustomCacheManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置缓存
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        try {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
            log.debug("设置缓存成功 - key: {}, timeout: {}{}", key, timeout, unit);
        } catch (Exception e) {
            log.error("设置缓存失败 - key: {}", key, e);
        }
    }
    
    /**
     * 获取缓存
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> clazz) {
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                log.debug("缓存未命中 - key: {}", key);
                return null;
            }
            
            log.debug("缓存命中 - key: {}", key);
            return clazz.isInstance(value) ? (T) value : null;
        } catch (Exception e) {
            log.error("获取缓存失败 - key: {}", key, e);
            return null;
        }
    }
    
    /**
     * 删除缓存
     */
    public boolean delete(String key) {
        try {
            Boolean result = redisTemplate.delete(key);
            log.debug("删除缓存 - key: {}, result: {}", key, result);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("删除缓存失败 - key: {}", key, e);
            return false;
        }
    }
    
    /**
     * 批量删除缓存
     */
    public long deletePattern(String pattern) {
        try {
            Set<String> keys = redisTemplate.keys(pattern);
            if (keys != null && !keys.isEmpty()) {
                Long result = redisTemplate.delete(keys);
                log.debug("批量删除缓存 - pattern: {}, count: {}", pattern, result);
                return result != null ? result : 0;
            }
            return 0;
        } catch (Exception e) {
            log.error("批量删除缓存失败 - pattern: {}", pattern, e);
            return 0;
        }
    }
    
    /**
     * 检查缓存是否存在
     */
    public boolean exists(String key) {
        try {
            Boolean result = redisTemplate.hasKey(key);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("检查缓存存在性失败 - key: {}", key, e);
            return false;
        }
    }
    
    /**
     * 设置缓存过期时间
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        try {
            Boolean result = redisTemplate.expire(key, timeout, unit);
            log.debug("设置缓存过期时间 - key: {}, timeout: {}{}", key, timeout, unit);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("设置缓存过期时间失败 - key: {}", key, e);
            return false;
        }
    }
    
    /**
     * 获取缓存剩余过期时间
     */
    public long getExpire(String key, TimeUnit unit) {
        try {
            Long expire = redisTemplate.getExpire(key, unit);
            return expire != null ? expire : -1;
        } catch (Exception e) {
            log.error("获取缓存过期时间失败 - key: {}", key, e);
            return -1;
        }
    }
}

// CacheService.java - 缓存服务类
@Service
@Slf4j
public class CacheService {
    
    @Autowired
    private CustomCacheManager cacheManager;
    
    private static final String USER_CACHE_PREFIX = "user:";
    private static final String PRODUCT_CACHE_PREFIX = "product:";
    
    /**
     * 缓存用户信息
     */
    public void cacheUser(User user) {
        String key = USER_CACHE_PREFIX + user.getId();
        cacheManager.set(key, user, 30, TimeUnit.MINUTES);
    }
    
    /**
     * 获取缓存的用户信息
     */
    public User getCachedUser(Long userId) {
        String key = USER_CACHE_PREFIX + userId;
        return cacheManager.get(key, User.class);
    }
    
    /**
     * 清除用户缓存
     */
    public void evictUser(Long userId) {
        String key = USER_CACHE_PREFIX + userId;
        cacheManager.delete(key);
    }
    
    /**
     * 清除所有用户缓存
     */
    public void evictAllUsers() {
        String pattern = USER_CACHE_PREFIX + "*";
        long count = cacheManager.deletePattern(pattern);
        log.info("清除所有用户缓存,数量: {}", count);
    }
    
    /**
     * 缓存预热 - 预加载热点数据
     */
    @PostConstruct
    public void warmUpCache() {
        log.info("开始缓存预热...");
        
        // 预加载热点用户数据
        List<Long> hotUserIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);
        for (Long userId : hotUserIds) {
            try {
                // 这里应该从数据库加载用户数据
                User user = loadUserFromDatabase(userId);
                if (user != null) {
                    cacheUser(user);
                    log.debug("预热用户缓存: {}", userId);
                }
            } catch (Exception e) {
                log.error("预热用户缓存失败: {}", userId, e);
            }
        }
        
        log.info("缓存预热完成");
    }
    
    /**
     * 从数据库加载用户数据(模拟)
     */
    private User loadUserFromDatabase(Long userId) {
        // 模拟数据库查询
        User user = new User();
        user.setId(userId);
        user.setUsername("user" + userId);
        user.setEmail("user" + userId + "@example.com");
        user.setCreateTime(LocalDateTime.now());
        return user;
    }
}

缓存问题解决方案

🛡️ 缓存问题解决方案

// CacheProblemSolver.java - 缓存问题解决方案
@Service
@Slf4j
public class CacheProblemSolver {
    
    @Autowired
    private CustomCacheManager cacheManager;
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 解决缓存穿透问题
     * 使用布隆过滤器或缓存空值
     */
    public User getUserWithBloomFilter(Long userId) {
        String key = "user:" + userId;
        
        // 1. 先检查缓存
        User cachedUser = cacheManager.get(key, User.class);
        if (cachedUser != null) {
            return cachedUser;
        }
        
        // 2. 检查布隆过滤器(这里简化为ID范围检查)
        if (userId <= 0 || userId > 1000000) {
            log.warn("用户ID超出范围,可能是恶意请求: {}", userId);
            // 缓存空值,防止重复查询
            cacheManager.set(key, new User(), 5, TimeUnit.MINUTES);
            return null;
        }
        
        // 3. 查询数据库
        User user = userRepository.findById(userId).orElse(null);
        
        // 4. 缓存结果(包括空值)
        if (user != null) {
            cacheManager.set(key, user, 30, TimeUnit.MINUTES);
        } else {
            // 缓存空值,设置较短过期时间
            cacheManager.set(key, new User(), 5, TimeUnit.MINUTES);
        }
        
        return user;
    }
    
    /**
     * 解决缓存雪崩问题
     * 使用随机过期时间
     */
    public User getUserWithRandomExpire(Long userId) {
        String key = "user:" + userId;
        
        User cachedUser = cacheManager.get(key, User.class);
        if (cachedUser != null) {
            return cachedUser;
        }
        
        User user = userRepository.findById(userId).orElse(null);
        if (user != null) {
            // 设置随机过期时间,避免同时失效
            int randomMinutes = 30 + new Random().nextInt(30); // 30-60分钟
            cacheManager.set(key, user, randomMinutes, TimeUnit.MINUTES);
        }
        
        return user;
    }
    
    /**
     * 解决缓存击穿问题
     * 使用分布式锁
     */
    public User getUserWithDistributedLock(Long userId) {
        String key = "user:" + userId;
        String lockKey = "lock:user:" + userId;
        
        // 1. 先检查缓存
        User cachedUser = cacheManager.get(key, User.class);
        if (cachedUser != null) {
            return cachedUser;
        }
        
        // 2. 尝试获取分布式锁
        boolean lockAcquired = acquireDistributedLock(lockKey, 10, TimeUnit.SECONDS);
        if (!lockAcquired) {
            // 获取锁失败,等待一段时间后重试
            try {
                Thread.sleep(100);
                return cacheManager.get(key, User.class);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        
        try {
            // 3. 双重检查缓存
            cachedUser = cacheManager.get(key, User.class);
            if (cachedUser != null) {
                return cachedUser;
            }
            
            // 4. 查询数据库
            User user = userRepository.findById(userId).orElse(null);
            
            // 5. 更新缓存
            if (user != null) {
                cacheManager.set(key, user, 30, TimeUnit.MINUTES);
            }
            
            return user;
        } finally {
            // 6. 释放分布式锁
            releaseDistributedLock(lockKey);
        }
    }
    
    /**
     * 获取分布式锁(简化实现)
     */
    private boolean acquireDistributedLock(String lockKey, long timeout, TimeUnit unit) {
        try {
            String lockValue = UUID.randomUUID().toString();
            Boolean result = cacheManager.redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, lockValue, timeout, unit);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("获取分布式锁失败: {}", lockKey, e);
            return false;
        }
    }
    
    /**
     * 释放分布式锁
     */
    private void releaseDistributedLock(String lockKey) {
        try {
            cacheManager.delete(lockKey);
        } catch (Exception e) {
            log.error("释放分布式锁失败: {}", lockKey, e);
        }
    }
    
    /**
     * 缓存更新策略 - 延时双删
     */
    @Transactional
    public User updateUserWithDelayedDoubleDelete(User user) {
        String key = "user:" + user.getId();
        
        // 1. 删除缓存
        cacheManager.delete(key);
        
        // 2. 更新数据库
        User updatedUser = userRepository.save(user);
        
        // 3. 延时删除缓存(异步执行)
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(1000); // 延时1秒
                cacheManager.delete(key);
                log.debug("延时双删完成: {}", key);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("延时双删被中断: {}", key);
            }
        });
        
        return updatedUser;
    }
}

缓存对比表

📊 缓存方案对比

缓存类型 适用场景 优点 缺点
本地缓存 单机应用、读多写少 速度快、无网络开销 不支持分布式、内存限制
Redis缓存 分布式应用、高并发 支持分布式、功能丰富 网络开销、单点故障
多级缓存 高性能要求、大规模应用 性能最优、可用性高 复杂度高、一致性难保证
CDN缓存 静态资源、全球分发 就近访问、减轻服务器压力 只适用静态内容

最佳实践

💡 缓存最佳实践

🎯 设计原则合理设置过期时间- 根据数据特性设置TTL避免缓存穿透- 使用布隆过滤器或缓存空值防止缓存雪崩- 设置随机过期时间解决缓存击穿- 使用分布式锁或互斥锁保证数据一致性- 选择合适的缓存更新策略

// CacheMonitor.java - 缓存监控
@Component
@Slf4j
public class CacheMonitor {
    
    @Autowired
    private CacheManager cacheManager;
    
    /**
     * 缓存统计信息
     */
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void logCacheStats() {
        if (cacheManager instanceof RedisCacheManager) {
            Collection<String> cacheNames = cacheManager.getCacheNames();
            
            for (String cacheName : cacheNames) {
                Cache cache = cacheManager.getCache(cacheName);
                if (cache instanceof RedisCache) {
                    log.info("缓存统计 - 缓存名: {}", cacheName);
                    // 这里可以添加更详细的统计信息
                }
            }
        }
    }
    
    /**
     * 缓存健康检查
     */
    @EventListener
    public void handleCacheEvent(CacheEvent event) {
        log.debug("缓存事件: {}", event);
    }
}

// 缓存配置建议
@Configuration
public class CacheOptimizationConfig {
    
    /**
     * 配置缓存键生成器
     */
    @Bean
    public KeyGenerator customKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName()).append(".");
            sb.append(method.getName()).append("(");
            for (Object param : params) {
                sb.append(param.toString()).append(",");
            }
            if (params.length > 0) {
                sb.setLength(sb.length() - 1);
            }
            sb.append(")");
            return sb.toString();
        };
    }
    
    /**
     * 配置缓存错误处理器
     */
    @Bean
    public CacheErrorHandler cacheErrorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, 
                                          Cache cache, Object key) {
                log.error("缓存获取错误 - cache: {}, key: {}", 
                    cache.getName(), key, exception);
            }
            
            @Override
            public void handleCachePutError(RuntimeException exception, 
                                          Cache cache, Object key, Object value) {
                log.error("缓存存储错误 - cache: {}, key: {}", 
                    cache.getName(), key, exception);
            }
            
            @Override
            public void handleCacheEvictError(RuntimeException exception, 
                                            Cache cache, Object key) {
                log.error("缓存清除错误 - cache: {}, key: {}", 
                    cache.getName(), key, exception);
            }
            
            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("缓存清空错误 - cache: {}", cache.getName(), exception);
            }
        };
    }
}

下一章节

🎉 恭喜完成第15章学习!

你已经掌握了SpringBoot Cache的核心技能,接下来学习应用启动和运行时管理! 🚀 下一章:SpringBoot Runtime 🏠 返回课程首页

🚀 SpringBoot Cache
http://www.clxhxhhr.top/posts/2883/
作者
clxstart
发布于
2026-09-19
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。