这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
前面记录了redis的一些基本操作,从本节开始学习日常业务中利用redis开发可能会遇到的一些问题以及实战等。本节主要学习和记录缓存穿透这一问题。
- 什么是缓存穿透?
- 传个不存在的id过来,redis查不到会去查mysql,mysql查不到会返回空,同时redis同样没有数据,不断重复该操作,导致大量请求到达mysql使mysql压力很大。
- 解决方案
- 缓存空对象
- 对于经过mysql查找后确认不存在的key,直接在redis缓存一个空对象,缓存时间不能太长,待key再请求时直接从redis获取空对象返回
- 优点:实现简单,维护方便
- 缺点:额外的内存消耗;有概率造成短期的不一致
- 布隆过滤
- 在访问redis前依靠算法判断该数据是否存在,存在则放行,否则直接返回空
- 实现原理:一个byte[]存储二进制位,把数据基于hash算法计算出hash值,然后将这些值转化为二进制位,保存到布隆过滤器里,判断数据是否存在时就判断对应数据是0或1
- 注意:概率事件,不存在则真的不存在,存在则不一定存在,仍有穿透风险
- 优点:内存占用小,无多余key
- 缺点:实现复杂;存在误判可能
- 主动方案
- 增强id的复杂度,避免被猜测id规律
- 做好数据的基础格式校验
- 加强用户权限校验
- 缓存空对象
- 案例
public Shop queryWithPassThrough(Long id){ String key = RedisConstants.CACHE_SHOP_KEY + id; Shop shop = new Shop(); try { shop = redisService.getObject(key, Shop.class); if(!Objects.isNull(shop)){ //存在直接返回 return shop; }else { String s = redisService.get(key); //在保存空值前,该会报空指针异常 if(s.equals("null")){ return null; } } } catch (Exception e){ log.info("null"); } //数据库查询 shop = getById(id); if(Objects.isNull(shop)){ //保存空值,针对缓存穿透问题 redisService.add(key,"null",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES); return null; } //非空则放入redis redisService.add(key,shop,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES); return shop; }