个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力。
使用缓存的同时,也会增加代码复杂度和运营的成本。
缓存的使用案例:
缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码(例如:
// 例1:本地用于高并发
Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>();
//例2:用于redis等缓存
static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build();
//例3:本地缓存
Static final Map<K,V> map = new HashMap();
由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被final修饰,所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的,不能改变,因此不用担心赋值(=)导致缓存失效;
Redis缓存作用模型
:
标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。
为查询的数据添加缓存 业务逻辑
:
@Resource
private StringRedisTemplate stringRedisTemplate;
// 根据id查询商铺信息
@Override
public Result queryById(Long id) {
// redis缓存的key
String key = RedisConstants.CACHE_SHOP_KEY + id;
//1. 从redis缓存中获取shop信息
String shopJSON = stringRedisTemplate.opsForValue().get(key);
//2. 缓存存在,返回(Hutool工具:StrUtil、JSONUtil)
if(StrUtil.isNotBlank(shopJSON)){
Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
return Result.ok(shop);
}
//3. 缓存未命中,从数据库中获取
Shop shop = this.getById(id);
//4. 数据库中不存在,返回错误
if(shop == null) return Result.fail("店铺不存在!");
//5. 数据库中存在,存入redis缓存(Hutool工具:JSONUtil)
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
//6. 返回
return Result.ok(shop);
}
双写问题
:
双写问题通常出现在以下场景:
在这个过程中,如果写入数据源成功而写入缓存失败,或者写入缓存成功而写入数据源失败,就会导致数据不一致的情况。例如:
解决方案
:
Cache Aside Pattern
人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案
Read/Write Through Pattern
: 缓存与数据库整合为一个服务,用服务来维护一致性。调用者调用该服务,无需关系缓存一致性问题。即:由系统本身完成,数据库与缓存的问题交由系统本身去处理
Write Behind Caching Pattern
:调用者只操作缓存,其他线程去异步处理数据库,实现最终一致
使用Cache Aside Pattern人工编码方式,需要注意的问题
:
√
)√
)流程
:
查询数据时
:
// 根据id查询商铺信息
@Override
public Result queryById(Long id) {
// redis缓存的key
String key = "cache:shop:" + id;
//1. 从redis缓存中获取shop信息
String shopJSON = stringRedisTemplate.opsForValue().get(key);
//2. 缓存存在,返回
if(StrUtil.isNotBlank(shopJSON)){
Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
return Result.ok(shop);
}
//3. 缓存未命中,从数据库中获取
Shop shop = this.getById(id);
//4. 数据库中不存在,返回错误
if(shop == null) return Result.fail("店铺不存在!");
//5. 数据库中存在,存入redis缓存,并设置过期时间ttl
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
//6. 返回
return Result.ok(shop);
}
查询数据时(解决缓存穿透):
// 根据id查询商铺信息(缓存空值,避免缓存穿透问题)
@Override
public Result queryById(Long id) {
// redis缓存的key
String key = RedisConstants.CACHE_SHOP_KEY + id;
//1. 从redis缓存中获取shop信息
String shopJSON = stringRedisTemplate.opsForValue().get(key);
if(shopJSON == null){ // 获取值为空,返回错误
return Result.fail("商铺不存在!");
}
//2. 缓存存在,返回
if(StrUtil.isNotBlank(shopJSON)){
Shop shop = JSONUtil.toBean(shopJSON, Shop.class);
return Result.ok(shop);
}
//3. 缓存未命中,从数据库中获取
Shop shop = this.getById(id);
//4. 数据库中不存在,空值写入Redis,返回错误
if(shop == null){
// 控制写入Redis,设置2分钟有效期
stringRedisTemplate.opsForValue().set(key, "", 2L, TimeUnit.MINUTES);
//返回错误
return Result.fail("商铺不存在!");
}
//5. 数据库中存在,存入redis缓存
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
//6. 返回
return Result.ok(shop);
}
修改数据时
:
@Override
@Transactional //开启事务,保证证缓存与数据库操作同时成功或失败
public Result update(Shop shop) {
Long id = shop.getId();
if(id == null) return Result.fail("商铺ID不能为空!");
//注意: 先更新数据库再删除缓存
//1. 更新数据库
this.updateById(shop);
//2. 删除缓存
stringRedisTemplate.delete("cache:shop:" + id);
return Result.ok();
}