【Java】缓存击穿解决方案
文章目录
- 什么是SingleFlight?
- 优化
- 缺点
- 优化策略
什么是SingleFlight?
SingleFlight是go语言中sync包中的一个东西。它用于确保在并发环境下某个操作(例如,函数调用)即使被多个goroutine同时请求,也只会被执行一次。这对于防止重复的、昂贵的操作(如数据库查询、HTTP请求等)被不必要地多次执行是非常有用的。
使用 sync.SingleFlight,可以确保对于同一个键的并发请求,在缓存失效的情况下,只有一个请求会去加载数据(例如从数据库中),而其他并发的请求会等待这个加载操作完成,并共享相同的结果。这样,即便缓存失效,也不会因为大量的并发请求而对数据库或后端服务产生压力。
具体来说,当缓存失效时,第一个到达的请求会触发数据加载的操作(如数据库查询),而其他同时到达的请求会等待这个操作的完成。一旦数据被加载,它会被返回给所有等待的请求,并重新被放入缓存中。这个过程中,sync.SingleFlight 保证了数据加载函数只被调用一次,避免了不必要的重复处理。
SingleFlight主要提供以下功能:
● Do(key string, fn func() (interface{}, error)): 这是SingleFlight最核心的方法。当多个goroutine同时调用Do方法时,只有一个会真正执行传入的fn函数,其它等待这个函数执行完成。执行完成后,返回的结果和错误将会被返回给所有调用Do方法的goroutine。这里的key是用来区分不同操作的唯一标识。
● DoChan(key string, fn func() (interface{}, error)): 与Do类似,但它返回一个channel,你可以从这个channel中读取执行结果。
● Forget(key string): 这个方法用于清除SingleFlight中缓存的结果,以便于同一个key对应的函数在未来可以再次被执行。
● DupSuppressed() int64: 返回被SingleFlight机制抑制的重复调用次数。
SingleFlight的一个常见用途是缓存层,避免在缓存失效时由于缓存击穿而导致大量请求直接落到数据库。
如下是在写go语言的时候的使用SingleFight解决缓存击穿的代码。
var g singleflight.Groupfunc getCachedData(key string) (data interface{}, err error) {// 使用Do方法确保对于同一个key的请求,函数只会被执行一次v, err, _ := g.Do(key, func() (interface{}, error) {// 这里是实际的获取数据的操作,比如从数据库读取return getDataFromDatabase(key)})return v, err
}func getDataFromDatabase(key string) (interface{}, error) {// 模拟数据库操作// ...return data, nil
}
优化
SingleFlight 是一种用于减少重复工作的工具,特别是在并发编程中处理类似缓存击穿这样的问题时。尽管它非常有用,但也有一些潜在的缺点和限制:
缺点
- 资源锁定:如果用于一个长时间运行的操作,SingleFlight 会阻止其他所有相关的请求,直到这个操作完成。这可能导致长时间的等待,特别是在操作非常耗时的情况下。
- 错误传播:如果共享的操作因为某些原因失败了,这个错误会被传播给所有等待的请求。在某些情况下,单独重试可能更合适。
- 内存压力:在高并发情况下,如果许多不同的键被请求,SingleFlight 结构可能占用大量内存。
- 不适合高变动数据:对于频繁变化的数据,使用 SingleFlight 可能不太有效,因为一旦数据被缓存,就需要等待旧数据失效才能获取新数据。
- 分布式锁:缺乏对分布式场景下的解决方案
优化策略
- 设置超时:为 SingleFlight 中的操作设置合理的超时时间,可以防止一个慢操作阻塞其他请求过长时间。
- 错误重试机制:对于某些操作,特别是网络请求等,实现自动重试逻辑可能会有帮助,而不是直接将一个失败共享给所有请求。
- 限制并发数量:可以对 SingleFlight 正在进行的操作数量设置上限,以减少内存压力。
- 数据版本控制:对于频繁变化的数据,可以结合数据版本控制,确保即使在数据更新的时候也能获取到最新的数据。
对上面的缺点进行优化,得到如下Java代码。
package blossom.project.towelove.common.utils;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;/*** @author: ZhangBlossom* @date: 2024/1/9 22:06* @contact: QQ:4602197553* @contact: WX:qczjhczs0114* @blog: https://blog.csdn.net/Zhangsama1* @github: https://github.com/ZhangBlossom* @description: 对于go语言中解决缓存击穿工具SingleFlight的实现*/
public abstract class SingleFlight<K, V> {private final ConcurrentHashMap<K, CompletableFuture<V>> ongoingOperations = new ConcurrentHashMap<>();/*** 版本号控制,使得每次进行更新的时候,一定都是对最新数据进行更新*/private final ConcurrentHashMap<K, AtomicLong> versions = new ConcurrentHashMap<>();// 设置默认超时时间,例如3秒private final long defaultTimeout = 3 * 1000;protected abstract boolean acquireDistributedLock(K key);protected abstract void releaseDistributedLock(K key);/*** 更新指定键的版本号** @param key 键*/public void updateVersion(K key) {versions.compute(key, (k, version) -> {if (version == null) {return new AtomicLong(1);} else {version.incrementAndGet();return version;}});}/*** 获取指定键的当前版本号** @param key 键* @return 版本号*/private long getVersion(K key) {return versions.getOrDefault(key, new AtomicLong(0)).get();}/*** 确保对于同一个键,相关的操作只会被执行一次,并且其结果将被所有调用者共享.* 如果超时了没有complete,将会返回TimeoutException** @param key 唯一key* @param function 要执行的方法函数* @param timeout 超时时间,单位为ms,默认3000ms=3s* @return*/public CompletableFuture<V> doOrTimeout(K key, Function<K, V> function, long timeout, boolean useDistributedLock) {if (useDistributedLock && !acquireDistributedLock(key)) {CompletableFuture<V> future = new CompletableFuture<>();future.completeExceptionally(new IllegalStateException("Unable to acquire distributed lock"));return future;}try {long versionAtCallTime = getVersion(key);return ongoingOperations.compute(key, (k, existingFuture) -> {if (existingFuture == null || getVersion(k) != versionAtCallTime) {CompletableFuture<V> future = new CompletableFuture<>();CompletableFuture.runAsync(() -> {try {V result = function.apply(k);future.complete(result);} catch (Exception e) {future.completeExceptionally(e);} finally {ongoingOperations.remove(k);}});// 应用超时设置return future.orTimeout(timeout, TimeUnit.MILLISECONDS);}return existingFuture;});} finally {if (useDistributedLock) {releaseDistributedLock(key);}}}/*** 当前方法会异步执行任务,并保证只有一个key能执行function任务,其他任务进行等待* 同时,如果执行失败,那么允许设定重试次数。并且再次执行function方法。** @param key 执行方法唯一key* @param function 要执行的任务* @param retries 重试次数* @param timeout 超时时间* @param delayBetweenRetries 重试前延迟时间* @return*/public CompletableFuture<V> doOrRetry(K key, Function<K, V> function, int retries, long timeout,long delayBetweenRetries, boolean useDistributedLock) {if (useDistributedLock && !acquireDistributedLock(key)) {CompletableFuture<V> future = new CompletableFuture<>();future.completeExceptionally(new IllegalStateException("Unable to acquire distributed lock"));return future;}try {long versionAtCallTime = getVersion(key);return ongoingOperations.compute(key, (k, existingFuture) -> {if (existingFuture == null || getVersion(k) != versionAtCallTime) {CompletableFuture<V> future = new CompletableFuture<>();executeWithRetriesOrCompensate(future, key, function, null, retries, timeout, delayBetweenRetries, versionAtCallTime);return future;}return existingFuture;});} finally {if (useDistributedLock) {releaseDistributedLock(key);}}}/*** 当前方法会异步执行任务,并保证只有一个key能执行function任务,其他任务进行等待* 同时,如果执行失败,那么允许设定重试次数。并且执行compensation补偿方法。** @param key 执行方法唯一key* @param function 原有方法* @param compensation 补偿方法 在执行失败的时候执行* @param retries 重试次数* @param timeout 超时时间* @param delayBetweenRetries 重试前延迟时间* @return*/public CompletableFuture<V> doOrCompensate(K key, Function<K, V> function, Function<K, V> compensation,int retries, long timeout, long delayBetweenRetries,boolean useDistributedLock) {if (useDistributedLock && !acquireDistributedLock(key)) {CompletableFuture<V> future = new CompletableFuture<>();future.completeExceptionally(new IllegalStateException("Unable to acquire distributed lock"));return future;}try {long versionAtCallTime = getVersion(key);return ongoingOperations.compute(key, (k, existingFuture) -> {if (existingFuture == null || getVersion(k) != versionAtCallTime) {CompletableFuture<V> future = new CompletableFuture<>();executeWithRetriesOrCompensate(future, key, function, compensation, retries, timeout,delayBetweenRetries, versionAtCallTime);return future;}return existingFuture;});} finally {if (useDistributedLock) {releaseDistributedLock(key);}}}/*** @param future* @param key* @param function* @param compensation* @param retries* @param timeout* @param delayBetweenRetries* @param versionAtCallTime*/private void executeWithRetriesOrCompensate(CompletableFuture<V> future,K key, Function<K, V> function, Function<K, V> compensation,int retries, long timeout, long delayBetweenRetries,long versionAtCallTime) {CompletableFuture.runAsync(() -> {try {if (getVersion(key) != versionAtCallTime) {throw new IllegalStateException("Data version changed");}V result = function.apply(key);future.complete(result);} catch (Exception e) {if (retries > 0 && getVersion(key) == versionAtCallTime) {try {TimeUnit.MILLISECONDS.sleep(delayBetweenRetries);} catch (InterruptedException ignored) {}Function<K, V> nextFunction = (compensation != null) ? compensation : function;executeWithRetriesOrCompensate(future, key, nextFunction, compensation, retries - 1,timeout, delayBetweenRetries, versionAtCallTime);} else {future.completeExceptionally(e);}}}).orTimeout(timeout, TimeUnit.MILLISECONDS).exceptionally(ex -> {if (retries > 0 && ex instanceof TimeoutException && getVersion(key) == versionAtCallTime) {Function<K, V> nextFunction = (compensation != null) ? compensation : function;executeWithRetriesOrCompensate(future, key, nextFunction, compensation, retries - 1, timeout,delayBetweenRetries, versionAtCallTime);} else {future.completeExceptionally(ex);}return null;});}/*** 提供一个方式来异步地执行操作,并返回一个 CompletableFuture,* 该 CompletableFuture 可以让调用者在未来某个时刻获取操作的结果** @param key* @param function* @return*/public CompletableFuture<CompletableFuture<V>> doChan(K key, Function<K, V> function) {return CompletableFuture.completedFuture(ongoingOperations.computeIfAbsent(key, k -> {CompletableFuture<V> future = new CompletableFuture<>();CompletableFuture.runAsync(() -> {try {V result = function.apply(k);future.complete(result);} catch (Exception e) {future.completeExceptionally(e);} finally {ongoingOperations.remove(k);}});return future;}));}/*** 从 ongoingOperations 映射中移除了给定的键** @param key*/public void forget(K key) {ongoingOperations.remove(key);}}/*** 假设一个基于Redis的SingleFlight分布式锁实现* 从而使得SingleFlight支持分布式锁* @param <K>* @param <V>*/
class RedisSingleFlight<K, V> extends SingleFlight<K, V> {// Redis 或其他分布式锁机制的实现@Overrideprotected boolean acquireDistributedLock(K key) {return false;}@Overrideprotected void releaseDistributedLock(K key) {}// 如果需要,可以添加特定于 Redis 的其他方法或逻辑
}
相关文章:
【Java】缓存击穿解决方案
文章目录 什么是SingleFlight?优化缺点优化策略 什么是SingleFlight? SingleFlight是go语言中sync包中的一个东西。它用于确保在并发环境下某个操作(例如,函数调用)即使被多个goroutine同时请求,也只会被执…...
【HarmonyOS】掌握 Stage 模型的核心概念与应用
从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是…...
2024年甘肃省职业院校技能大赛 “信息安全管理与评估”赛项样题卷①
2024年甘肃省职业院校技能大赛 高职学生组电子与信息大类信息安全管理与评估赛项样题 第一阶段:第二阶段:模块二 网络安全事件响应、数字取证调查、应用程序安全第二阶段 网络安全事件响应第一部分 网络安全事件响应第二部分 数字取证调查第三部分 应用程…...
我的AI之旅开始了
知道重要,但是就是不动。 今天告诉自己,必须开始学习了。 用这篇博文作为1月份AI学习之旅的起跑点吧。 从此,无惧AI,无惧编程。 AI之路就在脚下。 AI,在我理解,就是让机器变得更加智能&#…...
Day25 235二叉搜索树的公共祖先 701二叉搜索树插入 450二叉搜索树删除
235 二叉搜索树的最近公共祖先 如果利用普通二叉树的方法,就是利用后序遍历回溯从低向上搜索,遇到左子树有p,右子树有q,那么当前结点就是最近公共祖先。本题是二叉搜索树,所以说是有序的,一定能够简化上面…...
android系列-init 挂载文件系统
1.init 挂载文件系统 //android10\system\core\init\main.cppint main(int argc, char** argv) {return FirstStageMain(argc, argv); } //android10\system\core\init\first_stage_init.cppint FirstStageMain(int argc, char** argv) {CHECKCALL(mount("tmpfs",…...
Spring 七种事务传播性介绍
作者:vivo 互联网服务器团队 - Zhou Shaobin 本文主要介绍了Spring事务传播性的相关知识。 Spring中定义了7种事务传播性: PROPAGATION_REQUIRED PROPAGATION_SUPPORTS PROPAGATION_MANDATORY PROPAGATION_REQUIRES_NEW PROPAGATION_NOT_SUPPORTED…...
Count the Colors ZOJ - 1610
题目链接 题意: 给定n个区间[ l, r ]和颜色c, 每次给[l, r]涂上c这个颜色. 后面的涂色会覆盖之前的涂色. 最后要求输出区间[0, 8000]中每种颜色及其出现的次数, 如果该颜色没有出现过则不输出. 思路:典型的线段树区间染色问题,一般这种题…...
MATLAB点云处理总目录
一、点云滤波 原始点云包含过多噪点和冗余点,滤波和采样往往是点云预处理的必要步骤 1.滤波 重复点去除 NAN或INF无效点去除 自定义半径滤波 2.采样 基于空间格网的点云抽稀 随机下采样 均匀体素下采样 非均匀体素下采样 二、邻近搜索 如何组织点云快速获取当前…...
C语言逗号表达式如何计算
在 C 语言中,逗号表达式是一种特殊的表达式形式,它由逗号分隔的多个表达式组成。 逗号表达式的计算过程如下:1、从左到右依次计算每个表达式的值。2、最终返回的值是最右边表达式的值。3、逗号表达式的求值过程是顺序执行的,不会…...
Ubuntu 本地部署 ChatGPT-Next-Web
Ubuntu 本地部署 ChatGPT-Next-Web 文章目录 Ubuntu 本地部署 ChatGPT-Next-Web ChatGPT-Next-Web 项目地址:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web 本文主要演示如何在 Ubuntu 本地(默认是端口 3000)部署 ChatGPT-Next-Web&am…...
小程序商城搭建:快速入门指南
随着移动互联网的普及,小程序商城逐渐成为了商家们进行线上销售的重要渠道。如果你也想搭建一个小程序商城,那么本文将为你介绍如何使用乔拓云这一第三方小程序搭建平台来轻松搭建自己的小程序商城。 一、选择合适的第三方小程序搭建平台 在选择第三方小…...
c# windows10大小端试
测试代码: unsafe public void ceshi() {byte[] by BitConverter.GetBytes(0x12345678);Debug.WriteLine(" byte[0] 0x" by[0].ToString("x2"));Debug.WriteLine(" byte[1] 0x" by[1].ToString("x2"));Debug.WriteLi…...
【算法专题】动态规划之斐波那契数列模型
动态规划1.0 动态规划 - - - 斐波那契数列模型1. 第 N 个泰波那契数2. 三步问题3. 使用最小花费爬楼梯4. 解码方法 动态规划 - - - 斐波那契数列模型 1. 第 N 个泰波那契数 题目链接 -> Leetcode -1137. 第 N 个泰波那契数 Leetcode -1137. 第 N 个泰波那契数 题目&…...
K2P路由器刷OpenWrt官方最新版本固件OpenWrt 23.05.2方法 其他型号的智能路由器OpenWrt固件刷入方法也基本上适用
最近路由器在开机时总出问题,于是就那他来开刀,直接刷一个OpenWrt官方最新版本的固件, 刷其他第三方的固件总是觉得不安全, 而且很多第三方固件都带了些小工具,始终会有安全隐患, 而且占用内存空间太多,本来这个东西就没有多少内存,于是就干脆刷一个官方的原始固件(才6.3M, 相…...
AI大语言模型会带来了新一波人工智能浪潮?
以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮,可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…...
How to view the high-tech zone atmospheric project
How to view the high-tech zone atmospheric project 问题与建议登录界面没有验证码部分页面加载时间过长联动型下拉列表框点击反应迟钝页面缺乏导航没有采用https协议没有完成域名实名认证左侧菜单区不能收缩大屏区域功能图层不能完全隐藏部分页面表单控件没有文案提示其功能…...
sqlalchemy 中的缓存机制解释
SQLAlchemy 的缓存机制主要涉及两个层面:会话(Session)缓存和查询缓存。这两种缓存机制对于提升应用性能和数据一致性都非常重要。下面详细解释这两种缓存机制: 1. 会话(Session)缓存 会话缓存是 SQLAlch…...
网络安全B模块(笔记详解)- 漏洞扫描与利用
漏洞扫描与利用 1.通过Kali对服务器场景server2003以半开放式不进行ping的扫描方式并配合a,要求扫描信息输出格式为xml文件格式,从生成扫描结果获取局域网(例如172.16.101.0/24)中存活靶机,以xml格式向指定文件输出信息(使用工具Nmap,使用必须要使用的参数),并将该操…...
【C语言】指针——从底层原理到应用
C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻 目录 一、前言二、变量与指针的本质 1. 内存地址2. 32位与64位系统3. 变量4. 指针变量5. 操作指针变量 5.1 指针变量自身的值5.2 获取指针变量所指向的数据5.3 以什么样的数据类型来使用/解释指针变量所指…...
想了解步进伺服的朋友可以了解下这个方案
TMC4361A 是一款小型化、高性能的驱动步进电机的运动控制器。实用于很多的斜坡轮廓的应用,特别是速度快、限制过冲的运动场合。用户根据自己的要求实现 S 形或 sixPoint™六点式速度轮廓配置及闭环或开环的操作、动态修改运动参数。TMC4361A 包含 SPI接口、Step/Dir…...
航天航空线束工艺3D虚拟展馆支持多人异地参观漫游
为了满足汽车线束企业员工工作需要,让新老员工了解到更先进、规范的线束工艺设计技术,华锐视点基于VR虚拟仿真、web3d开发和图形图像技术制作了一款汽车线束工艺设计VR虚拟仿真模拟展示系统。 汽车线束工艺设计VR虚拟仿真模拟展示系统共分为pc电脑端和VR…...
JAVA面向对象基础-容器
一、泛型 我们可以在类的声明处增加泛型列表,如:<T,E,V>。 此处,字符可以是任何标识符,一般采用这3个字母。 【示例9-1】泛型类的声明 1 2 3 4 5 6 7 8 9 10 class MyCollection<E> {// E:表示泛型; Object[] o…...
2022年山东省职业院校技能大赛高职组信息安全管理与评估—开发测试服务器解析
任务5:开发测试服务器 目录 任务5:开发测试服务器 解题方法:...
2024年我国网络安全发展形势展望
2023年,我国网络安全政策法规陆续出台,网络安全与数据安全产业发展势头强劲,网络安全形势整体向好。展望2024年,世界各国在网络空间中的竞争将变得愈发激烈,我国网络安全领域的法律法规将不断完善,数据安全…...
如何使用 NFTScan NFT API 在 PlatON 网络上开发 Web3 应用
PlatON 是由万向区块链和矩阵元主导开发的面向下一代的全球计算架构,创新性的采用元计算框架 Monad 和基于 Reload 覆盖网络的同构多链架构,其愿景是成为全球首个提供完备隐私保护能力的运营服务网络。它提供计算、存储、通讯服务,并提供算力…...
如何使用web文件管理器Net2FTP搭建个人网盘
文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一,特别是智能设备的大面积使用,无论是个人…...
总结多线程的各种锁
1、公平锁和非公平锁 公平锁是严格按照线程请求的顺序来分配锁,每一个线程都能获取到锁,避免线程饥饿现象;相反,非公平锁表示线程竞争锁时可以插队来抢占资源。 非公平锁在大多数情况下效率优于公平锁,因为公平锁涉及到…...
树形结构的窗口小部件
这段代码是一个使用Qt框架的C程序,实现了一个树形结构的窗口小部件(TreeWidget)。以下是主要的解释: #include "treewidget.h" #include "ui_treewidget.h"TreeWidget::TreeWidget(QWidget *parent) : QWidg…...
【现代密码学】笔记3.1-3.3 --规约证明、伪随机性《introduction to modern cryphtography》
【现代密码学】笔记3.1-3.3 --规约证明、伪随机性《introduction to modern cryphtography》 写在最前面私钥加密与伪随机性 第一部分密码学的计算方法论计算安全加密的定义:对称加密算法 伪随机性伪随机生成器(PRG) 规约法规约证明 构造安全…...
昆明网站建设推广/排名点击工具
1 引用类库: using System.Diagnostics; 2 执行方法: Stopwatch sw Stopwatch.StartNew(); //初始化,并且开始计时Invoke(new Action(() >{textBox1.Text s;}));MessageBox.Show(sw.ElapsedMilliseconds.ToString()); //显示执行时…...
红色网站建设的作用和意义/平台推广引流
一、股民的迷失投资的核心是风险与回报。风险与回报是利剑的双锋,如影随形,相互匹配。人们天生喜欢回报,厌恶风险,对于投资者来说,追求低风险高回报的策略,以此作为投资者的圣杯。然而,什么是风…...
建设b2b网站要求/企业员工培训课程有哪些
if exists (select * from dbo.sysobjects where id object_id(N[dbo].[p_qry]) and OBJECTPROPERTY(id, NIsProcedure) 1)drop procedure [dbo].[p_qry]GO/*--生成交叉表的简单通用存储过程根据指定的表名,纵横字段,统计字段,自动生成交叉表并可根据需要生成纵横两个方向的合…...
德州做网站最好的公司有哪些/沈阳seo关键字优化
5.UITableViewCell性能优化 1> 定义一个循环利用标识static NSString *ID "C1";2> 从缓存池中取出可循环利用的cellUITableViewCell *cell [tableView dequeueReusableCellWithIdentifier:ID];3> 如果缓存池中没有可循环利用的cellif (cell nil) {cell …...
济南集团网站建设方案/互联网广告优势
Android5.0中向我们介绍了一个全新的控件–CardView,从本质上看,可以将CardView看做是FrameLayout在自身之上添加了圆角和阴影效果。请注意:CardView被包装为一种布局,并且经常在ListView和RecyclerView的Item布局中,作…...
网站建设中出现的问问题/怎么让百度快速收录网站
因为最近larbin项目需要统计下爬取出来的图片到底有多少是jpg、bmp等等类型的。许久没有写python代码,写了就记录下来。 1.统计某种类型的图片格式有多少,自然用python字典。也就哈希表,在文件中出现过一次,1. 2.读取文件&#…...