一.大纲
二.缓存
2.1 缓存穿透
案例:根据id查询文章
缓存穿透:当查询一个不存在的数据,mysql查询不到数据,也不会写入缓存,就会导致每次查询时候都会去查数据库。如果当黑客知道了请求的链路,一直用不存在的id去查询数据,就会可能导致数据库的压力增大,导致宕机。
解决方案:
解决方案 | 描述 | 优点 | 缺点 |
---|---|---|---|
缓存空数据 | 缓存空数据,查询返回的数据为空,也存在缓存中去 | 简单 | 1.当存在大量空数据的时候,会消耗内存;2.当原来的空数据,mysql数据库又突然有的时候,就会出现数据一致性问题。 |
布隆过滤器 | 布隆过滤器 | 占用内存少,没有多余的key | 1.实现复杂 ;2.存在误判 |
布隆过滤器的实现
布隆过滤器的主要作用是检验一个元素是否在集合中存在。它的底层实现是先去初始化一个比较大的数组,里面存放的二进制0或1,在一开始都是0,当一个key来了之后经过3次hash计算,与数组的长度进行模取余,然后把数组中对应位置的0改为1,然后数组中的3个位置就能标明一个key的存在,查询的时候,需要匹配到对应的3个位置为1,就说明key存在。
布隆过滤器可能会存在误判,如下图所示:
误判率:数组越大,误判率越低,消耗内存越多;数组越小,误判率越高,消耗内存越小。
布隆过滤器有可能产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,这个误判是必然存在的,我们可以通过增加数组的长度,来减小误判率,但是会增加内存的消耗,一般5%的误判率一般的项目都能接受,不至于高并发下压倒数据库。
2.2 缓存击穿
缓存击穿:当给一个key设置了过期时间,当key过期的时候,此时恰好对应的key有大量的并发请求进来,这些请求有可能压垮数据库。
解决方案:
方案1:互斥锁,保证数据强一致,性能差。
如上图所示,使用互斥锁时,当线程1缓存未命中时,不会立即去load db,先使用如redis的setnx去设置一个互斥锁,当线程1获取锁成功时在进行load db的操作并设回缓存,如果在线程1重设数据的过程中,此时有其他线程也未中缓存,是会采用不断重试的方式访问缓存。
方案2:设置key逻辑过期,高可用,性能优,不能保证数据强一致性。
如上如所示:设置key逻辑过期的大概实现思路如下:
1.在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间;
2.当查询的时候,从redis取出数据后判断时间是否过期;
3.如果过期则开发另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新的。
2.3 缓存雪崩
缓存雪崩:当大量的key过期或者redis宕机的时候,导致大量的请求到达数据库,带来巨大压力。
解决方案
1.针对存在大量key过期的问题,我们可以给不同的key的TTL添加随机值;
2.利用Redis集群提高服务的高可用,利用redis的哨兵模式、集群模式等;
3.给缓存业务添加降级限流策略,如nginx或者spring clound gateway;
4.给业务添加多级缓存 ,如Guava 或Caffeine。
2.4 双写一致性
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
当我们采取方案保持数据一致性的时候,需要考虑是要求一致性要求高,还是允许延迟一致。
读操作:缓存命中,直接返回