商户查询缓存
初步实现代码如上所示较为简单,p37留了个作业,可以考虑实现一下。
上述代码只是简单的将数据加入到了缓存之中,那么既然用到了redis就会存在一些问题,首先就是数据一致性问题。
通俗来说就是,数据库数据发生了更新,但是redis 中还没有更新,那么redis中的数据就是旧数据了,此时若是有请求来读取,那么数据就不对了。
要想解决这个问题,这就要涉及到缓存更新策略的选择了。
缓存更新策略
内存淘汰
redis内置的一种淘汰机制,不需要开发者维护,当内存不足的时候会自动淘汰掉一些数据,这样更新的好处就是不需要维护,但是无法解决一致性的问题,如果内存足够或者有些数据一直没被淘汰。
超时剔除
顾名思义,就是给缓存设置一个有效期,有效期了缓存就过期淘汰了,这会存在维护成本,数据一致性问题也没有得到解决,因为在有效期内还是会存在问题。
主动更新
我们自己来写业务逻辑,去处理一致性问题,数据库发生了修改,我们就立马更新缓存,当然,这样就加大了维护成本,但是较好的解决了数据一致性问题(注意,不是一定,总会有一些意外发生)。
所以呢,在一些数据不经常修改的情况下,考虑内存淘汰机制,数据修改频繁的话,就需要主动更新,甚至以超时剔除作为兜底方案。
那么这时候就需要考虑一些问题:
-
我们是删除缓存还是更新缓存呢?
- 一般会选择删除缓存,因为数据库如果修改多次了,就得更新多次缓存,而这段时间没人访问,那么这更新就很浪费资源。
- 而删除缓存指的就是,数据库你更新很多次,我直接把缓存删了,等下次有人查询的时候再把最新的数据库值缓存更新过来就可。
-
如何保证缓存和数据库的操作同时成功或者同时失败?
- 单体系统:把缓存和数据库的操作放在一个事务之中。
- 分布式系统:利用TTC(SpringCloud)等分布式事务方案。
-
先操作缓存还是先操作数据库?
- 这两种方式都会发生不安全的情况,但先操作数据库再操作缓存发生不安全行为的概率会低一些。
具体分析:
缓存穿透
缓存穿透指的就是客户端请求的数据在缓存中和在数据库中都不存在,这样缓存永远都不会生效,这些请求都会达到数据库,可能会有人利用这一点,开无数并发的线程,请求这些不存在的数据,容易搞垮数据库。
解决思路
-
缓存空对象
-
简单粗暴的一种方法,既然你都没命中,那我就把null值缓存到redis中,那么下次redis就会命中了,就不会访问数据库了。
-
优点:实现简单,维护方便
-
缺点:额外的内存消耗:设置较短的超时时间可解决。
-
缺点:可能会造成短期的数据不一致。
-
-
布隆过滤
- 是一种算法
- 那么布隆过滤器怎么知道数据是否存在呢?
- 把数据转为哈希值再转为二进制保存到过滤器中,通过判断01来确定是否存在
- 不存在就一定不存在,但是存在不一定存在。
缓存雪崩
缓存雪崩指的是同一时段大量的缓存Key都失效了,或者Redis宕机了,此时大量请求发送到数据库,带来巨大压力。
解决方案:
- 给不同的Key的TTL设置随机值(解决同时失效的问题)
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
缓存击穿
热点Key问题,就是一个被频繁使用,被高并发访问的Key,并且缓存重建业务较为复杂的Key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
缓存重建:重新从数据库中查询的过程非常复杂,关联了很多个表,在这段过程中缓存中就没有存储这部分信息。
解决方案
-
互斥锁
性能比较差,因为如果数据库重建缓存时间较久的话,后面线程基本上都会处于等待状态。
-
逻辑过期
互斥锁实现方式:
利用Redis的setnx命令实现互斥锁
只有这个key对应的value不存在的时候才能设置值,相当于就是获取锁。
那么把这个key删掉,就相当于释放锁了。
注意:哪怕是获取锁了,那也要再次判断缓存是否存在,存在直接返回命中缓存,不存在再重建,做一个DoubleCheck。
逻辑过期实现方式:
这种方式的目的,其实就是需要被高频访问的key 提前通过后台管理系统,加入到redis中,没命中证明不是活动需要的商品,就直接返回空,命中了才需要解决缓存击穿问题,也就是判断缓存是否过期。
- 命中换成你,需要将json反序列化为对象
- 判断是否过期
- 过期,需要缓存重建
注意:哪怕是获取锁了,那也要再次判断缓存是否存在,存在直接返回命中缓存,不存在再重建,做一个DoubleCheck。