缓存redis缓存缓存更新策略主动更新策略缓存穿透缓存穿透现象缓存空对象处理缓存穿透布隆过过滤器处理缓存穿透 缓存击穿
缓存是数据交换的缓冲区(cache);是数据存储临时的地方;一般读写性能较高好处 降低后端负载提高读写效率;降低响应时间 成本 一致性成本代码维护(为了一致性)运维成本(缓存一般是集群的)
redis缓存执行流程
;Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {;Autowired private StringRedisTemplate stringRedisTemplate; ;Override public Result queryById(Long id) {String key = ;cache:shop:;;id; //1、从Redis查询id String shopJson = stringRedisTemplate.opsForValue().get(key); //2、判断redis是否存在数据 if(StrUtil.isNotBlank(shopJson)) {//3、存在 Shop shop = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop); } //4、不存在 Shop shop = getById(id); //5、存在返回错误 if(shop == null){return Result.fail(;店铺不存在;); } //6、存在写入reids stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop)); //7、返回 return Result.ok(shop); } }
缓存更新策略是为了解决缓存不足的问题 内存淘汰;redis的内存淘汰机制;当内存不足自动淘汰部分数据;下次查询时更新缓存 一致性差、无维护成本 超时删除;给缓存数据添加过期时间;到期删除缓存数据;下次查询更新缓存 一致性一般;维护成本低 主动更新;编写业务逻辑;在修改数据库的同时;更新缓存 一致性好 场景 低一致性需求;使用内存淘汰机制;基本不会改变的数据高一致性需求;主动更新;经常改变的数据
缓存调用者;在更新数据库的同时更新缓存(推荐)
删除缓存与更新缓存
删除缓存;每次更新数据库时让缓存失效;查询时在更新缓存;推荐;更新缓存;每次更新数据库都更新缓存;无效写操作多
保证缓存与数据库操作同时成功或者失败
单体系统;将缓存与数据库操作放在一个事务分布式系统;利用TCC等分布式事务方案
先删除缓存;在更新数据库、先更新据库;在删除缓存
都会造成一致性问题;后者概率低;需要加锁也就是上面说的事务
缓存与数据库合为一个服务;有服务来维护一致性;调用者调用该服务;无需关心缓存一致性问题(实现起来复杂)
调用者只操作缓存;由其他线程异步的将缓存数据持久化到数据库(实现起来复杂)
缓存穿透;客户端请求的数据在缓存中和数据库都不存在;这样缓存永远不会生效;这些请求都会到达数据库
缓存空对象布隆过滤增强id复杂度做好数据格式校验加强用户格式校验
客户端访问了一个数据库不存在的数据
当数据没查到数据时;我们将null数据设置到缓存中;并设置过期时间好处 实现简单;维护方便 缺点 额外内存消耗可能造成短期不一致(突然插入该数据);解决就是将插入的数据存入缓存
布隆过滤器通过算法;将数据库的数据二进制位(占用较少空间)存在过滤器中 存在;数据库一定存在该数据不存在;数据库可能存在、也可能不存在该数据 好处 内存占用少 缺点 实现复杂
缓存击穿问题也叫热点key问题;就是一个被高并发访问并且缓存重建业务的key突然消失了;无数请求访问会在瞬间给数据库带来巨大的压力
缓存击穿之后;同一时间节点;可能有多个线程都在重建缓存;解决方案
互斥锁;发现有线程在重建缓存;新来线程休眠一会在获取锁
优点
没有额外内存消耗保证一致性实现简单
缺点
线程需要等待;性能受影响可能有死锁的风险
逻辑过期;不给key设置过期时间;在value设置一个过期时间(逻辑时间);如果发现时间已经过期;当前线程会返回旧的数据;然后开启一个新的线程去重建缓存;在缓存没有重建后之前;别的线程访问也是旧的缓存数据
优点 线程无需等待;性能较好 缺点 不能保证一致性有额外内存消耗实现负复杂