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. 性能优化建议
- 合理设置缓存大小和过期时间
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();
}
}
- 使用批量操作提高性能
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;
}
}
- 使用管道提高性能
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 缓存的各个方面,从基础配置到高级特性,以及性能优化和安全考虑。你可以根据具体项目需求选择合适的部分进行使用和调整。