雪崩?穿透?数据库一致性?面试必考Redis知识点总结(二)

点击上方“后端开发技术”,选择“设为星标” ,优质文章和资源,及时送达

接着上一篇的内容,我们继续总结。如何应对面试?我对Redis知识点的总结(一)

雪崩·击穿·穿透

一、缓存雪崩遇到过吗?如何解决?

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

比如存在一些App首页的热点数据或者商品热点数据,有一个小时的过期时间,需要定时任务去做刷新。但是比如到凌晨有秒杀活动,正好此时缓存到期了。大量的请求都跳过缓存直接打到了数据库,比如redis可以承受每秒7000个请求,数据库5000,但是当7000的请求全部打到数据库就可能承受不住了。如果是核心业务比如用户被其他服务大量调用,并且没有做熔断,就可能造成大面积服务瘫痪。并且即使是所谓万能的重启服务,请求又会直接打到数据库再次挂掉。(血的教训,笔者遇到过)

如何解决:1.可以不设置超时时间,直避免过期,直接在更新时候去刷新。2. 可以在失效时间上加随机的过期时间,避免集中过期。3.集群部署可以将数据在每个数据库均匀分布存储,避免全部失效。4.有相应的限流降级策略

二、缓存击穿是什么

缓存击穿,是指一个热点Key,在不停地扛着大量的请求,大并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的大并发直接落到了数据库上,就在这个 Key 的点上击穿了缓存。

缓存击穿和缓存雪崩的区别?

缓存击穿是单个热点key失效,而缓存雪崩是缓存大面积失效。

解决办法:

1.可以设置key不过期,更新时直接覆盖。

2.可以加互斥锁,避免大量请求同时打库。

//redis查询数据
String result = getRedisData(key);
//检查结果是否为空
if (StringUtils.isBlank(result)) {
   try {
       //抢锁 如果是分布式环境 这里也可以使用分布式锁
       if (reenLock.tryLock()) {
       //数据库查询
       result = getMySqlData(key);
       //检查是否为空
       if (StringUtils.isNotBlank(result)) {
       //保存到redis
          setRedisValue(key, result);
        }
      } else {
      //过一会在尝试
      Thread.sleep(100L);
      result = 本方法(key);
      }
    } finally {
   //释放锁
    reenLock.unlock();
    }
}
return result;

三、缓存穿透是什么

缓存穿透,是指缓存和数据库中都没有数据,而用户不断发起请求。因为用户的请求进入系统会先去查缓存,而缓存没有又会去查数据库,数据库没有返回null,正常数据此时会保存到Redis,下次直接查询缓存,而这里null的数据下次还是依旧打到了数据库。这里可能是用户不存在此数据,也可能是黑客的恶意攻击。这会给服务器带来很大的压力。

如何解决:

1.既然是空会下次依旧查询数据库,那可以把空对象存到Redis中,但是设置一个较短的过期时间。比如60s,避免频繁打库,也保证了数据最终会得到更新。

2.接口层增加校验,比如用户鉴权,参数做校验,不合法的校验直接 return,比如 id 做基础校验,id<=0 直接拦截。

3.可以利用布隆过滤器(Bloom Filter),将所有可能的查询key 先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。

Redis和memcached的区别

存储方式上:Memcache 会把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis 有部分数据存在硬盘上,这样能保证数据的持久性。 

数据支持类型上:Memcache 对数据类型的支持简单,只支持简单的 key-value,,而 Redis 支持五种数据类型。

使用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。Redis 直接自己构建了 VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

Value 的大小:Redis 可以达到 1GB,而 Memcache 只有 1MB。

Redis与Mysql双写一致性方案

1.延时双删策略

先淘汰缓存,再写数据库,休眠1秒,再次淘汰缓存。

之所以这么麻烦,是因为这种策略是为了解决先删除缓存,再删除数据库时产生的问题。如果采用这种方案,假设同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存 

(2)请求B查询发现缓存不存在

(3)请求B去数据库查询得到旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库上述情况就会导致不一致的情形出现。如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

所以为了解决上述问题,需要删除两次,而为了保证在B请求之后再去删除B产生的脏数据,所以需要有个休眠时间,这里是以1s举例,具体怎么设置呢?评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。

public void write(String key,Object data){
    redis.delKey(key);//删除缓存
      db.updateData(data);//更新数据
      Thread.sleep(1000);//休眠1秒
      redis.delKey(key);//再次删除
}

如果担心再次删除所产生的阻塞导致吞吐量降低,二次删除时可以异步删除。

2.先更新数据库,再删缓存。

是不是这样就没有问题呢?理论上其实是有的。

此时缓存刚好失效,请求A查询数据库,得一个旧值,然后请求B将新值写入数据库,请求B删除缓存,然后请求A将查到的旧值写入缓存。

这样,便产生了脏数据。但是这种情况会发生吗?概率极其低!

因为如果产生了,则说明读的速度要低于写的速度。基于mysql的特性,这一情况很难出现。

3.利用消息队列。

因为方案2可能存在删除缓存失败,所以可以提供一个补偿措施即可,例如利用消息队列。

1.将需要删除的key发送至消息队列,消费消息,获得需要删除的key,继续重试删除操作,直到成功然而。缺点:对业务线代码造成大量的侵入。

2.启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。(可以利用的中间件 canal)

不知不觉写到快12点了,下篇继续讲解Redis。

请继续关注。

◆ ◆ ◆  ◆ ◆

关注并后台回复 “面试” 或者  “视频”,

即可免费获取最新2019BAT

大厂面试题和大数据微服务视频

您的分享和支持是我更新的动力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端开发技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值