How to integrate Redis in Spring applications? Design and implementation of caching strategies?
考察点:缓存策略和Redis集成。
答案:
Redis是高性能的内存数据库,在Spring应用中主要用作缓存、会话存储和消息队列。Spring Boot提供了便捷的Redis集成支持,通过Spring Cache抽象和RedisTemplate可以轻松实现缓存功能。合理的缓存策略能显著提升应用性能,减少数据库访问压力,改善用户体验。
Spring Boot Redis集成:
-
依赖配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
-
Redis配置:
spring:
redis:
host: localhost
port: 6379
password: mypassword
database: 0
timeout: 2000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
cache:
type: redis
redis:
time-to-live: 600000
cache-null-values: false
-
Redis配置类:
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
Spring Cache注解使用:
-
基础缓存注解:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id).orElse(null);
}
@Cacheable(value = "users", key = "#username", condition = "#username != null")
public User getUserByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
}
}
-
复杂缓存策略:
@Service
public class ProductService {
@Cacheable(
value = "products",
key = "#root.methodName + ':' + #category + ':' + #page + ':' + #size",
unless = "#result.isEmpty()"
)
public List<Product> getProductsByCategory(String category, int page, int size) {
return productRepository.findByCategory(category, PageRequest.of(page, size)).getContent();
}
@Caching(
cacheable = @Cacheable(value = "product", key = "#id"),
put = @CachePut(value = "popularProducts", key = "#id")
)
public Product getProductWithStats(Long id) {
Product product = productRepository.findById(id).orElse(null);
if (product != null) {
product.incrementViewCount();
productRepository.save(product);
}
return product;
}
}
RedisTemplate直接操作:
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void setString(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public String getString(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
public void setHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public Object getHash(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
public void addToSet(String key, Object... values) {
redisTemplate.opsForSet().add(key, values);
}
public Set<Object> getSet(String key) {
return redisTemplate.opsForSet().members(key);
}
public void pushToList(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public List<Object> getList(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public boolean exists(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public void expire(String key, long timeout, TimeUnit unit) {
redisTemplate.expire(key, timeout, unit);
}
}
缓存策略设计:
-
多级缓存策略:
@Service
public class MultiLevelCacheService {
private final Map<String, Object> localCache = new ConcurrentHashMap<>();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
String key = "user:" + id;
User user = (User) localCache.get(key);
if (user != null) {
return user;
}
user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
localCache.put(key, user);
return user;
}
user = userRepository.findById(id).orElse(null);
if (user != null) {
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
localCache.put(key, user);
}
return user;
}
}
-
缓存预热策略:
@Component
public class CacheWarmupService {
@Autowired
private ProductService productService;
@EventListener(ApplicationReadyEvent.class)
public void warmupCache() {
List<String> hotCategories = Arrays.asList("electronics", "books", "clothing");
CompletableFuture.allOf(
hotCategories.stream()
.map(category -> CompletableFuture.runAsync(() ->
productService.getProductsByCategory(category, 0, 20)))
.toArray(CompletableFuture[]::new)
).join();
}
@Scheduled(cron = "0 0 2 * * ?")
public void refreshCache() {
}
}
缓存更新策略:
-
Cache-Aside模式:
@Service
public class OrderService {
public Order getOrder(Long orderId) {
String key = "order:" + orderId;
Order order = (Order) redisTemplate.opsForValue().get(key);
if (order != null) {
return order;
}
order = orderRepository.findById(orderId).orElse(null);
if (order != null) {
redisTemplate.opsForValue().set(key, order, 15, TimeUnit.MINUTES);
}
return order;
}
public Order updateOrder(Order order) {
Order savedOrder = orderRepository.save(order);
String key = "order:" + order.getId();
redisTemplate.delete(key);
return savedOrder;
}
}
-
Write-Through模式:
@Service
public class UserCacheService {
public User updateUser(User user) {
User savedUser = userRepository.save(user);
String key = "user:" + user.getId();
redisTemplate.opsForValue().set(key, savedUser, 30, TimeUnit.MINUTES);
return savedUser;
}
}
分布式锁实现:
@Service
public class DistributedLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean tryLock(String lockKey, String requestId, long expireTime) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
Boolean result = redisTemplate.execute(
new DefaultRedisScript<>(script, Boolean.class),
Collections.singletonList(lockKey),
requestId
);
return Boolean.TRUE.equals(result);
}
@Async
public void processWithLock(String lockKey, Runnable task) {
String requestId = UUID.randomUUID().toString();
if (tryLock(lockKey, requestId, 30000)) {
try {
task.run();
} finally {
unlock(lockKey, requestId);
}
}
}
}
实际应用最佳实践:
- 缓存键设计: 使用有意义的命名空间和版本号
- 过期策略: 根据数据特性设置合理的TTL
- 缓存穿透: 对空值进行缓存或使用布隆过滤器
- 缓存雪崩: 设置随机的过期时间避免同时失效
- 缓存击穿: 使用分布式锁防止热点数据重复查询
- 内存管理: 监控Redis内存使用,设置合适的淘汰策略
- 数据一致性: 在强一致性要求的场景慎用缓存