Redis学习笔记 ----- 缓存

一、什么是缓存

缓存(Cache)是数据交换的缓冲区,是存储数据的临时地方,一般读写性能较高。

(一)缓存的作用

  • 降低后端负载:减少对数据库等后端存储的直接访问压力。
  • 提高读写效率,降低响应时间:利用缓存的高性能,快速响应数据请求。

(二)缓存的成本

  • 数据一致性成本:缓存与后端数据可能存在不一致,维护一致性需要额外处理。
  • 代码维护成本:引入缓存后,代码逻辑会更复杂,增加维护难度。
  • 运维成本:缓存系统的部署、监控、扩容等都需要投入运维资源。

二、缓存更新策略

策略说明一致性维护成本
内存淘汰利用Redis的内存淘汰机制,内存不足时自动淘汰部分数据,下次查询时更新缓存
超时剔除给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存一般
主动更新编写业务逻辑,在修改数据库的同时更新缓存

业务场景

  • 低一致性需求:使用内存淘汰机制,例如店铺类型的查询缓存。
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案,例如店铺详情查询的缓存。

代码实现:主动更新策略

@Override
@Transactional
public Result updateShop(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("商铺ID不能为空");}// 1.先更新数据库updateById(shop);// 2.再删除缓存(保证数据一致性)stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();
}

三、缓存更新策略的最佳实践方案

(一)低一致性需求

使用Redis自带的内存淘汰机制。

(二)高一致性需求

主动更新,并以超时剔除作为兜底方案。

  • 读操作
    • 缓存命中则直接返回。
    • 缓存未命中则查询数据库,并写入缓存,设定超时时间。
  • 写操作
    • 先写数据库,然后再删除缓存。
    • 要确保数据库与缓存操作的原子性。

代码实现:基础缓存读写逻辑

private Result queryShopById1(Long id) {// 1.查询RedisString cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 2.缓存命中直接返回if (StrUtil.isNotBlank(cacheShop)) {Shop shop = JSONUtil.toBean(cacheShop, Shop.class);return Result.ok(shop);}// 3.缓存未命中查询数据库Shop shop = getById(id);if (shop == null) {return Result.fail("商铺不存在!");}// 4.数据库查询结果写入Redis,设置超时时间stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);
}

四、缓存问题及解决方案

(一)缓存穿透

问题定义

客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

问题场景
  1. 恶意攻击:黑客故意构造大量不存在的ID(如负数、超范围的随机数)发起请求,试图耗尽数据库资源
  2. 业务误操作:前端表单未做校验,用户输入了无效ID导致大量无效查询
  3. 数据已删除:商品已下架或用户已注销,但仍有请求访问这些已不存在的数据
  4. 爬虫抓取:爬虫程序遍历不存在的URL路径,导致大量无效查询
解决方案
1. 缓存空对象

原理:当数据库查询结果为空时,仍然将这个空结果缓存起来(可以是空字符串、null或特定标识),并设置较短的过期时间,避免同一无效请求反复穿透到数据库。
在这里插入图片描述

实现代码

private Result queryShopById2(Long id) {// 1.查询RedisString cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 2.缓存命中(包括空值)if (StrUtil.isNotBlank(cacheShop)) {// 2.1 空值判断if (cacheShop.isEmpty()) {return Result.fail("商铺不存在!");}// 2.2 正常数据返回Shop shop = JSONUtil.toBean(cacheShop, Shop.class);return Result.ok(shop);}// 3.数据库查询Shop shop = getById(id);if (shop == null) {// 3.1 数据库不存在则缓存空值(短期有效)stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail("商铺不存在!");}// 4.正常数据写入缓存stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);
}

优缺点

  • 优点:实现简单,维护方便,能有效拦截重复的无效请求
  • 缺点:
    • 占用额外内存空间存储空值
    • 可能造成短期数据不一致(如刚删除的数据又被创建)
    • 对于海量不同的无效ID,仍可能消耗大量缓存空间
2. 布隆过滤器

原理:在缓存之前增加一层布隆过滤器,预先将数据库中所有存在的Key存入布隆过滤器。当请求进来时,先通过布隆过滤器判断Key是否可能存在:

  • 若不存在,则直接返回,无需访问缓存和数据库
  • 若可能存在,再走正常的缓存+数据库查询流程

实现思路

  1. 系统初始化时,将数据库中所有有效ID加载到布隆过滤器
  2. 接收请求时,先通过布隆过滤器验证ID有效性
  3. 对布隆过滤器判断不存在的ID,直接返回错误

优缺点

  • 优点:内存占用少(相比缓存空对象),处理海量无效ID效率高
  • 缺点:
    • 实现复杂,需要维护布隆过滤器
    • 存在误判可能(不能准确判断元素是否存在)
    • 需要定期更新布隆过滤器数据,以反映数据库变化

(二)缓存雪崩

问题定义

在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力,甚至引起数据库宕机。

问题场景
  1. 集中过期:系统上线时为一批热点数据设置了相同的过期时间(如24小时),导致24小时后这批数据同时失效
  2. Redis宕机:Redis服务器因硬件故障、网络问题或内存溢出等原因突然宕机,整个缓存层失效
  3. 批量更新:电商平台在大促前批量更新商品信息,导致大量缓存被删除
  4. 缓存穿透引发:大量穿透请求导致数据库压力过大,进而影响缓存服务的正常运行
解决方案
1. 过期时间随机化

原理:为不同的缓存Key设置基础过期时间的同时,增加一个随机偏移量(如±10%),避免大量Key在同一时间点同时过期。

实现代码

// 设置过期时间时增加随机值
int baseTtl = 30; // 基础30分钟
int random = new Random().nextInt(10); // 0-9分钟随机
stringRedisTemplate.opsForValue().set(key, value, baseTtl + random, TimeUnit.MINUTES
);
2. Redis集群

原理:通过部署Redis集群(主从+哨兵或Redis Cluster)提高缓存服务的可用性,避免单点故障导致整个缓存层失效。

关键措施

  • 主从复制:实现数据备份和读写分离
  • 哨兵机制:自动监控和故障转移
  • 集群分片:分散数据存储,提高整体容量和性能
3. 降级限流

原理:当缓存服务出现异常时,通过降级策略限制对数据库的请求流量,保护数据库不被压垮。

实现方式

  • 使用熔断器(如Sentinel、Hystrix)监控缓存服务状态
  • 当缓存服务异常时,返回默认数据或提示信息,而非直接查询数据库
  • 对查询数据库的请求设置限流阈值,超出阈值的请求直接拒绝
4. 多级缓存

原理:构建多级缓存架构(如本地缓存+分布式缓存),即使分布式缓存失效,本地缓存仍能提供部分缓冲能力。

常见架构

  1. 本地缓存(Caffeine、Guava):存在于应用进程内,速度最快
  2. 分布式缓存(Redis):供多个应用实例共享
  3. 数据库缓存:数据库自身的缓存机制

(三)缓存击穿

问题定义

也叫热点Key问题,指一个被高并发访问的热点数据的缓存突然失效,无数请求会在瞬间同时到达数据库,给数据库带来巨大冲击。

问题场景
  1. 热门商品:电商平台的爆款商品缓存过期,恰逢促销活动期间,大量用户同时访问
  2. 热点事件:突发新闻事件的相关数据缓存过期,引发大量用户同时查询
  3. 排行榜:热门游戏排行榜数据缓存过期,大量玩家同时刷新页面
  4. 秒杀活动:秒杀商品的缓存过期,大量用户在同一时间点抢购
解决方案
1. 互斥锁

原理:当缓存失效时,不是让所有请求都去查询数据库,而是通过锁机制保证只有一个请求能去查询数据库并重建缓存,其他请求则等待缓存重建完成后再从缓存获取数据。
在这里插入图片描述

实现代码

private Result queryShopById3(Long id) {// 1.查询RedisString cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (StrUtil.isNotBlank(cacheShop)) {if (cacheShop.isEmpty()) {return Result.fail("商铺不存在!");}return Result.ok(JSONUtil.toBean(cacheShop, Shop.class));}Shop shop = null;try {// 2.获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);if (!isLock) {// 2.1 获取锁失败则重试(短暂等待后再次查询)Thread.sleep(50);return queryShopById3(id);}// 3.获取锁成功,查询数据库shop = getById(id);// 模拟缓存重建耗时操作Thread.sleep(200);if (shop == null) {stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail("商铺不存在!");}// 4.写入缓存stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 5.释放锁unlock(LOCK_SHOP_KEY + id);}return Result.ok(shop);
}// 获取锁(使用Redis的setIfAbsent实现)
private boolean tryLock(String key) {return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 100, TimeUnit.SECONDS));
}// 释放锁
private void unlock(String key) {stringRedisTemplate.delete(key);
}

优缺点

  • 优点:
    • 保证数据一致性(每次都是最新数据)
    • 无额外内存消耗
    • 实现相对简单
  • 缺点:
    • 锁竞争会导致请求等待,影响接口响应性能
    • 可能存在死锁风险(需设置合理的锁过期时间)
    • 高并发下,第一个获取锁的请求可能成为性能瓶颈
2. 逻辑过期

原理:给缓存数据设置一个逻辑过期时间(而非Redis的实际过期时间),缓存永不过期。当查询时发现数据已过逻辑过期时间,不直接删除缓存,而是返回旧数据并异步更新缓存,保证后续请求能尽快获取到新数据。
在这里插入图片描述

实现代码

// 线程池(用于异步重建缓存)
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);// 逻辑过期数据封装类
@Data
public class RedisData {// 逻辑过期时间private LocalDateTime expireTime;// 实际存储的数据(如Shop对象)private Object data;
}private Result queryShopById4(Long id) {// 1.查询RedisString cacheShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (StrUtil.isBlank(cacheShop)) {return Result.fail("商铺不存在!");}if (cacheShop.isEmpty()) {return Result.fail("商铺不存在!");}// 2.解析缓存数据(带逻辑过期时间)RedisData redisData = JSONUtil.toBean(cacheShop, RedisData.class);JSONObject data = (JSONObject) redisData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 3.判断是否逻辑过期if (expireTime.isAfter(LocalDateTime.now())) {// 3.1 未过期直接返回return Result.ok(shop);}// 3.2 已过期,尝试获取锁重建缓存boolean isLock = tryLock(LOCK_SHOP_KEY + id);if (isLock) {// 3.2.1 获取锁成功,异步重建缓存(不阻塞当前请求)CACHE_REBUILD_EXECUTOR.submit(() -> {try {saveShop2Redis(id, CACHE_SHOP_TTL);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {unlock(LOCK_SHOP_KEY + id);}});}// 3.2.2 无论是否获取锁,都返回旧数据(保证用户体验)return Result.ok(shop);
}// 封装逻辑过期数据并写入Redis
public void saveShop2Redis(long id, Long seconds) throws InterruptedException {Shop shop = getById(id);// 模拟缓存重建延时Thread.sleep(200);RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(seconds));stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

优缺点

  • 优点:
    • 无请求阻塞,性能好(始终返回数据,不等待)
    • 避免大量请求同时冲击数据库
  • 缺点:
    • 数据可能短期不一致(返回旧数据)
    • 需要额外存储过期时间,占用更多内存
    • 实现相对复杂,需要处理异步更新逻辑

五、缓存常量定义

public class RedisConstants {// 商铺缓存键前缀public static final String CACHE_SHOP_KEY = "cache:shop:";// 商铺缓存默认过期时间(30分钟)public static final Long CACHE_SHOP_TTL = 30L;// 空值缓存过期时间(2分钟,用于解决缓存穿透)public static final Long CACHE_NULL_TTL = 2L;// 商铺缓存锁键前缀(用于解决缓存击穿)public static final String LOCK_SHOP_KEY = "lock:shop:";// 锁过期时间(10秒)public static final Long LOCK_SHOP_TTL = 10L;
}

六、缓存方案对比

方案解决问题优点缺点适用场景
基础缓存实现简单存在缓存穿透、击穿问题低并发、非核心业务
缓存空值缓存穿透防止数据库压力过大占用额外缓存空间无效请求相对集中的场景
布隆过滤缓存穿透内存占用少有误判、实现复杂海量无效ID场景
互斥锁缓存击穿数据一致性好可能阻塞请求一致性要求高的热点数据
逻辑过期缓存击穿无阻塞,性能好可能返回旧数据高并发、一致性要求不高
随机TTL缓存雪崩实现简单只能解决集中过期问题所有需要设置过期的场景
多级缓存缓存雪崩提高可用性和性能实现复杂、维护成本高核心业务、高可用要求

参考来源

本文内容基于黑马程序员《Redis入门到实战教程》相关章节学习整理,部分代码示例与知识点解析参考了该课程的讲解。

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

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

相关文章

React响应式链路

文章目录响应式链路的核心环节1.状态定义与初始化2.状态更新触发(状态变更)3.调度更新(Scheduler)4.重新渲染(Render 阶段)5.协调(Reconciliation)与 Fiber 架构6.提交更新&#xff…

软件设计师——计算机网络学习笔记

一、计算机网络 网络基础 1. 计算机网络的分类2. 网络拓扑结构 总线型(利用率低、干扰大、价格低) 星型(交换机形成的局域网、中央单元负荷大) 环型(流动方向固定、效率低扩充难) 树型(总线型的扩充、分级结构) 分布式(任意节点连接、管理难成本高)一般来说,办公室局…

1200 SCL学习笔记

一. IF. 如果。下面是一个起保停IF #I_start AND NOT #I_stop THEN //如果I_start接通 和 I_stop没有接通#Q_run : 1; //输出Q_run 接通 ELSIF #I_stop THEN //如果I_stop接通#Q_run : 0; //。。。。。。 END_IF;二. CASECASE…

单例模式与线程池

1. 单例模式单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在需要控制资源访问、管理共享状态或协调系统行为时非常有用。单例模式的核心特点:私有构造函数:防止外部通过n…

Chrome和Edge如何开启暗黑模式

Edge和Chrome浏览器都提供了实验性功能,可以通过修改实验性设置来开启暗黑模式。 在浏览器地址栏中输入edge://flags/(Edge)或chrome://flags/(Chrome)。在搜索框中输入“dark”,找到与暗黑模式相关的选项。…

【科研绘图系列】浮游植物的溶解性有机碳与初级生产力的关系

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍 数据准备 数据处理 溶解性有机碳(DOC)与初级生产力(NPP)的关系 溶解性有机碳(DOC)与光照强度(PAR)的关系 数据可视化 加载R包 数据下载 导入数据 画图1 画图2 总结 系统信…

IDEA相关的设置和技巧

IDEA相关的设置和技巧 我的博客对应文章地址 1.布局设置 IDEA的布局自定义程度很高,顶部工具栏,侧边栏都可以随意定制,设置好的布局方案可以保存,在新项目中快速使用 1.1 工具栏设置 [!tip] 举个例子:比如我要在顶部…

AWS Lambda 完全指南:解锁无服务器架构的强大力量

在云计算的发展浪潮中,无服务器(Serverless) 架构已然成为构建现代应用的新范式。而在这场变革的中心,AWS Lambda 作为开创性的 Function-as-a-Service (FaaS) 服务,彻底改变了我们部署和运行代码的方式。 本文将带您深入探索 AWS Lambda,从核心概念、工作原理到高级实践…

人工智能时代下普遍基本收入(UBI)试验的实践与探索——以美国硅谷试点为例

一、硅谷UBI试验的最新进展(2025年)1. 试验规模与资金来源圣克拉拉县试点:硅谷所在地圣克拉拉县针对脱离寄养家庭的年轻人开展UBI试验,每月发放1000美元补贴,持续1-2年,覆盖约60名参与者,成本约…

云计算之云主机Linux是什么?有何配置?如何选?

一、云环境如何选择Linux发行版 1.1、Linux在各个领域的发展 Linux在各个领域的发展序号Linux发展领域说明1Linux在服务器领域的发展目前Linux在服务器领域已经占据95%的市场份额,同时Linux在服务器市场的迅速崛起,已经引起全球IT产业的高度关注&#xf…

XCVU13P-2FHGB2104E Xilinx(AMD)Virtex UltraScale+ FPGA

XCVU13P-2FHGB2104E 是 Xilinx(AMD)Virtex UltraScale FPGA 系列中的一款高性能芯片,适用于需要大量逻辑资源、高带宽和高速数据传输的应用场景。作为该系列中的旗舰产品,XCVU13P-2FHGB2104I 结合了强大的处理能力和灵活的可编程性…

自动化单词例句获取系统设计方案

方案一 (网络爬虫) 这个方案的核心思路是:创建一个自动化的脚本,该脚本会读取你 MongoDB 中的单词,然后去一个免费的在线词典网站上抓取这些单词的例句,最后将抓取到的例句存回你的 MongoDB 数据库中对应的单词条目下。 一、 核心思路与技术选型 自动化脚本: 我们将使用 P…

WPF Alert弹框控件 - 完全使用指南

WPF Alert弹框控件 - 完全使用指南概述快速开始nuget安装与引用基本用法功能特性详细说明AlertType 枚举方法参数详解Show 方法(局部弹窗)ShowGlobal 方法(全局弹窗)完整示例代码XAML 布局C# 代码实现界面演示功能特性对比表格自定…

可视化-模块1-HTML-01

1-软件下载: 软件名称:HBuilderX 官网地址: https://www.dcloud.io/hbuilderx.html 下载文佳-解压缩-打开exe文件 创建快捷方式至桌面 2-创建项目 【普通项目】-【基本HTML项目】-【项目名:week1-1】 【index】输入&#xff1…

机器翻译 (Machine Translation) 经典面试笔试50题(包括详细答案)

更多内容请见: 机器翻译修炼-专栏介绍和目录 文章目录 第一部分:基础理论与概念 (1-15题) 1. 题目: 什么是机器翻译(MT)?请简述其发展历程中的几个主要范式。 2. 题目: 机器翻译的主要评价指标有哪些?请详细解释BLEU指标的计算原理和优缺点。 3. 题目: 什么是平行语料…

linux中文本文件操作之grep命令

文章目录背景案例demo环境方式一、安装wsl方式二、安装grep一、查找指定字符串二、忽略大小写查找三、查找时显示行号四、统计匹配的次数五、精准匹配一个单词六、显示匹配上下文七、只显示匹配的内容八、按固定字符串匹配背景 在日常运维中会对日志文件,使用grep命…

链表漫游指南:C++ 指针操作的艺术与实践

文章目录0. 前言1. 链表的分类2. 单链表的实现2.1 链表的基本结构——节点(Node)2.2 核心操作详解2.2.1 构造和析构2.2.2 插入操作2.2.3 删除操作2.3.4 其他操作2.4 总结3. 双向链表的实现3.1 基本结构设计3.2 基本操作3.2.1 初始化与销毁3.2.2 插入与删…

Claude Code赋能企业级开发:外卖平台核心系统的智能化重构

开篇:万亿市场背后的技术挑战中国外卖市场日订单量超过1亿单,每一单背后都是一个复杂的技术链条:用户下单→商家接单→骑手抢单→实时配送→评价反馈。构建这样一个支撑千万级并发、涉及地理位置计算、实时调度、支付结算的超级平台&#xff…

【使用Unsloth 微调】数据集的种类

1. 什么是数据集 对于大型语言模型(LLMs),数据集是用于训练模型的数据集合。为了训练有效,文本数据需要能够被分词(tokenized)。创建数据集的关键部分之一是聊天模板(chat template)…

【码蹄杯】2025年本科组省赛第一场

个人主页:Guiat 归属专栏:算法竞赛 文章目录1. MC0455 四大名著-西游签到2. MC0456 斩断灵藤3. MC0457 符咒封印4. MC0458 移铁术5. MC0459 昆仑墟6. MC0460 星空迷轨阵7. MC0461 排队8. MC0462 最后一难正文 总共8道题。 1. MC0455 四大名著-西…