当前位置: 首页 > 后端技术 > Java

Redis挂了,流量也把数据库挂了,怎么办?

时间:2023-04-01 13:16:31 Java

大家好,我是伟伟。就像这样。前几天有位读者给我留言,说他在面试的时候遇到一道场景题:他说一时半会找不到回答问题的角度,感觉没回答到重点.仔细想了想,还真的觉得这道题有点奇怪,有一种让人说千言万语的魔力,突然又不知从何说起。你为什么这么说?我们先看题,仔细看题,我给你翻译一下。如果线上Redis挂了。然后所有请求都打到数据库,数据库也挂了。这是什么?Redis宕机了,不就是因为缓存没了吗?缓存没了,不就是缓存雪崩吗?缓存雪崩,不会导致数据库挂掉吧?一提到“缓存雪崩”这两个词,你的脑海里马上就会不自觉地出现缓存穿透、缓存穿透这对兄弟,以及对应的几种解决方案。然后,就像背书一样,所有的缓存都没有了,数据库里没有缓存,缓存里也没有,数据库里也没有……嘴巴不停地张了几分钟。另外,很多同学会混淆缓存击穿和缓存穿透。你改变符号,缓存穿透,缓存穿透。磨损,刚刚通过缓存。彻底,直接到最后。仔细品尝应该不会记错。除了上面的千篇一律的“Redis缓存三连击”之外,还隐藏着另一种千篇一律:Redis宕机了,为什么宕机了?为什么挂了?有单点问题吗?这不是问你Redis服务的高可用吗?说到Redis的高可用,你的脑海里肯定马上就会浮现出master-slave、sentry和cluster吧?想到这里,他嘴巴张了好几分钟,没有停顿。但是过了几分钟几千字,马上就被下面的问题搞糊涂了?这时候怎么恢复?现在问你如何恢复是理所当然的事情。你得先谈如何恢复,再谈如何预防。上来就回答前面提到的“缓存三合一”和“高可用架构”,包括大部分同学能想到的多级缓存、限流措施、服务降级、熔断机制等。这些有点牵强。因为这些手段毕竟是事前的预防措施,据说这些背书的痕迹更加明显。答案必须要回答,工作过程中从恢复到事前预防的过渡,重在事前预防,怎样才能更自然?先说说事发期间如何恢复。其实我觉得三言两语就可以说完了。服务挂了,大哥,怎么恢复,当然是重启服务。从运维人员的角度来说,当然优先重启Redis和数据库服务。但是在开始之前,你必须做一个小操作来移除流量。可以先在入口处拦截流量。比如简单粗暴的通过Nginx的配置把请求转移到一个设计良好的错误页面,就是这么一个意思。这样做的目的是防止出现流量过大的情况,新启动的服务直接启动,一个接一个,一个挂掉。如果上手之后还搞不定,请在心里默念一下分布式系统的三大武器:搞不定就加钱堆机器。如果觉得堆机没什么技术含量,可以从缓存预热的角度来回答。即当Redis服务重启时,系统先通过程序放入一些已知的热点key,然后系统对外提供服务,防止缓存崩溃。而且,以上这一系列操作与开发人员关系不大,主要是运维同学完成的。大部分开发同学在设计服务的时候都是为了让服务无状态化,从而达到快速水平扩展的目的。至于如何快速横向扩容,那是运维同学的事了。暂时不要抢别人的饭碗。回答完这个,就可以用“但是”提前过渡到预防,开始自己的表演了。故作沉思,我对面试官说“但是”:我觉得从技术方案的角度来说,我们应该提前做好预防。所有这些问题都是由Redis崩溃造成的,也就是缓存雪崩。在高并发的情况下,除了缓存雪崩,还要考虑缓存的击穿和穿透。为什么Redis会崩溃?使用姿势有错吗?不保证高可用性吗?是否需要在服务中考虑限流或熔断机制,最大程度的保护程序的运行?还是应该建立多级缓存机制,防止Redis挂掉后大量流量直接冲击MySQL服务,导致数据库崩溃?至此,“但是”完成,过程中恢复答题方向,转向事前预防措施,进入我们的强项,八股文专场,接下来就可以开始“背诵”了。这里简单说说缓存问题的三重打击和Redis的高可用。至于多级缓存,可以看我之前发的这篇文章:《这波舒服了,落地多级缓存!》。缓存击穿先说一下缓存击穿的概念。缓存击穿是指请求要访问的数据不在缓存中,而是在数据库中。这通常意味着缓存已过期。但是这个时候有很多用户并发访问缓存。这是一个热点键。那么多的用户请求同时过来,但是没有从缓存中取数据,于是同时去数据库取数据,导致数据库流量激增,压力增大。瞬间增大,直接崩塌给你看。所以一段数据是有缓存的,每次请求都很快从缓存中返回数据,但是在某个时间点,缓存失效了,某个请求没有请求到缓存中的数据,这个时候我们说请求“打坏”缓存。对于这种场景,一般有三种对应的解决方案。第一种是只释放一个请求到数据库,然后做构建缓存的操作。多个请求只允许一个,怎么做呢,用Redis的setNX命令设置一个flag就可以了,设置成功就释放,设置失败就轮询等待,第二种方案是后台续命。这个方案的思路是在后台开启一个定时任务,主动更新即将过期的数据。比如在程序中设置whyhotspotkey的时候,设置过期时间为10分钟,那么后台程序会在第8分钟去数据库查询数据并放回缓存,并再次设置缓存为10分钟。怎么样,是不是有点像Redisson的分布式锁看门狗?我认为这些想法是一条连续的线。只是当方案实现的时候,从代码编写的角度来说有点麻烦。我也用这个想法开发了一个序列号系统。大概是这样。序列号系统是一个非常关键的系统。为了减少数据库异常对服务的影响,服务启动后会在缓存中为每个业务系统预缓存5000个序号。然后后台Job定时检查缓存中还剩多少序号。如果小于1000,它会预先生成一个新的序列号加入到缓存中,这样缓存中的序列号就会重新回到5000。这样做的好处是,数据库异常后,我保证至少有5000个缓存来保证上游业务,我有一定的时间来恢复数据库。这也算是一种延续生命在后台的想法吧。第三种方法很简单:永不过期。为什么缓存坏了?是不是设置了超时时间然后回收了?那我不设置超时时间?如果结合实际场景,用脚趾头就可以认为这个key一定是hotkey,会有大量请求访问这个数据。而这个key对应的value是不会改变的。为什么要为这样的数据设置过期时间?只要放进去,它永远不会过期。其实上面背景中的延寿思想的最终体现就是永不过期。只是后台的lifeextensionidea会主动更新缓存,适合缓存会变化的场景。会出现缓存不一致的情况,这取决于你的业务场景能接受缓存不一致多久。总之,具体情况,具体分析。但思路应该清晰,最终的解决方案是常规方案的组合或变体。缓存穿透那么什么是缓存穿透呢?缓存穿透是指一个请求要访问的数据不在缓存或数据库中,而是用户在短时间内高密度发起这样的请求,每次都命中数据库服务,造成数据库压力。一般来说,此类请求属于恶意请求。比如我是技术公众号,你明明知道我没有,却非要来我家买瓶啤酒,这就是恶意请求。如何解决?两种选择。第一个缓存空对象。即即使没有查询到数据库中的数据,我们也将这个请求缓存为key,value可以为NULL。同样的请求下次会命中这个NULL,缓存层会处理这个请求,不会给数据库造成压力。这实现起来很简单,开发成本也很低。但是一定要注意下面的面试题:对于恶意攻击,请求的时候key往往是不同的,而且只请求一次,那么如果要缓存这些key,因为每个key只请求一次,数据库还是每次都会请求,数据库没有保护?这个问题,Bloomfilter,懂了吗?之前写过这篇关于Bloomfilter的文章,大家可以看看:《布隆,牛逼!布谷鸟,牛逼!》Bloomfilter的特点是当某个值存在时,这个值可能不存在。当它说它不存在时,它肯定不存在。因此,基于这个特性,所有已有的数据都可以内置到Bloomfilter中。然后它可以帮助阻止大部分攻击。但是还有一系列容易被忽略的问题:面试官:Bloomfilter容量有限,不支持删除。随着里面内容的增多,误判率也会增加。请问,你是怎么解决这个问题的?回答问题也有两个方向。首先,如果不支持删除,就换一个支持删除的Bloom滤镜轮。比如我之前文章中提到的布谷鸟过滤器。或者只是提前重构布隆过滤器。例如,当容量达到50%时,申请一个新的更大的Bloomfilter来替换之前的filter。唯一需要注意的是,重构需要知道需要重构哪些数据,所以一定要有个地方记录下来。比如Redis、数据库,甚至内存缓存都可以。没登陆过没关系,可以放心回答。你要相信,80%的面试官都没有落地过,可能你们看的都是一样的资料。缓存雪崩缓存雪崩是指缓存中的大部分数据同时到达过期时间,查询数据量巨大。这个时候缓存是没有的,数据库里有。所有的请求都打到数据库上,导致数据库流量激增,压力瞬间增大,直接崩溃给你看。与上面提到的缓存击穿不同,缓存击穿是指大量请求并发查询同一条数据。缓存雪崩是指不同的数据都到了过期时间,导致无法在缓存中查询到这些数据。雪崩还是很形象的。防止雪崩的解决方案就是错峰呼气。在设置key过期时间的时候,加上一个很短的随机过期时间,避免大量缓存同时过期造成缓存雪崩。如果出现雪崩,我们可以有服务降级、熔断、限流的手段来拒绝一些请求,保证服务正常。但是,这些都对用户体验造成了一定的影响。假设我们的程序有这个逻辑,也是用来覆盖底线的。从用户的角度来看,他们不想陷入这样的逻辑。因此,防止雪崩仍然很重要。最后还有一种雪崩,就是整个Redis服务挂了。所以,接下来就要说说Redis服务的高可用架构了。Redis高可用架构说到Redis高可用架构,大家基本上可以想到三种模式:master-slave、sentinel、cluster。主从结构很简单,就不说了。主要缺点是出现故障时需要人工干预。如果需要人工干预,那不是真正的高可用。哨兵和集群是官网上写的两种方案:https://redis.io/topics/intro...上面划线的话翻译过来就是:Redis通过Redis哨兵(Sentinel)和Cluster集群(高可用)提供高可用.其中sentinel是官方推荐的高可用方案:所以我们主要说说sentinel模式。Sentinel用于管理多个Redis服务器。我从《Redis开发与运维》书上截了一张图给大家看看:它主要执行三类任务:监控(Monitoring):Sentinel会不断检查你的masterserver和slaveservers是否正常工作。通知:当被监控的Redis服务器出现问题时,Sentinel可以通过API向管理员或其他应用发送通知。自动故障转移(Automaticfailover):当一个主服务器无法正常工作时,Sentinel会启动一个自动故障转移操作,将故障主服务器的其中一台从服务器升级为新的主服务器,并让故障主服务器其他从属服务器复制新的主服务器;当客户端尝试连接到故障的主服务器时,集群也会将新的主服务器的地址返回给客户端,这样集群就可以使用新的主服务器来替换故障的服务器。Sentinel其实是一个分布式系统,我们可以运行多个Sentinel。然后这些Sentinels需要相互通信,交换信息,投票决定是否进行自动故障转移,并选择哪个从服务器作为新的主服务器。Sentinels之间采用的协议是gossip,这是一个去中心化的协议。它实现了最终一致性,这很有趣。之前写的PhoenixArchitecture对gossip协议有介绍。大家可以看看:https://icyfenix.cn/distribut...另外,如果master节点挂了,Sentry会用什么规则来选出新的master节点,也就是选举过程是怎样的,并且偶尔也会出现在采访环节。以前也有人问过,还好我当时背的很熟练。我简单说一下规则。没有它,记忆就够了:挂在主节点下的从节点中,被标记为主观下线、断开连接,或者最后一次回复PING命令的时间大于五秒的从节点没有资格参与在选举中。在downmaster节点下的slave节点中,与downmaster节点的连接断开时间超过down-after配置指定时间十倍以上的slave节点没有资格参与选举。经过以上两轮淘汰后,在剩余的从服务器中,选择复制偏移量最大的从服务器作为新的主服务器。如果复制偏移量不可用,或者副本具有相同的复制偏移量,则具有最小运行ID的从服务器将成为新的主服务器。实际上,执行上述操作的是哨兵。而我们的哨兵一般都在三个以上,那么这些操作会由哪个哨兵来执行呢?其实这个sentinel也需要从多个sentinel中选举出来,选举出来的sentinel就是领导哨兵(leaderSentinel)。在选举leadingsentinel时,采用Raft算法。至于哨兵模式的建设,一般来说就是运维的工作。不过网上有很多搭建教程,如果能跟着教程自己搭建一波就更好了。相信我,你在搭建过程中会遇到各种各样的问题,而这些问题就是你的收获。回到开篇,回到最开始的那个面试题:其实看到这个题我就想到了一直被炮轰的微博。巧的是,这周又吃了一波吴某凡的瓜。我在看女排的直播。当我看到报道的时候,我的表情是这样的:.png)基本上周六晚上这个表情的瓜田里面跳上跳下的真好吃。不过对于凡凡这波,不知道是因为凡凡的流量不好,还是微博的架构经受住了考验。微博还是比较流畅的,没有出现大规模的、非常明显的服务卡顿现象。在我的印象中,最近的一条微博是关于鹿晗和关晓彤的。不是因为我关注他们,而是因为我关注了那天要结婚的程序员。要说丁振凯这位同学,实在是太惨了。结婚的时候偶遇鹿晗,公布恋情。在海外度假时,偶遇双宋官宣。妻子临盆时,他偶遇华晨宇,承认自己与张碧晨私生一女。这次去看了,表现比较平静。应该是一手抱娃,一手扩容,顺便吃瓜。提到鹿晗,微博小助手说是因为一条微博的转发和评论太多了。这不全面,单纯的转发评论,压不住大微博。而那天鹿晗的微博,应该不是他所有微博中转发和评论最多的一条。就是因为并发转发和评论太高太高了,是我这辈子碰不到的即时流量。吃瓜群众也蜂拥而至,短时间内在线率飞速爆发,服务器被秒杀:关于这个问题,在知乎上看到一个评论,觉得很好。截图:https://www.zhihu.com/questio...看看,这一幕是不是和面试官问的问题很像?强如微博,加了1000台服务器来应对这个流量高峰。那么,如果服务宕机了怎么办?重启。重启还是失败怎么办?加钱扩充机器。如果鹿晗关晓彤事件发生,著名狗仔卓伟就能提前爆料,提前采取措施。或许,微博能抵挡住那一波流量高峰。如果吴某签了这件事情,北京警方可以提前和微博沟通,在发布之前通知微博相关人员,哪怕是提前10分钟?或许,吃瓜吃得顺滑丝滑的人会更多。最后一句(求关注)好吧,看到这里再安排一个后续吧,周庚很累,需要一些正反馈。感谢阅读,本人坚持原创,欢迎并感谢您的关注。