1、缓存穿透
指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存不命中,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,缓存就没有意义了。
在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒。
- 布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。
2、缓存击穿
主要针对的是热点key
指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案
- 设置热点数据永远不过期。
- 接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。
- 加互斥锁。
3、缓存雪崩
指缓存中数据大批量过期,而查询数据量巨大,引起数据库压力过大甚至down机。
缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
- 设置热点数据永远不过期。
4、缓存污染
缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间。
缓存污染会随着数据的持续增加而逐渐显露,随着服务的不断运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。
一般性建议缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。
5、缓存淘汰策略
主要分成三大类
- 不淘汰:noeviction (v4.0后默认的)
- 设置了过期时间的数据:
随机:volatile-random
ttl:volatile-ttl
lru:volatile-lru
lfu:volatile-lfu - 全部淘汰:
随机:allkeys-random
lru:allkeys-lru
lfu:allkeys-lfu
5.1、缓存淘汰策略详解
- noeviction: 该策略是Redis的默认策略,一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。这种策略不会淘汰数据,所以无法解决缓存污染问题。一般生产环境不建议使用。
- volatile-random: 在设置了过期时间的键值对中,进行随机删除。因为是随机删除,无法把不再访问的数据筛选出来,所以可能依然会存在缓存污染现象,无法解决缓存污染问题。
- volatile-ttl: 参考的指标比随机删除时多进行一步过期时间的排序。Redis在筛选需删除的数据时,越早过期的数据越优先被选择。
- volatile-lru: 按照最近最少使用的原则来筛选数据。这种模式下会使用 LRU 算法筛选设置了过期时间的键值对。
- volatile-lfu: 使用 LFU 算法选择设置了过期时间的键值对.
LFU 算法:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
6、数据库和缓存一致性问题
加入redis换粗后的一般的业务场景如下:
读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。
不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。
总结:
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
6.1、缓存一致性的解决方案
在更新数据库成功后,删除缓存key不一定生效的,解决方案是什么?
方案1:队列 + 重试机制
流程如下所示:
1.更新数据库数据;
2.缓存因为种种问题删除失败
3.将需要删除的key发送至消息队列
4.自己消费消息,获得需要删除的key
5.继续重试删除操作,直到成功
该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案2
方案2:异步更新缓存(基于订阅binlog的同步机制)
整体思路:MySQL binlog增量订阅消费+消息队列+增量数据更新到redis