基于single flight来解决缓存击穿

目录

    • 1. 缓存击穿
    • 2. 常见解决方案
    • 3.single flight方式
      • 3.1 模拟业务场景
      • 3.2 使用single flight的方式

缓存雪崩、缓存击穿、缓存穿透不单单是缓存领域的经典场景,更是面试当牛马时必备(背)八股文。

我们来讨论下缓存击穿场景下的解决方案。

1. 缓存击穿

高并发场景下,某个缓存到了过期时间,自动失效,导致大量请求在该缓存中查询不到值,会直接请求数据库进行查询,连接过多可能会导致数据库压力过大无法响应,从而导致系统宕机。

2. 常见解决方案

  • 缓存永不过期

既然缓存过期会导致缓存,我们可以让它没机会过期,在设置缓存过期时间时设置为永不过期就好了。

这种方式简单且方便理解,但缺点也明显。

首先缓存本身不是做永久性数据存储,要不然也不会叫做’缓’存,缓存一般使用的是内存,相对于磁盘来说是一种昂贵的资源,当需要缓存的数据很多时,永不过期的方式弊大于利。

从业务层面来说,很多时候缓存的数据都是热门数据,比如说活动页,大促商品,会吸引大量请求,需要缓存缓解数据库压力,如果活动结束,大促结束,这些数据的请求急剧降低,无需在缓存中存在,永不过期就不是理想的方案。

  • 缓存一个空值

缓存失效时,可能是因为DB的数据已删除,为了保证一致性,缓存中的数据也会删除,此时如果大量查询进来,缓存中无数据,也会打到DB。

缓存一个空值对上述场景可减轻DB压力。

  • 加分布式锁

要保证只有一个请求查询DB,显而易见的一个方案就是加锁,对于查询DB的函数,加上分布式锁来控制查询,当有请求获取到锁时,其他请求只能轮询等待。

这样当然也有很大弊端,需要不断的释放锁获取锁,对锁进行整个生命周期的管理。另外加锁也会对查询的并发带来很大的降低。


3.single flight方式

single flight设计思想,Go语言开发者应该都很熟悉,譬如go-zero框架中的, core/syncx/singleflight.go

将并发请求合并成一个请求,以减少对下层服务的压力。

将single flight应用到缓存击穿场景上,基本思想就是:

确保在缓存失效后,只有一个线程去加载数据,其余线程等待该线程完成加载后直接使用其结果。


3.1 模拟业务场景

简单用业务代码模拟一个业务场景:

优先从缓存中查询数据,如果缓存不存在再查询DB,缓存设置一定的过期时间。

代码如下:

public class CacheExpired {private final static JedisPool jedisPool = new JedisPool("localhost", 6379);public static void main(String[] args) {//初始化缓存try (Jedis jedis = jedisPool.getResource()) {jedis.psetex("key", 300, "value");}CacheExpired cacheExpired = new CacheExpired();ExecutorService executorService = Executors.newFixedThreadPool(5);List<Future<String>> futures = new ArrayList<>();for (int i = 0; i < 10; i++) {Future<String> result = executorService.submit(() -> {//先从缓存中获取String s = cacheExpired.loadFromCache();if (s != null) {return s;}//缓存中无数据时,再从DB中获取。s = cacheExpired.loadFromDB();return s;});futures.add(result);}for (Future<String> future : futures) {try {System.out.println(future.get());} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}}}public String loadFromDB() {try {//从db获取数据是个耗时的操作Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//更新缓存try (Jedis jedis = jedisPool.getResource()) {jedis.psetex("key", 200, "value");}return Thread.currentThread().getName() + ":从db获取数据成功";}public String loadFromCache() {try (Jedis jedis = jedisPool.getResource()) {//模拟从缓存中获取数据Thread.sleep(100);String cachedValue = jedis.get("key");if (cachedValue != null) {return Thread.currentThread().getName() + ":从缓存获取数据成功";}} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":缓存中无数据");return null;}
}

当缓存失效时,如上,高并发场景下,DB将承担所有的查询请求,会给DB带来巨大的压力,造成缓存击穿。


3.2 使用single flight的方式

这里讨论使用single flight的方式主要就是为了替换掉加锁的逻辑,需要保证以下两点:

1.只会有一个请求查询DB

2.其他请求需要获取第一个请求查询DB后的数据

总结起来就是等待计算

恰好JDK中包含有支持此逻辑的功能:Future

Future表示异步计算的结果,Future提供了多个方法用来校验执行计算的结果是否完成,并且等待计算的完成,在计算完成之前,会一直阻塞等待。

对于上面要保证的两点,可以使用Map + Future的方式来实现。

Map用来缓存第一次请求,Key是请求参数,Value为Future包装的异步计算的结果。

后续的请求根据Key获取到第一次请求查询封装的Future,然后通过Future.get(),获取第一次查询DB的结果。

如下String singleFlight()

  1. 请求进来时,判断Map中是否有相同的请求
  2. 如果没有包装成FutureTask放入Map中。
  3. 执行FutureTaskrun()方法。
  4. 如果其他请求此时进来,Map中已有相同请求在执行,其他请求会在Future.get()处阻塞等待第一次请求的结果。

为了更好的观测执行效果,我们可以将从redis中获取缓存的逻辑去掉,直接全部请求DB。

public class CacheExpired {//    private final static JedisPool jedisPool = new JedisPool("localhost", 6379);public static void main(String[] args) {//        //初始化缓存//        try (Jedis jedis = jedisPool.getResource()) {//            jedis.psetex("key", 300, "value");//        }CacheExpired cacheExpired = new CacheExpired();ExecutorService executorService = Executors.newFixedThreadPool(5);List<Future<String>> futures = new ArrayList<>();for (int i = 0; i < 10; i++) {Future<String> result = executorService.submit(() -> {//                //先从缓存中获取//                String s = cacheExpired.loadFromCache();//                if (s != null) {//                    return s;//                }//全部从DB中获取。String s = cacheExpired.singleFlight();return s;});futures.add(result);}for (Future<String> future : futures) {try {System.out.println(future.get());} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}}}//存储正在进行或者已完成的请求,如果多个请求同时进来,可保证只有一个请求回去查询DBprivate final ConcurrentHashMap<String, Future<String>> cache = new ConcurrentHashMap<>();public String singleFlight() throws Exception {while (true) {Future<String> future = cache.get("key");if (future == null) {Callable<String> callable = () -> {loadFromDB();return "执行完成";};FutureTask<String> futureTask = new FutureTask<>(callable);future = cache.putIfAbsent("key", futureTask);if (future == null) {future = futureTask;futureTask.run(); // 执行加载任务}}try {return future.get(); // 等待结果} catch (CancellationException e) {cache.remove("key", future);System.out.println(e);} catch (ExecutionException e) {throw new Exception(e.getCause());}}}public void loadFromDB() {try {//从db获取数据是个耗时的操作Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":从db获取数据成功");}
}

执行结果如下,可以看到只有第一次查询请求达到了DB。

当然上述方案也是有缺点的,比如Map中数据存储请求数据的时效,需不需要自动过期删除,Map本身不支持自动过期,需要根据业务需求来处理Map中缓存的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/28835.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI开发】RAG基础

RAG的基本流程&#xff1a; 用一个loader把knowledge base里的知识做成一个个的document&#xff0c;然后把document切分成snippets&#xff0c;把snippets通过embedding&#xff08;比如openai的embedding模型或者huggingface的&#xff09;向量化&#xff0c;存储到vectordb…

git 配置私人令牌

这里写自定义目录标题 获取私人令牌配置个人令牌 获取私人令牌 在个人设置里点击私人令牌选型&#xff0c;之后生成令牌即可。注意&#xff1a;令牌只会出现一次&#xff0c;务必保存好。 配置个人令牌 个人令牌&#xff1a;3c15c866fa61066212a83c66fd8133ba # 进入项目文…

护眼灯对眼睛有伤害吗?千万要小心的行业四大弊端内幕

护眼灯&#xff0c;作为现代家居与办公环境中不可或缺的照明伴侣&#xff0c;因其普及性广受青睐。然而&#xff0c;随着大众对视觉健康意识的逐渐增强&#xff0c;一个疑问悄然滋生&#xff1a;护眼灯对眼睛有伤害吗&#xff1f;这一问题不仅触及到了人们对视觉健康的深层担忧…

MLOps模型部署的三种策略:批处理、实时、边缘计算

机器学习运维&#xff08;MLOps&#xff09;是一组用于自动化和简化机器学习&#xff08;ML&#xff09;工作流程和部署的实践。所选择的部署策略可以显著影响系统的性能和效用。所以需要根据用例和需求&#xff0c;采用不同的部署策略。在这篇文章中&#xff0c;我们将探讨三种…

Java家政预约系统源码 家政上门APP源码 家电安装、维修、清洗、美容系统源码、家政系统各端功能细分

Java家政预约系统源码 家政上门APP源码 家电安装、维修、清洗、美容系统源码、家政系统各端功能细分 家政服务系统是一种提供家政服务的系统&#xff0c;它可以为客户提供上门家庭清洁、钟点工、保姆、月嫂、育婴师、护理员等家政服务。节省时间和成本&#xff0c;提高效率&…

网络层只懂路由?这9个知识点被严重低估了

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 下午好&#xff0c;我的网工朋友。 网络层想必你已经耳熟能详&#xff0c;它的作用自然是不容小觑。 它负责将数据从源头准确地投递到目的地&am…

onnx基本概念

onnx基本概念 参考 文章目录 onnx基本概念Input, Output, Node, Initializer, AttributesSerialization with protobuf元数据List of available operators and domains支持的类型Opset版本Subgraphs, tests and loopsExtensibilityFunctionsShape (and Type) Inferencetools O…

编码在网络安全中的应用和原理

前言:现在的网站架构复杂&#xff0c;大多都有多个应用互相配合&#xff0c;不同应用之间往往需要数据交互&#xff0c;应用之间的编码不统一&#xff0c;编码自身的特性等都很有可能会被利用来绕过或配合一些策略&#xff0c;造成一些重大的漏洞。 什么是编码&#xff0c;为什…

UniVue更新日志:使用Carousel组件实现轮播图效果

github仓库 稳定版本仓库&#xff1a;https://github.com/Avalon712/UniVue 开发版本仓库&#xff1a;https://github.com/Avalon712/UniVue-Develop UniVue扩展框架-UniVue源生成器仓库&#xff1a;https://github.com/Avalon712/UniVue-SourceGenerator 更新说明 今天的更…

吴恩达深度学习笔记:机器学习(ML)策略(1)(ML strategy(1))1.11-1.12

目录 第三门课 结构化机器学习项目&#xff08;Structuring Machine Learning Projects&#xff09;第一周 机器学习&#xff08;ML&#xff09;策略&#xff08;1&#xff09;&#xff08;ML strategy&#xff08;1&#xff09;&#xff09;1.11 超过人的表现&#xff08;Surp…

报错:C1189#error: The <experimental/filesystem> header providing 解决方案

今天开发过程中&#xff0c;需要使用文件系统experimental/filesystem&#xff0c;报错C1189#error: The &#xff1c;experimental/filesystem&#xff1e; header providing &#xff0c;通过以下解决方案&#xff0c;成功运行程序。 目录 一、打开项目下的属性 二、选择C/…

算法02 递归算法及其相关问题【C++实现】

递归 在编程中&#xff0c;我们把函数直接或者间接调用自身的过程叫做递归。 递归处理问题的过程是&#xff1a;通常把一个大型的复杂问题&#xff0c;转变成一个与原问题类似的&#xff0c;规模更小的问题来进行求解。 递归的三大要素 函数的参数。在用递归解决问题时&…

VMware挂载NAS存储异常处理

问题概述 由于非法关机或恢复&#xff0c;NFS存储可能会出现以下问题&#xff1a; 数据存储处于挂起状态或无法正常识别。虚拟机的配置文件或虚拟磁盘仍然注册在异常数据存储上。系统误认为有虚拟机在使用该数据存储。 问题对策 下面是详细的排查步骤和解决对策&#xff1a…

PFA烧杯带把手带刻度1000ml3000mlPFA氟树脂温度范围-270~250℃

随着越来越多的痕量分析实验需要对ppb和ppt级的浓度进行测定。目前所使用的一般材料由于无特别处理&#xff0c;不可避免会与所储存的样品&#xff0c;试剂或标准液反应&#xff0c;导致痕量分析实验得到不正确的结果。但我厂的PFA产品刚好能弥补其不足。PFA金属元素空白值低&a…

前端锚点 点击 滑动双向绑定

一. 页面样式 二. 代码 <div class"flexBox"><div class"mdDiv" v-for"(item,index) in tabList" :key"index" :class"nowChooseindex?choosed:" click"jumpMD(index, item.id)">{{item.name}}&l…

风光储一体化园区 | 图扑新能源可视化

随着全球能源结构转型加速&#xff0c;可再生能源成为能源发展的重要方向。风能、太阳能作为清洁、绿色的能源&#xff0c;得到了广泛的开发和应用。与此同时&#xff0c;储能技术的发展为解决风能和太阳能发电的间歇性和波动性问题提供了有效途径。 风光储园区作为整合风电、…

CAD中如何在线加载卫星影像?

CAD在各行各业都有着非常广泛的应用&#xff0c;但在规划设计等行业中&#xff0c;不可避免地需要加载高清卫星影像&#xff0c;从而有利于作规划设计时参考。 现在&#xff0c;就为你分享一种在CAD中在线加载卫星影像的方法&#xff0c;如果需要CAD安装包&#xff0c;请在文末…

解决外网404:清除DNS缓存并配置host主机使用知名公共DNS服务

在 Windows 上清除/刷新 DNS 缓存 对于所有Windows版本&#xff0c;清除DNS缓存的过程都是相同的。你需要使用管理员权限打开命令提示符并运行ipconfig /flushdns。 浏览器清除DNS缓存 大多数现代的Web浏览器都有一个内置的DNS客户端&#xff0c;以防止每次访问该网站时…

白酒:茅台镇白酒的酒厂社会责任与可持续发展

云仓酒庄豪迈白酒&#xff0c;作为茅台镇的品牌&#xff0c;不仅在产品品质和口感方面有着卓着的表现&#xff0c;在酒厂社会责任和可持续发展方面也做出了积极的探索和实践。 首先&#xff0c;云仓酒庄豪迈白酒注重环境保护和资源利用。酒厂在生产过程中严格控制能源消耗和排放…

Codesys 获取系统年、月、日、时、分、秒、星期几 +解决时区问题+ ST语言编程实现代码

一、 效果如图所示 二、功能说明 发现获取的时间比北京时间多一个时区&#xff08;8个小时&#xff09;&#xff0c;解决时区问题获取时间后&#xff0c;单独把年月日时分秒提取出来&#xff0c;单独保存在变量中获取星期几&#xff0c;保存在变量中 三、Codesys用ST语言实现…