一,使用缓存的痛点
在使用缓存时,有一个通用的编程模式:
- 首先查询缓存,如果命中缓存,返回缓存中的结果
- 如果没有命中,则查询数据库,返回结果的同时将查询结果写入缓存
- 为了防止缓存击穿,需要加分布式锁以及查询结束后解锁
这个模式下有大量的模板代码,如果有大量需要使用缓存的方法,会导致大量的冗余,代码可读性差、可维护性差。
举例说明
在这种情况下,需要手动编写逻辑来处理缓存的读写和分布式锁。
用户服务类 (UserService)
@Service
public class UserService {
public User findUserById(Long id) throws InterruptedException {
// 尝试从 Redis 中获取用户
User user = (User) redisTemplate.opsForValue().get("user:" + id);
if (user == null) {
// 如果 Redis 中没有,则尝试获取分布式锁
String lockKey = "lock:user:" + id;
Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lockAcquired)) {
try {
// 在锁保护下再次检查缓存
user = (User) redisTemplate.opsForValue().get("user:" + id);
if (user == null) {
// 如果仍然没有,则查询数据库
user = userRepository.findById(id).orElse(null);
// 存储到 Redis 中
if (user != null) {
redisTemplate.opsForValue().set("user:" + id, user);
} else {
// 防止缓存穿透,存储空对象或特殊标记
redisTemplate.opsForValue().set("user:" + id, "not-found", 5, TimeUnit.MINUTES);
}
}
} finally {
// 释放锁
stringRedisTemplate.delete(lockKey);
}
} else {
// 如果未能获取锁,则等待一段时间后重试
Thread.sleep(1000);
return findUserById(id); // 递归调用
}
}
return user;
}
}
上面代码,有大量嵌套的if/else和try/catch,导致代码冗长,可读性差,可维护性差。
理想情况
理想情况下,利用注解来简化缓存逻辑,开发者只需要专注于读取数据库的实现。
用户服务类 (UserService)
@Service
public class UserService {
@Cacheable
public User findUserByIdWithLock(Long id) throws InterruptedException {
// 查询数据库
return userRepository.findById(id).orElse(null);
}
}
实际上,Spring3.1开始,提供了这样的解决方案Spring Cache。
二,Spring Cache
1,简介
- Spring 从 3.1 开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发 - Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现,如 RedisCache,EhCacheCache,ConcurrentMapCache 等 - 使用SpringCache后,每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。这写逻辑的实现完全由SpringCache完成,开发者只需要提供查询数据库的方法,并在方法上添加SpringCache的注解,这种开发方式大大简化了缓存的使用。
- 使用 Spring 缓存时我们需要关注以下两点
- 1,确定方法需要被缓存以及缓存策略
- 2,从缓存中读取之前缓存存储的数据