互联网大厂的缓存策略:抵抗超高并发的秘密武器,已开源!

· 浏览次数 : 0

小编点评

本文主要讨论了互联网大厂项目中缓存技术的应用,特别以秒杀系统为例,探讨了缓存的核心诉求、使用场景、设计技巧、混合缓存方案、缓存刷新机制以及数据一致性等方面。 **主要内容概述如下**: 1. **[前言]**: 介绍了缓存的认识局限性和秒杀系统对缓存的高要求,强调了缓存在秒杀系统中的重要性。 2. **[秒杀系统缓存核心诉求]**: 讨论了秒杀系统在面对高并发流量时的缓存设计,以及在缓存架构中采用本地缓存和分布式缓存的必要性。 3. **[秒杀系统缓存使用场景]**: 详细分析了秒杀系统中缓存策略的应用,以及如何对接口进行缓存设计。 4. **[混合型缓存设计]**: 阐述了秒杀系统在缓存设计中采用本地缓存和分布式缓存的混合型结构,并解释了其优点和缓存交互流程。 5. **[缓存刷新机制]**: 从本地缓存和分布式缓存两个方面讨论了缓存数据的刷新策略,并提出了实现方案。 6. **[数据一致性]**: 涉及到秒杀系统中数据一致性的问题,并对比了强一致性保证和弱一致性保证在秒杀系统中的应用。 7. **[缓存落地实现]**: 详细说明了如何在秒杀系统中实现本地缓存和分布式缓存的结合,包括配置和代码扩展性方面的考虑。 总的来说,缓存技术在现代互联网大厂项目中扮演着重要的角色,尤其是在秒杀系统这样的关键场景中。本文不仅系统地分析了缓存在秒杀系统中的应用,还提供了实用的缓存方案和设计技巧,对于想要深入了解缓存技术的开发者具有很好的参考价值。

正文

大家好,我是冰河~~

最近,有小伙伴私信我:冰哥,我最近出去面试,面试官问我如何设计缓存能让系统在百万级别流量下仍能平稳运行,我当时没回答上来。接着,面试官问我之前的项目是怎么使用缓存的,我说只是缓存了一些数据。当时确实想不到缓存还有哪些用处,估计这次面试是挂了。冰哥,你可以给我讲讲互联网大厂项目是怎么设计和使用缓存的吗?

另外,本文缓存方案已经开源,开源地址如下,如果开源方案对你有点帮助或者启发,欢迎在代码仓库给个Star,让更过的小伙伴看到它,互相学习,一起进步。

一、前言

通过这位小伙伴的自述,我们明显感受到这位小伙伴对缓存的认识还是停留在简单的存储数据上,没有对使用缓存背后的场景和实现逻辑进行深层次的思考。在互联网大厂项目中,缓存也是一种必不可少的组件,那使用缓存仅仅是为了缓存热点数据,提升读性能吗?如果你对缓存的认识只是停留在这里,那就未免太浅显了。

今天,我们就以高并发、大流量业务场景中最具代表性的 秒杀系统 为例,采用市面上大家都比较熟悉的技术,一起探究下 秒杀系统 背后是如何设计和使用缓存的。

二、秒杀系统缓存核心诉求

秒杀系统在承接瞬时高并发流量时,如果将流量直接打到数据库,那数据库很有可能因为扛不住瞬间的高并发流量而导致崩溃和宕机。所以,需要对秒杀系统进行极致的缓存设计,让大部分流量走缓存。同时,在设计缓存架构方案时,为了进一步提升性能,将采用 本地缓存+分布式缓存的混合型缓存 设计方案,让本地缓存抗大部分流量,分布式缓存次之,数据库再次之,如图1所示

并且针对秒杀系统这种瞬时并发量高的场景,在设计缓存时,需要注意的技巧:优先读取本地缓存数据,如果本地缓存失效,则读取分布式缓存数据,并且在同一时刻,只能有一个线程更新本地缓存,防止缓存击穿。没有获取到本地缓存更新机会的其他线程,需要立即返回而不是原地等待。如果分布式缓存失效时,在同一时刻,也只能有一个线程更新分布式缓存,防止缓存击穿。没有获取到分布式缓存更新机会的线程,也需要立即返回而不是原地等待。

另外,需要注意的是:我们提出了采用 本地缓存+分布式缓存的混合型缓存设计方案,后文会着重对这种设计进行说明。

三、秒杀系统缓存使用场景

秒杀系统属于典型的读多写少的高并发系统,应对这种场景的一个有效措施就是使用缓存,不管是单机JVM缓存还是以Redis为例的分布式缓存,其读写性能都会比数据库高得多。所以,在秒杀系统中,为了应对高并发、大流量的业务场景,缓存自然也就成为建设秒杀系统过程中必不可少的环节。

3.1 秒杀系统接口分析

在秒杀系统中,主要是对一些读数据的接口设计缓存策略,而在这些读数据的接口中,获取秒杀活动列表、获取秒杀活动详情、获取秒杀商品列表和获取秒杀商品详情的接口流量比其他接口高。尤其是获取秒杀商品列表和获取秒杀商品详情的接口QPS一般会高于获取秒杀活动列表和秒杀活动详情的接口,毕竟大部分用户在秒杀开始前就已经进入到秒杀详情页,当然这也不是绝对的,还是要看秒杀系统对于这些接口的设计。

3.2 秒杀系统缓存场景

尽管获取秒杀商品列表和获取秒杀商品详情的接口QPS一般会高于获取秒杀活动列表和秒杀活动详情的接口,但是我们在设计缓存时,需要对这些接口一视同仁,都要以严格的高标准来设计这些接口,不然稍有不慎,一个接口出现问题,就可能导致整场秒杀活动以失败告终。秒杀系统缓存的使用场景如图2所示。

所以,在秒杀系统中,会对获取秒杀活动列表、获取秒杀活动详情、获取秒杀商品列表和获取秒杀商品详情的接口设计缓存策略。

四、混合型缓存设计

总体来说,在设计秒杀系统的缓存过程中,会采用 本地缓存+分布式缓存的混合型缓存 设计方案。其中,本地缓存指的就是单机缓存,比如JVM内存缓存,单机Cache缓存。分布式缓存指的是以分布式的方式集中管理的缓存,比如Memcached、Redis等,如图3所示。

4.1 抗流量洪峰

良好的缓存设计不仅仅能够提升系统的总体性能,还能作为抗瞬时流量洪峰的有效防线。可以这么说,如果整个秒杀系统前置的流量管控、流量清洗和限流等是秒杀系统流量洪峰的第一道防线,则本地缓存就是抗流量洪峰的第二道防线,而分布式缓存就是第三道防线,如图4所示。

使用缓存能够抗一定的流量洪峰,经过前置的流量管控、流量清洗和限流等措施的第一道防线、本地缓存的第二道防线、分布式缓存的第三道防线,真正进入数据库的流量就会比较小了。

4.2 缓存集群方案

从缓存集群模式的角度去分析,每台服务器甚至JVM实例都会拥有自己独立的本地缓存,在承载大并发流量时,,以本地缓存为主,分布式缓存次之,如图5所示。

可以看到,从缓存的集群模式角度来看,每台服务器都会自己独立本地缓存,除了前置的流程管控、流量清洗和限流等措施构筑的流量洪峰第一道防线外。本地缓存会承接剩余的大部分流量,构筑成流量洪峰的第二道防线,而分布式缓存则是流量洪峰的第三道防线。并且在缓存的设计上,分布式缓存的作用主要是协调和同步最新数据到本地缓存。

也就是说,只有本地缓存失效时,才会访问分布式缓存,将分布式缓存中的数据更新到本地缓存中,并且同一时刻只能有一个线程对本地缓存进行更新操作,以避免多个线程并发更新本地缓存。同样的,如果分布式缓存失效,则同一时刻只能有一个线程访问数据库来获取对应的数据,并将其更新到分布式缓存。

在集群模式下,我们应该尽最大努力将流量拦截在本地缓存,避免过多的请求访问分布式缓存,提高秒杀系统的性能,并且降低秒杀系统由于大量的远程IO导致的各种风险。

4.3 缓存交互流程

采用本地缓存+分布式缓存的混合型缓存架构设计方案时,在读取缓存数据时,会优先读取本地缓存的数据,如果本地缓存未开启,或者已经失效,此时就会使用分布式缓存,也就是说,优先读取本地缓存中的数据,如果本地缓存未开启或者缓存数据失效,则读取分布式缓存中的数据,如图6所示。

可以看到,只有在本地缓存未开启或者缓存失效的情况下,才会去访问分布式缓存,读取分布式缓存中的数据,并且在同一个时刻只能有一个线程更新本地缓存中的数据,这种方式可以最大限度减少远程IO为秒杀系统带来的风险。具体的流程如下所示。

(1)判断本地缓存是否开启,如果开启则进行第2步,否则进行第4步。

(2)判断本地缓存是否失效,如果未失效,则进行第3步,否则进行第4步。

(3)读取本地缓存数据,读取缓存流程结束。

(4)判断分布式缓存是否开启,如果开启则进行第5步,否则进行第7步。

(5)判断分布式缓存是否失效,如果未失效,则进行第6步,否则进行第7步。

(6)读取分布式缓存数据,同一时刻只有一个线程更新本地缓存数据,读取缓存流程结束。

(7)读取数据库数据,同一时刻只有一个线程更新分布式缓存数据,读取缓存流程结束。

这里,有一个设计技巧需要大家注意:如果本地缓存失效,并且某个线程没有获取到更新本地缓存的机会,这个线程需要立即返回而不是在原地阻塞等待,这种方式可以最大限度的节省服务器资源和线程切换的成本,尤其是像在秒杀系统这种承接瞬时高并发流量的系统中,这种设计能够节省不少服务器资源。这种线程未获取到更新数据的机会而快速返回的机制,需要客户端配合在适配处理,也就是说,客户端对这种情况需要进行静默处理,不要提示错误信息,也不做其他处理,稍后重新调用接口进行重试即可。

4.4 混合型缓存设计的优点

采用本地缓存+分布式缓存的混合型缓存架构设计方案存在诸多的优点。其中,本地缓存一个很大的优势就在于不会发生远程IO操作,性能更高,有利于服务的横向伸缩,大部分请求会命中本地单机缓存。这里,我们可以从整体的请求链路上进行分析。

例如,当前请求链路上需要读取5次分布式缓存中的数据,这样,如果秒杀系统承接了100万的请求,则会产生500万读取分布式缓存的IO操作。这成倍的IO风险对于秒杀系统来说,是绝对不能忽视的风险因素,如图7所示。

可以看到,一次请求会访问5次分布式缓存,这在无形当中就增加了分布式缓存的IO成本,这对秒杀系统来说,是不容忽视的风险项,稍有不慎,则系统可能会由于IO瓶颈引发各种事故,最终造成系统崩溃或者宕机。所以,在设计秒杀系统时,一定要注意这种放大效应带来的风险。所以,在高并发大流量的场景下,很有必要精心的设计本地缓存。

五、缓存刷新机制

数据存放到缓存中,并不是一成不变的,也不会永久存放到缓存中。也就是说,存放到缓存中的数据终归是要失效或者过期的,也就是存放到缓存中的数据会有相应的生命周期,为此需要以一定的策略对缓存中的数据进行刷新操作,以防止缓存中的数据长时间过期而导致大部分流量直接打入数据库。本节,就从本地缓存和分布式缓存两个角度简单聊聊缓存的生命周期。

5.1 本地缓存刷新机制

假设本地缓存基于Guava Cache实现,在设计本地缓存时,本地缓存的容量不宜过大,有效时长不宜过大,并且在设计本地缓存时,可以基于版本号机制来实现缓存的失效策略。

对于本地缓存会实现两种刷新机制:

(1)主动刷新

请求接口传入的版本号如果大于本地缓存中的版本号,说明本地缓存已经失效,此时,就需要从分布式缓存中重新获取数据进行刷新。

(2)被动刷新

本地缓存自动过期,被动从缓存中移除,此时,需要从分布式缓存中重新获取数据进行刷新。

5.2 分布式缓存刷新机制

假设分布式缓存基于Redis实现,对于分布式缓存来说,也需要设置缓存的过期时间,不能让缓存数据永久性驻留到Redis中。相比于本地缓存来说,分布式缓存的过期时间要稍微长一些,并且分布式缓存在刷新机制上与本地缓存略有不同。

(1)主动刷新

业务数据变更驱动刷新分布式缓存数据。当业务数据发生变更时,会主动刷新分布式缓存中的数据。

(2)被动刷新

可以基于Redis提供的缓存过期策略,比如基于LRU、TTL等策略淘汰缓存中的数据。后续在访问分布式缓存中的数据时,如果检测到分布式缓存中的数据已经过期,则会使用一个线程来刷新分布式缓存中的数据。

六、数据一致性

可以这么说,只要系统中使用了缓存,就或多或少会涉及到数据一致性的问题,在秒杀系统中,数据一致性的问题主要包括:本地缓存与分布式缓存数据一致性问题,缓存与数据库数据一致性问题。同时,在数据一致性保证方面,就包括强一致性保证和弱一致性保证。

6.1 强一致性保证

CAP理论为数据的强一致性奠定了理论基础,但是CAP理论下的数据强一致性,很难做到既保证系统高性能的同时,又要保证数据的绝对一致。在秒杀系统的设计中,我们会将数据的强一致性保证交给数据库和业务规则来实现,在业务规则层面结合数据库来实现强一致。

例如,假设用户在抢购秒杀商品中,缓存中存在商品库存,通过了缓存中的校验逻辑。在真正下单时,还要校验数据库中的商品库存,如果此时数据库中已经没有商品剩余库存了,则终止下单逻辑,提示用户商品已售罄。

6.2 弱一致性保证

强一致性保证交由业务规则和数据库共同约束实现,缓存层面的数据就可以实现为弱一致性。也就是说,在很小的一段时间内,允许缓存中的数据存在延迟,允许缓存中的数据与数据库中的数据在短时间内的不一致,只要在可接受的时间范围内最终达到一致即可。充分发挥缓存的实际作用,即:缓存数据,提供系统的读写性能和抗系统流量。

七、缓存落地实现

在秒杀系统中本地缓存和分布式缓存相结合,能够抗住进入秒杀系统内部的大部分流量。并且在技术选型上,假设本地缓存默认基于Guava Cache实现,分布式缓存默认基于Redis实现。并且本地缓存不仅仅只是支持Guava Cache,分布式缓存不仅仅只是支持Redis,在代码层面,都是面向接口编程,而非面向具体实现类编程,不管是本地缓存还是分布式缓存,都可以根据简单的配置切换具体的实现方式。

7.1 扩展性描述

代码具备良好的扩展性,后续维护和升级的成本就比较低。相反,如果代码写的杂乱无章,犹如“屎山”,那后期维护起来是相当痛苦的,谁也不想天天面对着一堆“屎山”,哪来有问题改哪里。所以,从一开始写的代码就要有良好的扩展性,方便后期的维护和升级。

假设秒杀系统整体基于SpringBoot+SpringCloud Alibaba技术栈实现,那如何写代码具备良好的扩展性呢?总体的原则就是面向接口编程,而非面向具体的实现类编程,具体业务逻辑里依赖的是接口,而非实现类,在接口不变的前提下,可以随时切换具体的实现类,也可以随时新增接口的实现类。业务中可以根据配置加载接口的某个具体实现类。

7.2 本地缓存落地实现

本地缓存的落地实现示意图如图8所示。

可以看到,具体秒杀业务中会依赖本地缓存的接口,而非具体的实现类。本地缓存的接口可以有多个实现类,在秒杀业务中可以根据具体的配置项指定要加载并使用哪个实现类,也可以根据具体的需求和业务场景随时新增本地接口的实现类,大大提高了程序的扩展性。

7.3 分布式缓存落地实现

分布式缓存的落地实现示意图如图9所示。

可以看到,分布式缓存在扩展性方面的设计与本地缓存类似,同样是秒杀系统在具体业务中依赖分布式缓存的接口,而非分布式缓存的具体实现类。分布式缓存的接口可以有多个实现类,在秒杀业务中可以根据具体的配置项加载并实例化具体的实现类,也可以根据具体的需求和业务场景新增分布式缓存接口的实现类,提高了实现分布式缓存程序的扩展性。

八、总结

缓存不仅仅可以用来存储热点数据,提升热点数据的读性能,还是业务系统中抗高并发、大流量的利器。以秒杀系统为例,采用本地缓存+分布式缓存的混合型缓存方案时,如果整个秒杀系统前置的流量管控、流量清洗和限流等是秒杀系统流量洪峰的第一道防线,则本地缓存就是抗流量洪峰的第二道防线,而分布式缓存就是第三道防线,经过层层流量过滤,最终进入数据库的流量就比较可控了。

同时,引入本地缓存+分布式缓存的混合型缓存方案后,要考虑缓存的刷新机制,数据一致性问题,在代码落地的过程中,还要最大程度避免缓存穿透、击穿和雪崩问题,并实现代码的高度可扩展性。

在提供的开源方案中,已经解决了缓存穿透、击穿和雪崩问题,开源地址如下:

如果开源方案对你有点帮助或者启发,欢迎在代码仓库给个Star,让更过的小伙伴看到它,互相学习,一起进步。

好了,今天就到这儿吧,我是冰河,我们下期见~~

与互联网大厂的缓存策略:抵抗超高并发的秘密武器,已开源!相似的内容:

互联网大厂的缓存策略:抵抗超高并发的秘密武器,已开源!

大家好,我是冰河~~ 最近,有小伙伴私信我:冰哥,我最近出去面试,面试官问我如何设计缓存能让系统在百万级别流量下仍能平稳运行,我当时没回答上来。接着,面试官问我之前的项目是怎么使用缓存的,我说只是缓存了一些数据。当时确实想不到缓存还有哪些用处,估计这次面试是挂了。冰哥,你可以给我讲讲互联网大厂项目是

架构与思维:秒杀和竞拍的业务架构,永不过时的话题

1 互联网架构越来越复杂? 为啥感觉互联网架构越来越复杂了,早期我们的系统,可能也就那么少部分人使用,大都是一些后台管理系统。 所以不用考虑很多东西,比如: 流量少,无需考虑并发问题 数据少,不用考虑什么索引优化、分库分表 访问不集中,不用考虑缓存、过载保护 如果数据不重要,不用考虑安全策略,甚至不

面试官:你了解git cherry-pick吗?

事情要从一次不规范的代码开发开始说起 背景故事 时间 2024年某个风平浪静的周五晚上 地点 中国,北京,西二旗,某互联网大厂会议室 人物 小杰,小A,小B,老K 对话 老K:昨天提交的代码被测试打回来了!为什么小B没开发完的内容也一起提交上去了? 小B:啊?我不清楚啊,我在开发分支B开发完一部分就

[转帖]国内常用镜像加速源分享

国内常用镜像加速源分享 我们经常在下载一些 Python 或 Java等包的时候总是会访问国外的资源站,但是墙内访问国外的网络一般都比较卡。这里作者搜集了一些常用的国内镜像源,涵盖一些互联网大厂和知名高校的repo, 用得上的可以收藏备用哦~ Python相关镜像 (1) pip使用说明 对于Pyt

阿里400+天,我为什么离开阿里

阿里还是挺不错了,感谢公司,感谢同事们! 零丶前言 今天是我在阿里的lastday,明天我将回成都(此处嘴角弯,我爱成都),端午后入职另外一家互联网大厂。 在去年3月份的时候,我从成都的某家金融科技银行跳槽到杭州阿里巴巴淘天集团,这篇《跳槽!阿里工作100+天,菜鸡职业生涯的一点记录》记录了我跳槽动

【程序员日记】---从业务编排到低代码

之前总聊微服务,今天换一个话题---低代码。低代码这个词也是最近这几年很火的概念,尤其是遇到大环境下行,很多大厂和互联网那个公司也在慢慢在低代码方向发力,当然,对于传统项目交付型的软件公司,低代码也具有相当大的吸引力。

唱衰这么多年,PHP 仍然还是你大爷!

PHP 是个庞然大物。 尽管有人不断宣称 PHP “即将消亡”。 但无法改变的事实是:互联网依然大量依赖 PHP。本文将通过大量的数据和事实告诉你为何 PHP 仍然在统治着互联网,你大爷仍然还是你大爷。 统计数据 PHP 仍然是首选编程语言 根据 W3 Techs 对全球前 1000 万个网站使用的

【进阶篇】基于 Redis 实现分布式锁的全过程

这一篇文章拖了有点久,虽然在项目中使用分布式锁的频率比较高,但整理成文章发布出来还是花了一点时间。在一些移动端、用户量大的互联网项目中,经常会使用到 Redis 分布式锁作为控制访问高并发的工具。

产品经理如何向最终用户提供创新的数据体验

本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 随着互联网和大数据技术的发展,越来越多的企业开始重视数据的价值。伴随着企业数字化转型的大趋势,数据分析需求将进一步爆发。据国际权威调研机构的报告预测,在未来两年内,企业

揭露 FileSystem 引起的线上 JVM 内存溢出问题

作者:来自 vivo 互联网大数据团队-Ye Jidong 本文主要介绍了由FileSystem类引起的一次线上内存泄漏导致内存溢出的问题分析解决全过程。 内存泄漏定义(memory leak):一个不再被程序使用的对象或变量还在内存中占有存储空间,JVM不能正常回收改对象或者变量。一次内存泄漏似乎