Redis数据缓存
缓存
一 缓存基础
1 缓存的概念和作用
缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高
2 缓存的使用
之前没有使用缓存是的模型
3 项目说明
当我们查询商家信息的时候,直接从mysql中获取的。现在我们将原来的项目改造。改造地方在ShopController,我们按照流程图去做,添加redis缓存,业务都是在service中实现的。
# 具体实现流程
1 redis中查询商户缓存
2 判断是否存在
3 存在直接返回
4 不存在根据id去数据库查询
5 数据库也不存在,返回错误
6 存在则写入redis中
7 返回
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version>
</dependency>
1 修改controller
/*** 根据id查询商铺信息* @param id 商铺id* @return 商铺详情数据
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryById(id);// return Result.ok(shopService.getById(id));
}
2 修改service
@Resource
private RedisTemplate<String,Object> redisTemplate;
@Override
public Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//4 不存在根据id去数据库查询Shop shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop));//7 返回return Result.ok(shop);
}
3 改造首页
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public Result queryTypeList() {
List<Object> list = redisTemplate.opsForList().range("cache.list",0,-1);if(list!=null && list.size()!=0){return Result.ok(list);}List<ShopType> sort = this.query().orderByAsc("sort").list();if(sort==null||sort.size()==0){return Result.fail("列表不存在");}sort.forEach(s->redisTemplate.opsForList().rightPush("cache.list",s));return Result.ok(sort);
}
二 数据一致性
1 思路
-
查询数据的时候,如果缓存未命中,则查询数据库,将数据写入缓存设置超时时间
-
修改数据时,先修改数据库,在删除缓存。
-
延时双删策略
2 代码实现
-
修改更新方法,添加超时时间
@Overridepublic Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//4 不存在根据id去数据库查询Shop shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);//7 返回return Result.ok(shop);}
-
修改ShopController
@PutMappingpublic Result updateShop(@RequestBody Shop shop) {// 写入数据库
//shopService.updateById(shop);//return Result.ok();return shopService.update(shop);}
-
修改service代码 延时双删策略
@Overridepublic Result update(Shop shop) {
Long id = shop.getId();if(id==null){return Result.fail("店铺id不存在");}// 删除缓存redisTemplate.delete("cache.shop:" + id);// 更新数据库updateById(shop);Thread.sleep(800);// 删除缓存redisTemplate.delete("cache.shop:" + id);return Result.ok();}
3 修改完代码以后,将所有的缓存删除,执行查询操作,多了超时
4 用postman执行修改方法: localhost:8081/shop
{"area":"大关","sold":3035,"address":"金华路锦昌文华苑29号","name":"102茶餐厅","x":120.149192,"y":30.316078,"typeId":1,"id":1
}
执行完成以后,数据库的数据发生改变,查看redis的数据已经删除了。
三 缓存常见问题
1 缓存穿透:
客户端请求的数据,在数据库和redis中都不存在,这样缓存永远都不会生效,请求最终都到了数据库上。
解决方案:
-
当在数据库查询的结果也不存在的时候,可以返回null值给redis,并且设置TTL
-
布隆过滤器
布隆过滤器是一种数据结构,底层是位数组,通过将集合中的元素多次hash得到的结果保存到布隆过滤器中。主要作用就是可以快速判断一个元素是否在集合里面,但是因为算法的原因,也有一定概率的错误。
开发的时候我们一般选择空值值方式。
-
代码方式实现
根据id查询的时候,如果信息不存在,则要将空值写入redis,并设置空值过期时间
@Overridepublic Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}if(shopJson!=null){return Result.fail("店铺不存在");}//4 不存在根据id去数据库查询Shop shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){// 空值写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);//7 返回return Result.ok(shop);}
2 缓存击穿:
也叫热点key问题,一个被高并发访问且业务复杂的key突然失效了,无数的请求瞬间给数据库带来的巨大冲击
解决方案: 互斥锁 逻辑过期
互斥锁思路:
查询缓存的时候,未命中需要获取锁 代码ShopServiceImpl
@Override
public Result queryById(Long id) {//1 redis中查询商户缓存String shopJson = (String)redisTemplate.opsForValue().get("cache.shop:" + id);//2 判断是否存在if(StrUtil.isNotBlank(shopJson)){//3存在直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}if(shopJson!=null){return Result.fail("店铺不存在");}Shop shop = null;String lockKey = "lock.id:" + id;try {//代码到这里说明没有命中缓存,那么就可以获取锁了boolean isLock = tryLock(lockKey);// 如果没有拿到锁,则等待一会,递归执行代码if(!isLock){Thread.sleep(100);queryById(id);}//获取锁成功//4 不存在根据id去数据库查询shop = this.getById(id);//5 数据库也不存在,返回错误if(shop==null){// 空值写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,"",3, TimeUnit.MINUTES);return Result.fail("店铺不存在");}//6 存在则写入redis中redisTemplate.opsForValue().set("cache.shop:" + id,JSONUtil.toJsonStr(shop),30, TimeUnit.MINUTES);} catch (InterruptedException e) {e.printStackTrace();} finally {unlock(lockKey);}//7 返回return Result.ok(shop);
}// 获取锁
private boolean tryLock(String key){Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}
//释放锁
private void unlock(String key){redisTemplate.delete(key);
}
3 缓存雪崩
同一时间段内,大量的缓存key失效或者redis宕机,到时大量的请求到达数据库,带来巨大的压力。
解决方案
-
给key设置随机的TTL
-
集群方案防止宕机不可用