当前位置: 首页 > news >正文

Redis缓存更新策略与缓存穿透、雪崩等问题的解决

文章目录

    • 一、缓存更新策略
      • 1、三种策略
      • 2、策略选择
      • 3、主动更新的方案
    • 二、缓存存在的问题
      • 1、缓存穿透
      • 2、缓存雪崩
      • 3、缓存击穿
    • 三、解决缓存问题
      • 1、自定义分布式锁
      • 2、解决缓存穿透问题
      • 3、解决缓存击穿问题

一、缓存更新策略

1、三种策略

  • 内存淘汰:redis自带的内存淘汰机制
  • 过期淘汰:利用expire命令给数据设置过期时间
  • 主动更新:主动完成数据库和缓存的同时更新

2、策略选择

  • 低一致性需求:内存淘汰或过期淘汰
  • 高一致性需求:主动更新为主,过期淘汰兜底

3、主动更新的方案

  • Cache Aside:缓存调用者在更新数据库的同时完成对缓存的更新
    • 一致性良好
    • 实现难度一般
  • Read/Write Through:缓存与数据库成为一个服务,服务保证两者的一致性,对外暴露的API接口。调用者调用API,无需知道自己操作的数据库还是缓存,不关心一致性
    • 一致性优秀
    • 实现复杂
    • 性能一般
  • Write Back:缓存调用者的CRUD都针对缓存完成。由独立线程异步的将缓存写到数据库,实现最终一致
    • 一致性差
    • 性能好
    • 实现复杂

二、缓存存在的问题

1、缓存穿透

产生原因:客户端请求的数据在缓存和数据库中都不存在。当这种情况大量出现或被恶意攻击时,接口的访问全部透过Redis访问数据库,而数据库中也没有这些数据,我们称这种现象为"缓存穿透"。

解决方案:

  1. 缓存空对象:对于不存在的数据也在Redis建立缓存,值为空,设置一个较短的TTL时间
    • 优点:实现简单,维护方便
    • 缺点:额外消耗内存,短期的数据不一致
  2. 布隆过滤:利用布隆过滤算法,在请求Redis之前先判断是否存在,如果不存在则直接拒绝访问
    • 优点:内存占用少
    • 缺点:实现复杂,存在误判的可能性
  3. 其他方法:
    1. 做好数据的基础格式校验
    2. 加强用户权限校验
    3. 做好热点数据的限流

布隆过滤器:

一种数据结构,由一串很长的二进制向量组成,可以将其看成一个二进制数组。

当要向布隆过滤器中添加一个元素key时,我们通过多个hash函数,算出一个值,然后将这个值所在的方格置为1。

因为多个不同的数据通过hash函数算出来的结果是会有重复的,所以布隆过滤器可以判断某个数据一定不存在,但是无法判断一定存在。

优点:优点很明显,二进制组成的数组,占用内存极少,并且插入和查询速度都足够快。

缺点:随着数据的增加,误判率会增加;还有无法判断数据一定存在;另外还有一个重要缺点,无法删除数据。

2、缓存雪崩

产生原因:在同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力

解决方案:

  1. 给不同的Key的TTL设置随机值
  2. 利用Redis集群提高服务的可用性
  3. 诶缓存业务添加降级限流策略
  4. 给业务添加多级缓存

3、缓存击穿

产生原因:热点Key在某一个时间段被高并发访问,而此时Key正好过期,如果重建缓存时间耗时长,在这段时间内大量请求剾数据库,带来巨大冲击

解决方案:

  1. 设置value永不过期:通过定时任务进行数据库查询更新缓存,当然前提时不会给数据库造成压力过大
    • 优点:最可靠,性能好
    • 缺点:占空间,内存消耗大,一致性差
  2. 互斥锁:给缓存重建过程加锁,确保重建过程只有一个线程执行,其他线程等待
    • 优点:实现简单,没有额外内存消耗,一致性好
    • 缺点:等待导致性能下降,有死锁风险
  3. 逻辑过期:热点Key缓存永不过期,认识设置一个逻辑过期时间,查询到数据时通过对逻辑时间判断,来决定是否需要进行缓存重建。重建过程也通过互斥锁来保证单线程执行。利用独立线程异步执行,其他线程无需等待,直接查询到旧的数据即可。
    • 优点:线程无需等待,性能较好
    • 缺点:不保证一致性,有额外内存消耗,实现复杂
private final RedisTemplate<String, String> redisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(20), r -> new Thread(r, "cache_rebuild"));public CacheClient(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;
}public void setWithLogicalExpire(String key, Object value, Long expireTime, TimeUnit unit) {// 设置逻辑过期时间RedisData redisData = new RedisData();redisData.setValue(value);redisData.setExpireTime(LocalDateTime.now().plusNanos(unit.toNanos(expireTime)));redisTemplate.opsForValue().set(key, JSON.toJSONString(redisData));
}/*** 逻辑过期,互斥锁获取值,用于避免热点数据出现缓存击穿*/
public <R, V> R getMutex(String keyPrefix, V id, Class<R> clazz, Function<V, R> dbFallback, Long expireTime, TimeUnit unit) {String key = keyPrefix + id;String value = redisTemplate.opsForValue().get(key);if (StringUtils.isBlank(value)) {return null;}RedisData redisData = JSON.parseObject(value, RedisData.class);R result = JSONUtil.toBean((JSONObject) redisData.getValue(), clazz);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return result;}// 如果缓存已过期,则尝试更新String localKey = RedisConstant.LOCK + id;// 获取锁成功if (getLock(localKey)) {// 异步更新缓存CACHE_REBUILD_EXECUTOR.submit(() -> {try {R res = dbFallback.apply(id);this.setWithLogicalExpire(key, res, expireTime, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {unLock(localKey);}});}return result;
}private boolean getLock(String key) {// 直接返回会进行自动拆箱,可能会出现空指针异常return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, "1"));
}private void unLock(String key) {redisTemplate.delete(key);
}

三、解决缓存问题

1、自定义分布式锁

/*** <pre>* 简易实现的Redis分布式锁* </pre>** @author <a href="https://github.com/Ken-Chy129">Ken-Chy129</a>* @date 2023/2/26 21:18*/
public class SimpleRedisLock {private final RedisTemplate<String, String> redisTemplate;/**锁的名字,根据业务设置*/private final String lockName;/*** key前缀*/private static final String KEY_PREFIX = "lock:";/*** value中线程标识的前缀(为每个节点提供一个随机的前缀,避免集群部署下线程id出现重复而导致value出现相同的情况)*/private static final String ID_PREFIX = UUID.fastUUID().toString(true);/*** 释放锁逻辑的lua脚本*/private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}public SimpleRedisLock(String lockName, RedisTemplate<String, String> redisTemplate) {this.lockName = lockName;this.redisTemplate = redisTemplate;}public boolean tryLock(long timeoutSec) {long threadId = Thread.currentThread().getId();// 返回的是Boolean类型,直接return会进行自动拆箱,可能会出现空指针异常// 需要为锁设置过期时间,防止因服务宕机而导致锁无法释放return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + lockName, ID_PREFIX + threadId, timeoutSec, TimeUnit.SECONDS));}public void unlock() {redisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + lockName),ID_PREFIX + Thread.currentThread().getId());}
}

Lua脚本——unlock.lua

--- 比较线程标识与锁中的标识是否一致
if(redis.call('get', KEYS[1]) == ARGS[1]) then--- 释放锁return redis.call('del', KEYS[1])
end
return 0

使得释放锁的操作具有原子性

Redis是单线程处理,本身不会存在并发问题,但是由于可能有多个客户端访问,每个客户端会有一个线程,之间存在竞争,所以服务端收到的指令有可能出现多个客户端的指令穿插,而lua脚本可以保证多条指令的原子性从而解决并发问题

2、解决缓存穿透问题

/*** 避免缓存穿透的获取*/
public <R, V> R get(String keyPrefix, V id, Class<R> clazz, Function<V, R> dbFallback, Long expireTime, TimeUnit unit) {String key = keyPrefix + id;// 查询缓存String value = redisTemplate.opsForValue().get(key);// 缓存存在则直接返回if (StringUtils.isNotBlank(value)) {return JSON.parseObject(value, clazz);}// 缓存不存在(到此处说明value要么是空,要么是null)if (value != null) {// 不为null则说明为“”,代表数据不存在,直接返回null,不用查询数据库(解决缓存穿透问题)return null;}// value为null则查询数据库获取数据进行更新R result = dbFallback.apply(id);if (result == null) {// 数据库查询不到结果,则存入空串避免缓存穿透redisTemplate.opsForValue().set(key, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 查询到结果,写回缓存this.set(key, result, expireTime, unit);return result;
}

3、解决缓存击穿问题

/*** 逻辑过期,互斥锁获取值,用于避免热点数据出现缓存击穿*/
public <R, V> R getMutex(String keyPrefix, V id, Class<R> clazz, Function<V, R> dbFallback, Long expireTime, TimeUnit unit) {String key = keyPrefix + id;String value = redisTemplate.opsForValue().get(key);if (StringUtils.isBlank(value)) {return null;}RedisData redisData = JSON.parseObject(value, RedisData.class);R result = JSONUtil.toBean((JSONObject) redisData.getValue(), clazz);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return result;}// 如果缓存已过期,则获取锁尝试更新SimpleRedisLock lock = new SimpleRedisLock(key, redisTemplate);// 获取锁成功if (lock.tryLock(5)) {// 异步更新缓存CACHE_REBUILD_EXECUTOR.submit(() -> {try {R res = dbFallback.apply(id);this.setWithLogicalExpire(key, res, expireTime, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {lock.unlock();}});}return result;
}

相关文章:

Redis缓存更新策略与缓存穿透、雪崩等问题的解决

文章目录一、缓存更新策略1、三种策略2、策略选择3、主动更新的方案二、缓存存在的问题1、缓存穿透2、缓存雪崩3、缓存击穿三、解决缓存问题1、自定义分布式锁2、解决缓存穿透问题3、解决缓存击穿问题一、缓存更新策略 1、三种策略 内存淘汰&#xff1a;redis自带的内存淘汰机…...

OSI和TCP/IP网络模型细讲

文章目录一、OSI七层参考模型二、TCP/IP体系结构三、TCP/IP参考模型四、沙漏计时器形状的TCP/IP协议族五、两种国际标准对比相似之处不同之处一、OSI七层参考模型 OSI参考模型共分为7层&#xff0c;低三层面向通信&#xff0c;可用软硬件实现&#xff1b;高三层面向信息处理&am…...

【正点原子FPGA连载】第十九章FreeRtos Hello World实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十九章FreeRto…...

php mysql高校田径运动会成绩管理系统

第一章 引言 1 1.1 选题背景 1 1.2 编写目的 2 1.3 目标 2 1.4 功能需求 3 第二章 开发工具介绍 4 2.1 PHP 4 2.2 APACHE 5 2.3 MYSQL数据库 5 2.4 运行环境 WINDOWS XP 6 2.5 XAMPP 6 2.6 DREAMWEAVE8 6 2.7 EDITPLUS 7 第三章 需求…...

scrum敏捷项目管理软件三款

Leangoo领歌Leangoo是国产的一个项目管理软件&#xff0c;www.leangoo.com &#xff0c; 专门的Scrum敏捷开发工具&#xff0c;看板的管理方式&#xff0c;高度可视化。它支持敏捷开发全流程。从产品路线图-需求-迭代-缺陷-测试-上线。燃尽图&#xff0c;工作量&#xff0c;迭代…...

【项目设计】高并发内存池(二)[高并发内存池整体框架设计|threadcache]

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…...

西电编译原理期末核心考点汇总(期末真题+相关知识点)

文章目录前言一、正规式1.1 相关知识点1.1.1 正规式定义1.1.2 辅助定义1.2 历年真题二、二义文法2.1 相关知识点2.1.1 二义性概念2.2 历年考题三、全部短语、直接短语和句柄3.1 相关知识点3.1.1 短语&#xff0c;直接短语和句柄定义3.1.2 短语&#xff0c;直接短语和句柄例题3.…...

追梦之旅【数据结构篇】——详解C语言实现二叉树

详解C语言实现二叉树~&#x1f60e;前言&#x1f64c;什么是二叉树&#xff1f;二叉树的性质总结&#xff1a;整体实现内容分析&#x1f49e;1.头文件的编写&#xff1a;&#x1f64c;2.功能文件的编写&#xff1a;&#x1f64c;1&#xff09;前序遍历的数值来创建树——递归函…...

独家 | Gen-1——可以改变视频风格的AI模型

翻译&#xff1a;吴振东校对&#xff1a;张睿毅本文约1000字&#xff0c;建议阅读3分钟 本文简单介绍了Runway公司的发展史&#xff0c;以及他们新推出的生成式AI模型Gen-1&#xff0c;可用于通过应用文本提示或者参考图像所指定的任意风格&#xff0c;将现有视频转换为新视频。…...

戴尔dell inspiron-5598电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板X99 K9 v2 Machinist处理器i5-10210U / *i7-10510U已驱动内存20GB已驱动硬盘1000GB SAMSUNG 860 QVO SATA已驱动显卡Intel UHD 620已驱动声卡Realtek ALC3204/236已驱动网卡RTL8168H Gigabit Ethernet已…...

3.2 网站图的爬取路径

深度优先与广度优先方法都是遍历树的一种方法&#xff0c;但是网站的各个网页 之间的关系未必是树的结构&#xff0c;它们可能组成一个复杂的图形结构&#xff0c;即有回路。如果在前面的网站中每个网页都加一条Home的语句&#xff0c;让每个网页都能回到主界面&#xff0c;那么…...

《SQL基础》12. SQL优化

SQL优化SQL优化数据插入insert优化大批量插入数据主键优化order by优化group by优化limit优化count优化count用法update优化SQL优化 数据插入 insert优化 如果我们需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化。 批量插入手动控制事务主键顺…...

fork之后是子进程先执行还是父进程先执行

CFS(完全公平调度器)是Linux内核2.6.23版本开始采用的进程调度器&#xff0c;它的基本原理是这样的&#xff1a;设定一个调度周期(sched_latency_ns)&#xff0c;目标是让每个进程在这个周期内至少有机会运行一次&#xff0c;换一种说法就是每个进程等待CPU的时间最长不超过这个…...

2023年java初级面试题(5道)

一、两个对象值相同(x.equals(y) true)&#xff0c;但却可有不同的hash code&#xff0c;这句话对不对&#xff1f;答&#xff1a;不对&#xff0c;如果两个对象x和y满足x.equals(y) true&#xff0c;它们的哈希码&#xff08;hash code&#xff09;应当相同。Java对于eqauls…...

【内网安全】——Linux权限维持

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a; 钱至少对于现在的我来说&#xff0c;的确是万能的在…...

Linux 真实使用内存计算

获取Linux内存信息&#xff0c;可通过cat /proc/meminfo查看&#xff0c;比如&#xff0c;Ubuntu 20.04.5 LTS上会显示以下信息&#xff1a; leoyaDESKTOP-LMR:~$ cat /proc/meminfo MemTotal: 16017572 kB MemFree: 15637472 kB MemAvailable: 15533140 kB Bu…...

Unity Jobsystem ECS

简介随着ECS的加入&#xff0c;Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来&#xff0c;我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS&#xff0c;也许不少人需要些时间适应ECS的使用&#xff0c;…...

Java中创建线程有哪几种方式

1.继承Thread类 总结&#xff1a;通过继承 Thread 类&#xff0c;重写 run() 方法&#xff0c;而不是 start() 方法 Thread 类底层实现 Runnable 接口类只能单继承 接口可以多继承2.实现Runnable接口 总结&#xff1a;通过实现 Runnable 接口,实现 run() 方法&#xff0c;依然…...

C++【string类用法详细介绍string类模拟实现解析】

文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…...

常见的开发模型和测试模型

软件的生命周期软件开发阶段的生命周期需求分析->计划->设计->编码->测试->运维软件测试阶段的生命周期需求分期->测试计划->测试设计与开发->执行测试->测试评估开发模型瀑布模型可以看到,这个模型和我们上面的软件开发生命周期很相似采用的是线性…...

逆向工程实现GitHub Copilot HTTP API:解锁AI代码补全的无限集成可能

1. 项目概述&#xff1a;一个反向工程的“桥梁”如果你是一名开发者&#xff0c;并且对 GitHub Copilot 的智能代码补全能力印象深刻&#xff0c;但同时又希望能在自己偏爱的编辑器、IDE&#xff0c;甚至是命令行工具里直接调用它的能力&#xff0c;那么purocean/expose-github…...

智能家居生态博弈下,如何构建本地优先的自主智能家居系统

1. 智能家居生态的十字路口&#xff1a;当选择变成非此即彼几年前&#xff0c;如果你问我怎么搭建一个智能家居&#xff0c;我可能会兴致勃勃地跟你聊起各种开源平台、五花八门的协议和那些充满极客气质的独立品牌设备。那时候&#xff0c;市场像个热闹的集市&#xff0c;虽然有…...

OpenClaw Gateway智能守护者:双触发自愈与AI诊断实践

1. 项目概述&#xff1a;一个为OpenClaw Gateway设计的智能守护者如果你在运维一个基于OpenClaw Gateway的服务&#xff0c;大概率经历过这样的深夜惊魂&#xff1a;手机突然收到告警&#xff0c;提示网关服务挂了&#xff0c;然后你不得不从床上爬起来&#xff0c;摸黑打开电脑…...

【Midjourney v8图像修复终极指南】:9大隐藏参数调优+3类高频崩坏场景实战修复(2024官方未公开文档级解析)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Midjourney v8图像修复功能全景概览 Midjourney v8 引入了革命性的图像修复&#xff08;Image Inpainting&#xff09;能力&#xff0c;不再依赖外部图层或第三方工具&#xff0c;而是通过原生提示词指…...

2026届学术党必备的AI写作网站实测分析

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 作为学术研究启动时核心的前置材料的开题报告&#xff0c;要完成文献梳理&#xff0c;要搭建…...

OAI 5G核心网搭建后,如何用Docker命令进行日常运维和故障排查?

OAI 5G核心网Docker运维实战&#xff1a;从日志分析到故障排查 当OAI 5G核心网完成基础部署后&#xff0c;真正的挑战才刚刚开始。面对由多个容器组成的复杂系统&#xff0c;如何快速定位AMF拒绝注册的原因&#xff1f;SMF的PDU会话建立失败该如何排查&#xff1f;本文将分享一…...

别再只盯着屏蔽罩了!PCB布局与软件防抖,才是低成本搞定EMC(静电/辐射/脉冲群)的关键

低成本EMC设计实战&#xff1a;PCB布局与软件防抖的黄金法则 当谈到电磁兼容性&#xff08;EMC&#xff09;设计时&#xff0c;许多工程师的第一反应往往是增加屏蔽罩、使用昂贵的滤波器或购买高规格的元器件。这种思路虽然有效&#xff0c;但对于资源有限的初创团队和小型项目…...

工程师创意竞赛全流程策划:从社区激活到公平投票的实战指南

1. 项目概述&#xff1a;一场别开生面的工程师创意竞赛又到了二月底&#xff0c;这意味着我们年初启动的那个“独轮车”图片配文竞赛&#xff0c;终于要进入最激动人心的投票环节了。我记得很清楚&#xff0c;那是2012年2月初&#xff0c;编辑部觉得冬天太沉闷&#xff0c;想找…...

手机号到QQ号查询技术实现原理与TEA加密通信架构解析

手机号到QQ号查询技术实现原理与TEA加密通信架构解析 【免费下载链接】phone2qq 项目地址: https://gitcode.com/gh_mirrors/ph/phone2qq phone2qq是一个基于Python实现的逆向工程工具&#xff0c;通过分析腾讯QQ客户端的通信协议&#xff0c;实现了通过手机号查询对应…...

2026 年行业真相:履职规范背后的管理秘密

现场冲突&#xff1a;安全与进度的激烈碰撞在工程建设领域&#xff0c;安全与进度的冲突一直是个老大难问题。就拿上海中心的建设来说&#xff0c;如此庞大复杂的项目&#xff0c;施工过程中安全管理难度极大。在某些施工阶段&#xff0c;为了赶进度&#xff0c;部分施工人员可…...