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 缓存的各个方面,从基础配置到高级特性,以及性能优化和安全考虑。你可以根据具体项目需求选择合适的部分进行使用和调整。

相关推荐
还是大剑师兰特1 小时前
Redis面试题及详细答案100道(01-15) --- 基础认知篇
redis·大剑师·redis面试
一只叫煤球的猫2 小时前
⚠️ 不是危言耸听,SpringBoot正在毁掉Java工程师
java·spring boot·spring
麦兜*4 小时前
LangChain4j终极指南:Spring Boot构建企业级Agent框架
java·spring boot·spring·spring cloud·ai·langchain·ai编程
hrrrrb6 小时前
【Spring Boot 快速入门】八、登录认证(二)统一拦截
hive·spring boot·后端
你我约定有三8 小时前
云服务器--阿里云OSS(2)【Springboot使用阿里云OSS】
服务器·spring boot·阿里云
cyhysr9 小时前
redis8.0.3部署于mac
redis·macos
苹果醋39 小时前
React Native jpush-react-native极光推送 iOS生产环境接收不到推送
java·运维·spring boot·mysql·nginx
独泪了无痕10 小时前
Hutool-RedisDS:简化Redis操作的Java工具类
数据库·redis
sssnnndddfff10 小时前
Redis原理,命令,协议以及异步方式
数据库·redis·缓存