Spring Boot Redis 缓存完全指南

Spring Boot Redis 缓存完全指南

1. 项目依赖配置

1.1 Maven依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Spring Boot Cache -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <!-- Apache Commons Pool2 - Redis连接池需要 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>

    <!-- FastJson - 用于Redis序列化 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>

    <!-- Lombok - 简化代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

1.2 环境配置文件

application.yml 不同环境配置示例:

yaml 复制代码
spring:
  # 开发环境配置
  profiles: dev
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 10000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
      shutdown-timeout: 100ms

---
# 生产环境配置
spring:
  profiles: prod
  redis:
    host: redis.prod.company.com
    port: 6379
    password: ${REDIS_PASSWORD}  # 使用环境变量
    database: 0
    timeout: 10000ms
    lettuce:
      pool:
        max-active: 32
        max-wait: 3000ms
        max-idle: 16
        min-idle: 8
      shutdown-timeout: 100ms

2. Redis配置类详解

2.1 基础配置类

java 复制代码
@Configuration
@EnableCaching
@Slf4j
public class RedisConfiguration {
    
    /**
     * Redis连接工厂配置
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
        LettuceConnectionFactory factory = new LettuceConnectionFactory();
        factory.setHostName(redisProperties.getHost());
        factory.setPort(redisProperties.getPort());
        factory.setPassword(redisProperties.getPassword());
        factory.setDatabase(redisProperties.getDatabase());
        return factory;
    }

    /**
     * FastJson序列化器配置
     */
    @Bean
    public FastJsonRedisSerializer<Object> fastJsonRedisSerializer() {
        FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
        FastJsonConfig config = new FastJsonConfig();
        // 序列化规则配置
        config.setSerializerFeatures(
            SerializerFeature.WriteClassName,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.PrettyFormat,
            SerializerFeature.WriteNullListAsEmpty,
            SerializerFeature.WriteNullStringAsEmpty
        );
        serializer.setFastJsonConfig(config);
        return serializer;
    }
}

2.2 缓存管理器配置

java 复制代码
@Configuration
public class CacheManagerConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, 
                                        FastJsonRedisSerializer<Object> fastJsonRedisSerializer) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))  // 默认过期时间
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer))
                .disableCachingNullValues();

        // 特定缓存空间配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        
        // 短期缓存配置 - 适用于频繁更新的数据
        configMap.put("shortCache", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(5))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer))
                .disableCachingNullValues());
        
        // 长期缓存配置 - 适用于不经常更新的数据
        configMap.put("longCache", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(12))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer))
                .disableCachingNullValues());
        
        // 永久缓存配置 - 适用于基础数据
        configMap.put("permanentCache", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ZERO)  // 永不过期
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(RedisSerializer.string()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer))
                .disableCachingNullValues());

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(defaultConfig)
                .withInitialCacheConfigurations(configMap)
                .build();
    }
}

3. 缓存注解详细使用指南

3.1 @Cacheable 详解

java 复制代码
public class CacheableExample {

    /**
     * 基本使用
     * value: 缓存空间名称
     * key: 缓存键,使用SpEL表达式
     * condition: 缓存条件
     * unless: 否定缓存条件
     */
    @Cacheable(
        value = "userCache",
        key = "#id",
        condition = "#id != null",
        unless = "#result == null"
    )
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }

    /**
     * 复杂key示例
     * 组合多个参数作为key
     */
    @Cacheable(
        value = "userCache",
        key = "#root.targetClass.simpleName + ':' + #name + ':' + #age"
    )
    public List<User> getUsersByNameAndAge(String name, Integer age) {
        return userMapper.findByNameAndAge(name, age);
    }

    /**
     * 多缓存空间示例
     */
    @Cacheable(cacheNames = {"shortCache", "backupCache"})
    public User getUserWithMultiCache(Long id) {
        return userMapper.selectById(id);
    }
}

3.2 @CachePut 详解

java 复制代码
public class CachePutExample {

    /**
     * 更新缓存示例
     * 总是执行方法,并将结果更新到缓存
     */
    @CachePut(
        value = "userCache",
        key = "#user.id",
        condition = "#user.age > 18"
    )
    public User updateUser(User user) {
        userMapper.updateById(user);
        return user;
    }

    /**
     * 批量更新缓存示例
     */
    @CachePut(
        value = "userCache",
        key = "#user.id + ':' + #user.version"
    )
    public List<User> batchUpdateUsers(List<User> users) {
        userMapper.batchUpdate(users);
        return users;
    }
}

3.3 @CacheEvict 详解

java 复制代码
public class CacheEvictExample {

    /**
     * 删除指定缓存
     */
    @CacheEvict(
        value = "userCache",
        key = "#id"
    )
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
    }

    /**
     * 批量删除缓存
     * allEntries = true 表示清除所有缓存
     * beforeInvocation = true 表示在方法执行前清除缓存
     */
    @CacheEvict(
        value = "userCache",
        allEntries = true,
        beforeInvocation = true
    )
    public void clearAllUserCache() {
        log.info("清除所有用户缓存");
    }

    /**
     * 多缓存空间清除
     */
    @Caching(evict = {
        @CacheEvict(value = "userCache", key = "#user.id"),
        @CacheEvict(value = "roleCache", key = "#user.roleId"),
        @CacheEvict(value = "permissionCache", key = "#user.id")
    })
    public void deleteUserAndRelatedCache(User user) {
        userMapper.deleteById(user.getId());
    }
}

4. 实战场景示例

4.1 统计数据缓存

java 复制代码
@Service
@Slf4j
public class StatisticsServiceImpl implements StatisticsService {

    /**
     * 政治面貌统计
     * 使用短期缓存,因为统计数据会定期更新
     */
    @Cacheable(
        value = "statisticsCache",
        key = "'political_stats_' + #region",
        unless = "#result == null || #result.isEmpty()"
    )
    public Map<String, Object> getPoliticalStats(String region) {
        log.info("计算{}地区政治面貌统计数据", region);
        return statisticsMapper.calculatePoliticalStats(region);
    }

    /**
     * 定时更新缓存
     */
    @Scheduled(cron = "0 0 1 * * ?")  // 每天凌晨1点执行
    @CacheEvict(value = "statisticsCache", allEntries = true)
    public void refreshStatisticsCache() {
        log.info("刷新统计数据缓存");
    }
}

4.2 多级缓存示例

java 复制代码
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 多级缓存实现
     * 1. 先查本地缓存
     * 2. 再查Redis缓存
     * 3. 最后查数据库
     */
    @Cacheable(
        value = "userCache",
        key = "#id",
        unless = "#result == null"
    )
    public User getUserWithMultiLevelCache(Long id) {
        // 1. 查询本地缓存(使用Caffeine实现)
        User user = localCache.getIfPresent(id);
        if (user != null) {
            log.debug("从本地缓存获取用户: {}", id);
            return user;
        }

        // 2. 查询Redis缓存
        String redisKey = "user:" + id;
        user = (User) redisTemplate.opsForValue().get(redisKey);
        if (user != null) {
            log.debug("从Redis缓存获取用户: {}", id);
            // 放入本地缓存
            localCache.put(id, user);
            return user;
        }

        // 3. 查询数据库
        user = userMapper.selectById(id);
        if (user != null) {
            log.debug("从数据库获取用户: {}", id);
            // 放入本地缓存
            localCache.put(id, user);
            // 放入Redis缓存
            redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES);
        }

        return user;
    }
}

4.3 分布式锁与缓存结合

java 复制代码
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 使用分布式锁防止缓存击穿
     */
    @Cacheable(
        value = "orderCache",
        key = "#orderId",
        unless = "#result == null"
    )
    public Order getOrderWithLock(String orderId) {
        String lockKey = "lock:order:" + orderId;
        boolean locked = false;
        try {
            // 尝试获取分布式锁
            locked = redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, "LOCKED", 10, TimeUnit.SECONDS);
            if (!locked) {
                // 未获取到锁,等待后重试
                Thread.sleep(100);
                return getOrderWithLock(orderId);
            }

            // 获取到锁,执行业务逻辑
            Order order = orderMapper.selectById(orderId);
            if (order != null) {
                // 添加缓存
                redisTemplate.opsForValue()
                        .set("order:" + orderId, order, 30, TimeUnit.MINUTES);
            }
            return order;
        } catch (InterruptedException e) {
            log.error("获取订单信息异常", e);
            Thread.currentThread().interrupt();
            return null;
        } finally {
            // 释放锁
            if (locked) {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

5. 缓存监控与维护

5.1 缓存监控配置

java 复制代码
@Configuration
public class CacheMonitorConfig {

    @Bean
    public CacheMetricsCollector cacheMetricsCollector(CacheManager cacheManager) {
        CacheMetricsCollector collector = new CacheMetricsCollector();
        
        // 注册缓存指标
        collector.bindCacheManager(cacheManager);
        
        return collector;
    }
}

5.2 自定义缓存监听器

java 复制代码
@Component
public class CustomCacheEventListener extends CacheEventListenerAdapter {
    
    @Override
    public void onEvent(CacheEvent event) {
        log.info("Cache event {} for key {} in cache {}", 
                 event.getType(), 
                 event.getKey(), 
                 event.getCache().getName());
    }
}

5.3 缓存统计

java 复制代码
@Component
@Slf4j
public class CacheStats {

    @Autowired
    private CacheManager cacheManager;

    /**
     * 收集缓存统计信息
     */
    @Scheduled(fixedRate = 300000) // 每5分钟执行一次
    public void collectCacheStats() {
        Map<String, Map<String, Object>> stats = new HashMap<>();
        
        cacheManager.getCacheNames().forEach(cacheName -> {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache instanceof RedisCache) {
                RedisCache redisCache = (RedisCache) cache;
                Map<String, Object> cacheStats = new HashMap<>();
                cacheStats.put("size", redisCache.estimatedSize());
                cacheStats.put("hitCount", redisCache.getStats().getHitCount());
                cacheStats.put("missCount", redisCache.getStats().getMissCount());
                stats.put(cacheName, cacheStats);
            }
        });

        log.info("Cache statistics: {}", stats);
    }
}

6. 最佳实践与注意事项

6.1 缓存键设计规范

java 复制代码
public class CacheKeyConstants {
    
    // 使用常量定义缓存键前缀
    public static final String USER_CACHE_PREFIX = "user:";
    public static final String ORDER_CACHE_PREFIX = "order:";
    public static final String PRODUCT_CACHE_PREFIX = "product:";

    // 组合缓存键
    public static String getUserCacheKey(Long userId) {
        return USER_CACHE_PREFIX + userId;
    }

    public static String getOrderCacheKey(String orderId, String type) {
        return String.format("%s:%s:%s", ORDER_CACHE_PREFIX, type, orderId);
    }
}

6.2 缓存异常处理

java 复制代码
@Aspect
@Component
@Slf4j
public class CacheErrorHandler {

    @Around("@annotation(org.springframework.cache.annotation.Cacheable)")
    public Object handleCacheError(ProceedingJoinPoint pjp) {
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            log.error("Cache operation failed", e);
            // 降级处理:直接访问数据库
            try {
                return pjp.proceed();
            } catch (Throwable ex) {
                log.error("Database operation also failed", ex);
                throw new RuntimeException("Service unavailable", ex);
            }
        }
    }
}

6.3 缓存预热

java 复制代码
@Component
public class CacheWarmer implements ApplicationRunner {

    @Autowired
    private UserService userService;

    @Override
    public void run(ApplicationArguments args) {
        log.info("开始预热缓存...");
        
        // 预热重要的用户数据
        List<Long> importantUserIds = userService.getImportantUserIds();
        importantUserIds.forEach(userId -> {
            try {
                userService.getUserById(userId);
                log.debug("预热用户缓存: {}", userId);
            } catch (Exception e) {
                log.error("预热用户缓存失败: {}", userId, e);
            }
        });

        log.info("缓存预热完成");
    }
}

7. 性能优化建议

  1. 合理设置缓存大小和过期时间
java 复制代码
@Configuration
public class CacheConfig {
    
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        // 根据实际业务需求设置缓存配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        
        // 高频访问数据 - 短期缓存
        configMap.put("highFrequency", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(5))
                .prefixCacheNameWith("hf:")
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer)));
                        
        // 低频访问数据 - 长期缓存
        configMap.put("lowFrequency", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(24))
                .prefixCacheNameWith("lf:")
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(fastJsonRedisSerializer)));
                        
        return RedisCacheManager.builder(factory)
                .withInitialCacheConfigurations(configMap)
                .build();
    }
}
  1. 使用批量操作提高性能
java 复制代码
@Service
public class BatchOperationExample {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 批量获取用户信息
     */
    public List<User> batchGetUsers(List<Long> userIds) {
        List<String> keys = userIds.stream()
                .map(id -> "user:" + id)
                .collect(Collectors.toList());
                
        // 批量获取缓存
        List<Object> cachedUsers = redisTemplate.opsForValue().multiGet(keys);
        
        // 处理缓存未命中的情况
        List<Long> missedIds = new ArrayList<>();
        List<User> result = new ArrayList<>();
        
        for (int i = 0; i < userIds.size(); i++) {
            if (cachedUsers.get(i) != null) {
                result.add((User) cachedUsers.get(i));
            } else {
                missedIds.add(userIds.get(i));
            }
        }
        
        if (!missedIds.isEmpty()) {
            // 批量查询数据库
            List<User> dbUsers = userMapper.batchSelect(missedIds);
            
            // 批量写入缓存
            Map<String, User> userMap = new HashMap<>();
            dbUsers.forEach(user -> 
                userMap.put("user:" + user.getId(), user));
            redisTemplate.opsForValue().multiSet(userMap);
            
            result.addAll(dbUsers);
        }
        
        return result;
    }
}
  1. 使用管道提高性能
java 复制代码
@Service
public class PipelineExample {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 使用管道批量操作
     */
    public void batchOperationWithPipeline(List<User> users) {
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (User user : users) {
                byte[] key = redisTemplate.getKeySerializer()
                        .serialize("user:" + user.getId());
                byte[] value = redisTemplate.getValueSerializer()
                        .serialize(user);
                connection.set(key, value);
                connection.expire(key, 1800); // 30分钟过期
            }
            return null;
        });
    }
}

8. 缓存安全

8.1 防止缓存穿透

java 复制代码
@Service
public class CachePenetrationProtection {

    /**
     * 使用布隆过滤器防止缓存穿透
     */
    @Cacheable(
        value = "userCache",
        key = "#id",
        unless = "#result == null"
    )
    public User getUserWithBloomFilter(Long id) {
        // 先检查布隆过滤器
        if (!bloomFilter.mightContain(id)) {
            return null;
        }
        
        // 查询缓存
        User user = (User) redisTemplate.opsForValue().get("user:" + id);
        if (user != null) {
            return user;
        }
        
        // 查询数据库
        user = userMapper.selectById(id);
        if (user != null) {
            redisTemplate.opsForValue().set("user:" + id, user, 30, TimeUnit.MINUTES);
        } else {
            // 防止缓存穿透,缓存空值
            redisTemplate.opsForValue().set("user:" + id, NULL_VALUE, 5, TimeUnit.MINUTES);
        }
        
        return user;
    }
}

8.2 防止缓存击穿

java 复制代码
@Service
public class CacheBreakdownProtection {

    /**
     * 使用互斥锁防止缓存击穿
     */
    public User getUserWithMutex(Long id) {
        String cacheKey = "user:" + id;
        String lockKey = "lock:" + cacheKey;
        
        // 查询缓存
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        if (user != null) {
            return user;
        }
        
        // 获取互斥锁
        boolean locked = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (!locked) {
            // 获取锁失败,等待后重试
            try {
                Thread.sleep(100);
                return getUserWithMutex(id);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("获取用户信息失败", e);
            }
        }
        
        try {
            // 双重检查
            user = (User) redisTemplate.opsForValue().get(cacheKey);
            if (user != null) {
                return user;
            }
            
            // 查询数据库
            user = userMapper.selectById(id);
            if (user != null) {
                redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
            }
            return user;
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

8.3 防止缓存雪崩

java 复制代码
@Service
public class CacheAvalancheProtection {

    /**
     * 使用随机过期时间防止缓存雪崩
     */
    @Cacheable(
        value = "userCache",
        key = "#id",
        unless = "#result == null"
    )
    public User getUserWithRandomExpiry(Long id) {
        User user = userMapper.selectById(id);
        if (user != null) {
            // 基础过期时间30分钟,增加随机值(0-5分钟)
            long randomExpiry = 30 + new Random().nextInt(5);
            redisTemplate.opsForValue().set(
                "user:" + id, 
                user, 
                randomExpiry, 
                TimeUnit.MINUTES
            );
        }
        return user;
    }
}

9. 缓存测试

9.1 单元测试

java 复制代码
@SpringBootTest
public class CacheTest {

    @Autowired
    private UserService userService;
    
    @Autowired
    private CacheManager cacheManager;

    @Test
    public void testUserCache() {
        // 第一次调用,应该查询数据库
        User user1 = userService.getUserById(1L);
        assertNotNull(user1);
        
        // 第二次调用,应该从缓存获取
        User user2 = userService.getUserById(1L);
        assertNotNull(user2);
        assertEquals(user1, user2);
        
        // 验证缓存中的数据
        Cache cache = cacheManager.getCache("userCache");
        assertNotNull(cache);
        assertNotNull(cache.get("user:1"));
    }
}

9.2 性能测试

java 复制代码
@SpringBootTest
public class CachePerformanceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testCachePerformance() {
        int iterations = 1000;
        long startTime = System.currentTimeMillis();
        
        // 预热缓存
        User user = userService.getUserById(1L);
        assertNotNull(user);
        
        // 测试缓存读取性能
        for (int i = 0; i < iterations; i++) {
            user = userService.getUserById(1L);
            assertNotNull(user);
        }
        
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        
        log.info("平均响应时间: {}ms", duration / (double) iterations);
    }
}

这个详细的教程涵盖了 Spring Boot Redis 缓存的各个方面,从基础配置到高级特性,以及性能优化和安全考虑。你可以根据具体项目需求选择合适的部分进行使用和调整。

相关推荐
人活一口气8 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
Java陈序员1 天前
企业级!一个基于 Java 开发的开源 AI 应用开发平台!
spring boot·agent·mcp
杨运交1 天前
[041][公共模块]分布式唯一ID生成器设计与实现:一款灵活可扩展的雪花算法框架
spring boot
用户3074596982072 天前
Redis 延时队列详解
redis
烤代码的吐司君2 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
Flittly2 天前
【AgentScope Java新手村系列】(14)人机交互
java·spring boot·spring
Flynt3 天前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
掉鱼的猫4 天前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·spring boot
leeyi4 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
人活一口气5 天前
Spring Boot与AIGC的完美结合:从零搭建智能内容生成平台
java·spring boot·aigc