一、什么是缓存
- 缓存:是数据存的缓冲区,是存储数据的地方,一般读写性能较好。
- 以WEB访问,缓存存在的各个地方
- 浏览器缓存:静态的CSS、JS脚本或图片
- Tomcat缓存:使用Redis对于
- 缓存的优缺点
二、查询商户的业务流程
三、具体业务代码实现
- 具体代码实现
package com.hmdp.service.impl; import cn.hutool.Hutool; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONNull; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.mapper.ShopMapper; import com.hmdp.service.IShopService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import io.netty.util.internal.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY; /** * <p> * 服务实现类 * </p> * * @author * @since 2021-12-22 */ @Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Autowired private StringRedisTemplate stringRedisTemplate; /** * 通过ID查询商铺的详细信息 * @param id * @return */ @Override public Result querById(Long id) { String redisId = CACHE_SHOP_KEY +id; // 1 先通过redis查询 String s = stringRedisTemplate.opsForValue().get(redisId); if (StrUtil.isNotBlank(s)) { Shop shop = JSONUtil.toBean(s, Shop.class); return Result.ok(shop); } // 返回空,查询数据库 Shop shop = getById(id); // 数据库返回空,返回错误信息 if (shop==null) { return Result.fail("商户不存在"); } // 不为空返回数据并将数据存储在redis中 stringRedisTemplate.opsForValue().set(redisId,JSONUtil.toJsonStr(shop)); return Result.ok(shop); } }
- 效果对比
- 查询数据库
- 查询redis
四、练习题:商铺分类列表redis缓存改造
- 实现方法和上章节类似,但是采用的存储方法为Zset
- 实现代码
package com.hmdp.service.impl; import cn.hutool.json.JSONUtil; import com.hmdp.dto.Result; import com.hmdp.entity.ShopType; import com.hmdp.mapper.ShopTypeMapper; import com.hmdp.service.IShopTypeService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.sun.xml.internal.bind.v2.runtime.reflect.Lister; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; /** * <p> * 服务实现类 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Slf4j @Service public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService { @Autowired StringRedisTemplate stringRedisTemplate; @Override public Result queryTypeList() { String key = "catch:shopType"; Set<String> range = stringRedisTemplate.opsForZSet().range(key, 0, 10); List<ShopType> list= new ArrayList<>(); if (range.size()!=0) { range.stream().forEach(item ->{ ShopType bean = JSONUtil.toBean(item, ShopType.class); list.add(bean); }); log.debug("redis get catch:shopType"); return Result.ok(list); } List<ShopType> sort = query().orderByAsc("sort").list(); if (sort.size()==0) { return Result.fail("系统错误"); } HashSet<ShopType> shopTypes = new HashSet<>(); shopTypes.stream().forEach(item->{ stringRedisTemplate.opsForZSet().add(key,JSONUtil.toJsonStr(item),item.getSort()); }); log.debug("Mysql get shopTypeList"); return Result.ok(sort); } }
五、缓存更新策略
1. 三种策略模式
2. 策略选择分析
- 低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存
3. 主动更新策略的三种实现业务方式
- 在实际开发中常使用的为方法付01
- 使用方法需要注意的三点问题:
- 点三建议使用,先操作数据库,在删除缓存
- 点三先删除缓存,在操作操作数据存在的线程不一致问题
- 正常
- 异常
- 点三 先操作数据库,在删除缓存
- 正常
- 异常(条件:缓存已经失效的场景)
- 此异常发生需要的条件
- 两个线程并行执行
- 线程1查询时,恰巧缓存失效,并已经查询完了数据库,准备写缓存了
- 在写缓存的的同时,恰巧出现了一个线程开始更新数据库
4. 总结
六 、 练习题:给查询商铺的缓存添加超时剔除和主动更新的策略
七、缓存穿透
- 缓存穿透:是指客户端请求的数据在缓存和数据库中都不存在。
- 这样缓存就不会生效,这些请求都会打到数据库中。
- 现象:
- 当用户使用多个根本不存在的ID查询数据时,因为不存在所以最后都会去查询数据库。从而导致数据库被进行大量的空访问。
1. 解决方法一:缓存空对象
- 特点:
- 流程:
2. 解决方法二:布隆过滤器
- 在过滤器中保证,没有的不存在的id拦截,存在的id可能到最后也是不存在。
- 会小概率的出现缓存穿透。
- 特点:
- 流程:
3. 缓存空对象的方法实践
- 流程:
- 需要修改的地方:
- 如果不存在就将在redis中存储一个空对象
- 在判断通过key查询redis中的数据时,多加一个判断,判断查询到的数据是否为null,为null,直接结束,返回警告信息。
@Override public Result querById(Long id) { String redisId = CACHE_SHOP_KEY +id; // 1 先通过redis查询 String s = stringRedisTemplate.opsForValue().get(redisId); if (StrUtil.isNotBlank(s)) { Shop shop = JSONUtil.toBean(s, Shop.class); return Result.ok(shop); } if (s==""){ return Result.fail("商户不存在"); } // 返回空,查询数据库 Shop shop = getById(id); // 数据库返回空,返回错误信息 if (shop==null) { //存储缓存空对象 stringRedisTemplate.opsForValue().set(redisId,"",2L, TimeUnit.MINUTES); // 60分钟 return Result.fail("商户不存在"); } // 不为空返回数据并将数据存储在redis中 stringRedisTemplate.opsForValue().set(redisId,JSONUtil.toJsonStr(shop),60L, TimeUnit.MINUTES); // 60分钟 return Result.ok(shop); }
4. 总结