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

Spring Boot Redis 缓存完全指南

Spring Boot Redis 缓存完全指南

1. 项目依赖配置

1.1 Maven依赖

<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 不同环境配置示例:

spring:# 开发环境配置profiles: devredis:host: localhostport: 6379password: database: 0timeout: 10000mslettuce:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0shutdown-timeout: 100ms---
# 生产环境配置
spring:profiles: prodredis:host: redis.prod.company.comport: 6379password: ${REDIS_PASSWORD}  # 使用环境变量database: 0timeout: 10000mslettuce:pool:max-active: 32max-wait: 3000msmax-idle: 16min-idle: 8shutdown-timeout: 100ms

2. Redis配置类详解

2.1 基础配置类

@Configuration
@EnableCaching
@Slf4j
public class RedisConfiguration {/*** Redis连接工厂配置*/@Beanpublic 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序列化器配置*/@Beanpublic 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 缓存管理器配置

@Configuration
public class CacheManagerConfig {@Beanpublic 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 详解

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 详解

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 详解

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 统计数据缓存

@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 多级缓存示例

@Service
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate 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 分布式锁与缓存结合

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Autowiredprivate 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 缓存监控配置

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

5.2 自定义缓存监听器

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

5.3 缓存统计

@Component
@Slf4j
public class CacheStats {@Autowiredprivate 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 缓存键设计规范

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 缓存异常处理

@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 缓存预热

@Component
public class CacheWarmer implements ApplicationRunner {@Autowiredprivate UserService userService;@Overridepublic 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. 合理设置缓存大小和过期时间
@Configuration
public class CacheConfig {@Beanpublic 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. 使用批量操作提高性能
@Service
public class BatchOperationExample {@Autowiredprivate 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. 使用管道提高性能
@Service
public class PipelineExample {@Autowiredprivate 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 防止缓存穿透

@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 防止缓存击穿

@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 防止缓存雪崩

@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 单元测试

@SpringBootTest
public class CacheTest {@Autowiredprivate UserService userService;@Autowiredprivate CacheManager cacheManager;@Testpublic 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 性能测试

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

http://www.lryc.cn/news/613995.html

相关文章:

  • 破解 Django N+1 查询困境:使用 select_related 与 prefetch_related 实践指南
  • sqlite的sql语法与技术架构研究
  • http请求响应
  • npm run 常见脚本
  • token过期为了保证安全,refresh token不过期,那么拿到refresh token就可以获取token,不还是不安全吗
  • C/C++与JavaScript的WebAssembly协作开发指南
  • 【科研绘图系列】R语言绘制气泡图
  • 【优选算法】多源BFS
  • CALL与 RET指令及C#抽象函数和虚函数执行过程解析
  • 【代码随想录day 14】 力扣 111.二叉树的最小深度
  • 集成电路学习:什么是URDF统一机器人描述格式
  • Spring MVC 父子容器深度解析:原理、实战与优化
  • Pytest项目_day09(skip、skipif跳过)
  • iOS 签名证书全流程详解,申请、管理与上架实战
  • 三方相机问题分析七:【datespace导致GPU异常】facebook 黑块和Instagram花图问题
  • 【性能测试】-2- JMeter工具的使用
  • 网吧在线选座系统|基于java和小程序的网吧在线选座小程序系统设计与实现(源码+数据库+文档)
  • 【Jmeter】设置线程组运行顺序的方法
  • Baumer相机如何通过YoloV8深度学习模型实现危险区域人员的实时检测识别(C#代码UI界面版)
  • 利用千眼狼sCMOS相机开展冷离子云成像与测量实验
  • 平板探测器的主要技术指标
  • Spring Boot 优雅配置InfluxDB3客户端指南:@Configuration + @Bean + yml实战
  • C# 异步编程(GUI程序中的异步操作)
  • 从浅拷贝到深拷贝:C++赋值运算符重载的核心技术
  • 【设计模式】抽象工厂模式 (工具(Kit)模式)
  • 【接口自动化】-2- request模块及通过变量实现接口关联
  • 瑞利杂波背景下不同环境的虚警概率与目标检测概率仿真
  • 项目历程—右键菜单(问题,解决,拓展(非教学向,因为乱))
  • django uwsgi启动报错failed to get the Python codec of the filesystem encoding
  • 17.14 CogVLM-17B多模态模型爆肝部署:4-bit量化+1120px高清输入,A100实战避坑指南