加载中...
redis作为缓存遇到问题点
发表于:2021-08-03 | 分类: 后端

这篇文章由一篇面试题说起。。。

面试官问,如果线上redis挂了,所有请求都打到了数据库了,然后数据库也挂了。这时应该怎样恢复?

理解

如果线上 Redis 挂了。然后所有请求打到数据库导致数据库也挂了。

redis 挂了 => 缓存都没了;

缓存都没有了 => 缓存雪崩;

缓存雪崩了 => 数据库挂了;

  1. 缓存全没了:缓存雪崩;
  2. 缓存中没有数据库中有:缓存击穿;
  3. 缓存和数据库中都没有:缓存穿透。

问题

redis 挂了,为什么挂了?怎么就挂了?是不是有单点问题

这就是在问 redis 服务的高可用。

首先要解答 这时该怎么进行恢复?

怎么恢复是正事,“缓存三连击”,“高可用架构”都是预防措施。答肯定要答,从事中恢复过渡到事前预防方案要自然一些。

服务挂了,先把 redis 和数据库服务重新启动起来。启动之前把流量摘掉,可以先把流量拦截在入口的地方,比如简单粗暴的通过 Nginx 的配置把请求都转到精心设计的错误页面上。这样做的目的是为了防止流量过大,直接把新启动的服务,启动一个打挂一个的情况出现。

要是启动起来又扛不住了,不行就价钱,加机器

加机器没什么技术含量,再从缓存预热的角度往下说。

当 redis 服务重新启动后,通过程序先放点已知的热点 key 进去后,系统再对外提供服务,防止缓存击穿的场景。

下面再继续说说事前预防。

缓存击穿

缓存击穿是指一个请求要访问的数据,缓存中没有,但数据库中有的情况。

这种情况一般来说就是缓存过期了。

但是这时由于并发访问这个缓存的用户特别多,这是一个热点 key ,这么多用户的请求同时过来,在缓存里面没有取到数据,所以又同时去访问数据库取数据,引起数据库流量激增,压力瞬间增大,直接崩溃。

所以一个数据有缓存,每次请求都从缓存中快速的返回了数据,但是某个时间点缓存失效了,某个请求在缓存中没有请求到数据,这时候我们就说这个请求“击穿”了缓存。针对这个场景,对应的解决方案一般来说有三种。

  • 第一个就是只放行一个请求到数据库,然后做构建缓存的操作。
    借助 Redis setNX 命令设置一个标志位就行。设置成功的放行,设置失败的就轮询等待。

  • 第二个解决方案就是后台续命
    这个方案的思想就是,后台开一个定时任务,专门主动更新即将过期的数据。

比如程序中设置 program 这个 key 的时候,同时设置了过期时间为 10 分钟,那后台程序在第 8 分钟的时候,会去数据库查询数据并重新放到缓存中,同时再次设置缓存为10分钟。

有种 redisson 分布式锁看门狗的原理。思想是一脉相承的。按照看门狗的思想就是每个 key (分布式锁)一个监控任务。有一定的性能消耗,所以要做取舍,不用所有 key 都做这种机制。方案落地的时候,从代码编写的角度来说麻烦些。

运用这个思想开发一个流水号系统,思路是这样的。

流水号系统,属于比较关键的系统,为了降低数据库异常对服务带来的冲击,所以服务启动后就会为每种业务系统都预先在缓存中缓存5000个流水号。

然后后台job定时检查缓存中还剩下多少流水号,如果小于1000个,则再预先生成新的流水号,补充到缓存中,让缓存中的流水号再次回到5000个。

这样做的好处就是数据库异常后,至少保证还有5000个缓存可以保证上游业务,有一定的时候去恢复数据库。

也算是一种后台续命的思想。

  • 第三个方法就是:永不过期
    结合实际场景就是这个key一定是个热点key,会有大量的请求来访问这个数据。而且这个key对应的value不会发生变化。

其实上面的后台续命思想的最终体现也就是永不过期。

只是后台续命的思想,会主动更新缓存,适用于缓存会变的场景。会出现缓存不一致的情况,取决于你的业务场景能接受多长时间的缓存不一致。

缓存穿透

缓存穿透是指一个请求要访问的数据,缓存和数据库中都没有,而用户短时间、高密度的发起这样的请求,每次都打到数据库上,给数据库造成了压力。

一般来说这是恶意请求。

两个方案解决

第一个是缓存空对象。就是在数据库即时没有查询到数据,我们也把这次请求当作key缓存起来, value可以是null。下次同样请求就会命中这个null,缓存层就处理了这个请求,不会对数据库产生压力。

这样实现起来简单,开发成本很低。这里会衍生出来一道面试题:

对于恶意攻击,请求的时候key往往不相同,且只请求一次,那你要把这些key都缓存起来的话,因为每个key都只请求一次,那还是每次都会请求到数据库,没有保护到数据库。

答案是,布隆过滤器

布隆过滤器的特性是说某个值存在时,这个值可能不存在。当它说不存在时,那就肯定不存在。

可以基于这个特性,把已有数据都构建到布隆过滤器里面去。它可以帮忙挡住绝大部分的攻击。

这里有个连环炮的问题:
面试官:布隆过滤器容量有限且不支持删除,随着里面内容的增加,误判率就会随之上升。请求,这个问题你们是怎么解决的?

也是两个答题方向。

首先,不支持删除的话,就换一个支持删除的布隆过滤器的轮子,比如布谷鸟过滤器。或者就是提前重构布隆过滤器。

比如在容量达到50%的时候,就申请一个新的更大的布隆过滤器来替换掉之前的过滤器。只是需要注意的是,重建你得知道有哪些数据需要进行重建的,所以你得有个地方来记录。比如就是redis、数据库,甚至内存缓存都可以。

缓存雪崩

缓存雪崩是指缓存中大多数的数据在同一时间到达过期时间,而查询数据量巨大,这时候,又是缓存中没有,数据库中有的情况了。

和前面将的缓存击穿不同的是,缓存击穿是指大量的请求并发查询同一条数据。

缓存雪崩是不同数据都到了过期时间,导致这些数据在缓存中查询不到。

防止雪崩的方案简单来说就是错峰过期。

在设置key过期时间的时候,再加上一个短的随机过期时间,这样就能避免大量缓存在同一时间过期,引起的缓存雪崩。

Redis 高可用架构

Redis 高可用架构,基本上是主从、哨兵、集群这三种模式。

主从结构很简单,弊端主要是出现故障的时候需要人工介入干预,需要人工介入的,就是阉割版的高可用。

哨兵是官网推荐的高可用方案。接下来主要说下哨兵模式。

图片

哨兵是用来管理多个 Redis 服务的。

它主要执行三种类型的任务:

  • 监控(Monitoring):Sentinel会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,是的集群可以使用新主服务器代替失效服务器。

哨兵其实也是一个分布式系统,我们可以运行多个哨兵。

然后这些哨兵之间需要相互通气,交流信息,通过投票来决定是否执行自动故障迁移,以及选择哪个从服务器作为新的主服务器。

另外,如果主节点挂了,哨兵到底通过什么规则选择新的主节点,也就是选举过程大致是怎么样的,也偶先面试环节。

这些规则会背就完事了。

  • 在挂了的主节点下挂的从节点中,被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从节点都没有资格参与选举。
  • 在挂了的主节点下挂的从节点中,那些与挂了的主节点连接断开的时长超过 down-after 配置指定的时长十倍的从节点都没有资格参与选举。
  • 经过上面这两轮淘汰之后,剩下来的从服务器中,选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器。如果复制偏移量不可用,或者从服务器的复制偏移量相同,那么带有最小运行 ID 的那个从服务器成为新的主服务器。

其实执行上面这些操作的,是一个哨兵。而我们的哨兵一般是三个以上,那么那个哨兵来执行这些操作呢?

其实这个哨兵也是需要从多个哨兵中被选举一个出来的,被选出来的这个哨兵就是领头哨兵(leader Sentinel)。

选举领头哨兵的时候,采取的是 Raft 算法。

下一篇:
有趣的位运算
本文目录
本文目录