一、什么是缓存
缓存就是数据交换的缓冲区,cache,是存储数据的临时地方,一般读写性能较高
缓存的作用:
- 降低后端负端,省去磁盘操作
- 提高读写效率,降低响应时间(高并发问题)
缓存成本:
- 数据一致性成本(DB数据变了,缓存没变,数据就不一致了)
- 代码维护成本提高(解决一致性等)
- 运维成本
二、添加Redis缓存
1、代码:根据id查询商铺缓存的代码
/**
* 根据id查询商铺信息
* @param id 商铺id
* @return 商铺详情数据
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
return shopService.queryById(id);
}
2、业务层
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {
// 从Redis 中查询商铺缓存
String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
String shopJson = stringRedisTemplate.opsForValue().get(shopKey + id);
// 判断是否存在
if (!StringUtils.isEmpty(shopJson)) {
// 存在则返回shop对象信息
Shop shop = JSONObject.parseObject(shopJson, Shop.class);
return Result.ok(shop);
}
// 不存在则查询数据库
Shop shop = getById(id);
if (shop == null) {
return Result.fail("商品不存在");
}
// 存在则放入redis里
stringRedisTemplate.opsForValue().set(shopKey + id, JSONObject.toJSONString(shop));
return Result.ok(shop);
}
2、查询分类的流程,是一个list,字符串的话,就用JSONArray
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryTypeList() {
String key = "cache:shop:list";
String listJson = stringRedisTemplate.opsForValue().get(key);
log.info("listJson={}", listJson);
if (!StringUtils.isEmpty(listJson)) {
System.out.println("111111");
JSONArray objects = JSONArray.parseArray(listJson);
return Result.ok(objects);
}
List<ShopType> sort = query().orderByAsc("sort").list();
stringRedisTemplate.opsForValue().set(key, JSONArray.toJSONString(sort));
return Result.ok(sort);
}
二、缓存更新策略
解决数据一致性问题
主动更新
针对上述3 先删除缓存,在操作数据库的问题,缓存和数据库结果不一致,产生线程安全问题
先操作数据库在删除缓存,
- 线程1查询缓存,缓存(恰好)失效了 未命中,查数据库的值是10。。(恰好查完要写缓存的同时,(在写得微秒范围内)线程二进来了,线程二去更新数据库,然后删缓存,在这个微秒的时间内,线程二执行完的可能性不高,因为缓存的速度,要高于磁盘的速度,即:要同时满足上面三个恰好)
- 线程2更新数据库20,删除缓存。
- 线程1开始写缓存,写进去的就是旧数据10了,
方案二,如果恰巧出现了,那么我们以后给缓存加个超时时间,TTL 就好了
总结:写操作,即Update的时候
案例
1、给redis添加过期时间
stringRedisTemplate.opsForValue().set(shopKey + id, JSONObject.toJSONString(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
2、修改操作
/**
* 更新商铺信息
* @param shop 商铺数据
* @return 无
*/
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
// 写入数据库
return shopService.update(shop);
}
业务层:这里要开启事务,保证要么都执行,报错都回滚
@Override
@Transactional /*开启事务保证磁盘操作和缓存操作的原子性*/
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null) {
return Result.fail("店铺Id不能为空");
}
// 更新数据库操作
updateById(shop);
// 删除redis
stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);
return Result.ok();
}
利用postMan手动触发接口,修改数据:put请求,然后可以去页面操作了,页面刷新回立即更改的
localhost:8081/shop
{
"id": 1,
"name": "118茶餐厅",
"typeId": 1,
"images": "https://qcloud.dpfile.com/pc/jiclIsCKmOI2arxKN1Uf0Hx3PucIJH8q0QSz-Z8llzcN56-_QiKuOvyio1OOxsRtFoXqu0G3iT2T27qat3WhLVEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vfCF2ubeXzk49OsGrXt_KYDCngOyCwZK-s3fqawWswzk.jpg,https://qcloud.dpfile.com/pc/IOf6VX3qaBgFXFVgp75w-KKJmWZjFc8GXDU8g9bQC6YGCpAmG00QbfT4vCCBj7njuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg",
"area": "大关",
"address": "金华路锦昌文华苑29号",
"x": 120.149192,
"y": 30.316078,
"avgPrice": 80,
"sold": 4215,
"comments": 3035,
"score": 37,
"openH