Redis:原理速成+项目实战——Redis实战6(封装缓存工具(高级写法)缓存总结)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Redis:原理速成+项目实战——Redis实战5(互斥锁、逻辑过期解决缓存击穿问题)
📚订阅专栏:Redis速成
希望文章对你们有所帮助

如果要看懂这篇文章的代码,请提前了解一下函数式编程,了解一下lambda表达式以及this::getById的写法。

在之前几个文章已经讲解了如何给我们的项目增加Redis缓存,并进行了Redis缓存的最佳实践,并且针对Redis缓存会存在的三个问题(缓存穿透、缓存雪崩、缓存击穿)进行了解决:
Redis缓存最佳实践
解决Redis缓存穿透、雪崩、击穿
互斥锁、逻辑过期解决缓存击穿问题
解决缓存击穿和缓存穿透的代码都还是有点复杂的,但是我们可以发现他们的方法的通用性,因此我们可以对其进行封装,封装成工具类以便于后续的开发。并进行一个简单的总结。

封装缓存工具&&缓存总结(高级写法)

  • 缓存工具封装
    • 工具调用方式
  • 缓存总结
    • 认识缓存
    • 缓存更新策略
    • 缓存穿透
    • 缓存雪崩
    • 缓存击穿

缓存工具封装

我们要基于StringRedisTemplate封装一个缓存工具类,可以满足以下的需求:
1、将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间。
2、将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题。
3、根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题。
4、根据指定的key查询缓存,并反序列化指定类型,需要利用逻辑过期解决缓存击穿问题。

意思很容易理解,但是真实的实现过程中,要满足通用性就需要一些泛型、函数式编程等,具体的内容将在代码中进行注释。
我们将上面的内容封装成工具类CacheClient:

package com.hmdp.utils;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.Shop;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;import static com.hmdp.utils.RedisConstants.*;@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit){//用JSONUtil将其序列化为json对象,再以String形式进行存储stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}//set并带上逻辑过期public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){RedisData redisData = new RedisData();//注入基本的valueredisData.setData(value);//注入逻辑过期时间,也就是当前时间+time,注意要把时间转换成秒redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//这里写入Redis的序列是redisData序列化后的stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 解决缓存穿透,注意这里要做成泛型的* 我们需要的参数是形成key的前缀,id,以及传过来的对象* 这里的id也同样是泛型,因为没办法保证用户传过来的类型是Int还是Long* 这里如果不存在的话,我们就需要查询数据库,通用的函数根本不知道从数据库的哪张表进行查询,因此我们需要自行传入* Function<T, R>表示有参数有返回值的类型,“dbFallback”表示数据库降级逻辑,代表查询Redis失败后要去做的后备方案*/public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;//从Redis中查询序列化的json字符串String json = stringRedisTemplate.opsForValue().get(key);//判断是否存在if (StrUtil.isNotBlank(json)) {//存在,直接返回return JSONUtil.toBean(json, type);}//注意isNotBlank会忽略null,所以还要看命中的是否是nullif (json != null) {return null;}//不存在,根据id查询数据库R r = dbFallback.apply(id);//不存在,返回错误if (r == null){//存一个null到Redis中//这种没用的信息,TTL没必要设置太长了,这里我设置成了2minstringRedisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);return null;}//存在,写入Redis,直接用set方法this.set(key, r, time, unit);//返回return r;}//逻辑过期解决缓存击穿问题private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = CACHE_SHOP_KEY + id;//从Redis中查询商铺缓存,存储对象可以用String或者Hash,这里用StringString shopJson = stringRedisTemplate.opsForValue().get(key);//判断是否存在if (StrUtil.isBlank(shopJson)) {//未命中,直接返回return null;}//命中,先把json反序列化成对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);JSONObject data = (JSONObject) redisData.getData();//注意我们获取到了data信息以后,返回的会是一个JSONObject的格式R r = JSONUtil.toBean(data, type);LocalDateTime expireTime = redisData.getExpireTime();//判断是否过期if (expireTime.isAfter(LocalDateTime.now())){//未过期,直接返回店铺信息return r;}//已过期,缓存重建//获取互斥锁String lockKey = LOCK_SHOP_KEY + id;//LOCK_SHOP_KEY="lock:shop:"boolean isLock = tryLock(lockKey);//判断是否获取锁成功if (isLock){//成功获取锁,开启独立线程来实现缓存重建,用线程池来做CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 重建缓存//查询数据库,这里依旧使用函数式编程R r1 = dbFallback.apply(id);//写入Redisthis.setWithLogicalExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {//释放锁unlock(lockKey);}});}//没有成功获取,直接返回过期信息return r;}private boolean tryLock(String key){//opsForValue里面没有真正的setNx,而是setIfAbsent,表示如果不存在就执行set//值就随便设定一下,重点是要获取到锁,但是设定了TTL为10sBoolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);/*** 如果是直接返回flag,可能会有拆箱操作,造成空指针,需要用BooleanUtil工具类* 因为Boolean不是基本类型的boolean,是boolean的封装类*/return BooleanUtil.isTrue(flag);}private void unlock(String key){stringRedisTemplate.delete(key);}
}

工具调用方式

我们之前对于店铺信息的查询方式就可以修改成如下形式:

    @Resourceprivate CacheClient cacheClient;@Overridepublic Result queryById(Long id) {//缓存穿透的代码调用//这里的this::getById其实就是lambda表达式:id2->getById(id2),写高级点//Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);//逻辑过期解决缓存击穿Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);if (shop == null) {return Result.fail("店铺不存在");}//返回return Result.ok(shop);}

相应的验证方式大家可以借鉴之前的文章,在这里我已经是完成了验证了,证明代码调通,没有发生线程安全问题。

缓存总结

认识缓存

问题答案
什么是缓存一种具备高效读写能力的数据暂存区域
缓存的作用降低后端负载;提高服务读写响应速度
缓存的成本开发成本、运维成本、一致性问题

我们为商铺查询以及商品类型的查询分别添加了缓存,基本的缓存作用模型很容易理解,需要注意一下相应的String与Hash类型的应用。当然之前的文章中我还用了List类型实现了商品类型查询的缓存。

缓存更新策略

1、三种策略
(1)内存淘汰:Redis自带的内存淘汰机制
(2)过期淘汰:利用expire命令给数据设置过期时间
(3)主动更新:主动完成数据库与缓存的同时更新
2、策略选择
(1)低一致性要求:内存淘汰或过期淘汰
(2)高一致性要求:主动更新为主,过期淘汰兜底
3、主动更新的方案有三种,我们选用的是最常用的Cache Aside Pattern,由缓存的调用者在更新数据库同时更新缓存。
4、Cache Aside Pattern的模式选择

问题选择
更新缓存还是删除缓存?删除缓存
先操作数据库还是缓存?先操作数据库
如何确保数据库与缓存操作的原子性?单体系统(事务机制)或分布式系统(分布式事务机制)

总结:先操作数据库、再删除缓存,并保证原子性。
5、最佳实践:Redis缓存最佳实践
(1)查询
①查缓存
②命中,直接返回
③未命中,查询数据库
④将数据库数据写入缓存
⑤返回结果
(2)修改(确保原子性)
①修改数据库
②删除缓存

缓存穿透

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

2、解决方案:
(1)缓存空对象(我们的选择)
(2)布隆过滤
(3)其他:做好数据的基础格式校验、加强用户权限校验;做好热点参数的限流

缓存雪崩

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

2、解决方案:
(1)给不同的key的TTL添加随机值
(2)利用Redis集群提高服务可用性
(3)给缓存业务添加降级限流策略
(4)给业务添加多级缓存

缓存击穿

1、产生原因:也叫作热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

2、解决方案:
(1)互斥锁
(2)逻辑过期

上面三个问题的解决:
解决Redis缓存穿透、雪崩、击穿
互斥锁、逻辑过期解决缓存击穿问题

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

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

相关文章

C# Entity Framework 中不同的数据的加载方式

延迟加载 延迟加载是指在访问导航属性时&#xff0c;Entity Framework 会自动查询数据库并加载相关数据。这种方式在我们需要访问导航属性时比较方便&#xff0c;因为我们无需手动加载相关数据&#xff0c;而且只会在需要时才会进行查询&#xff0c;从而减少了不必要的开销。但…

Python基础-07(for循环、range()函数)

文章目录 前言一、for循环1.for循环结构2.参数 end&#xff08;使其输出时变为横向&#xff09; 二、range()函数1.range(常数)2.range(起始值&#xff0c;结束值)3.range(起始值&#xff0c;结束值&#xff0c;步长)4.例子 总结 前言 此章介绍循环结构中最常用的循环&#xf…

Redisson与SQL乐观锁:实现接口幂等性的终极指南与实战演示

Redisson与SQL乐观锁&#xff1a;实现接口幂等性的终极指南与实战演示 Redisson与SQL乐观锁&#xff1a;实现接口幂等性的终极指南与实战演示 接口幂等性.md

497 蓝桥杯 成绩分析 简单

497 蓝桥杯 成绩分析 简单 //C风格解法1&#xff0c;*max_element&#xff08;&#xff09;与*min_element&#xff08;&#xff09;求最值 //时间复杂度O(n)&#xff0c;通过率100% #include <bits/stdc.h> using namespace std;using ll long long; const int N 1e4 …

java基于VUE3+SSM框架的在线宠物商城+vue论文

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

【langchain】在单个文档知识源的上下文中使用langchain对GPT4All运行查询

In the previous post, Running GPT4All On a Mac Using Python langchain in a Jupyter Notebook, 我发布了一个简单的演练&#xff0c;让GPT4All使用langchain在2015年年中的16GB Macbook Pro上本地运行。在这篇文章中&#xff0c;我将提供一个简单的食谱&#xff0c;展示我们…

【Docker基础三】Docker安装Redis

下载镜像 根据自己需要下载指定版本镜像&#xff0c;所有版本看这&#xff1a;Index of /releases/ (redis.io) 或 https://hub.docker.com/_/redis # 下载指定版本redis镜像 docker pull redis:7.2.0 # 查看镜像是否下载成功 docker images 创建挂载目录 # 宿主机上创建挂…

element-ui table height 属性导致界面卡死

问题: 项目上&#xff0c;有个点击按钮弹出抽屉的交互, 此时界面卡死 原因分析: 一些场景下(父组件使用动态单位/弹窗、抽屉中使用), element-ui 的 table 会循环计算高度值, 导致界面卡死 github 上的一些 issues 和解决方案: Issues ElemeFE/element GitHub 官方讲是升…

bootstrap教程

bootstrap教程 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;让我们一同进入前端开发的世界&#xff0c;探索一款备受欢迎的前端框架——Bootstra…

修改安卓apk设置为安卓主屏幕(launcher)

修改安卓apk 将apk可以设置安卓主屏幕 原理&#xff1a; 将打包好的apk文件进行拆包增加配置文件在重新编译回apk包 需要得相关文件下载 解包 apktool :https://pan.baidu.com/s/1oyCIYak_MHDJCvDbHj_qEA?pwd5j2xdex2jar&#xff1a;https://pan.baidu.com/s/1Nc-0vppVd0G…

2024年学习计划

2024-2-29号完成 机器视觉基础知识学习&#xff0c;并可以处理视觉工作中的需求。 2024-3月份学习SCARA机械手应用开发SCARA机器人-埃斯顿自动化 - ESTUN 2024-4月份继续学习python 好了&#xff0c;今年可以完成这三个目标就满足了 好好学习&#xff0c;天天向上。每天进步…

【数据库系统概论】数据库并发控制机制——并发控制的主要技术之封锁(Locking)

系统文章目录 数据库的四个基本概念&#xff1a;数据、数据库、数据库管理系统和数据库系统 数据库系统的三级模式和二级映射 数据库系统外部的体系结构 数据模型 关系数据库中的关系操作 SQL是什么&#xff1f;它有什么特点&#xff1f; 数据定义之基本表的定义/创建、修改和…

Vue CLI组件通信

目录 一、组件通信简介1.什么是组件通信&#xff1f;2.组件之间如何通信3.组件关系分类4.通信解决方案5.父子通信流程6.父向子通信代码示例7.子向父通信代码示例8.总结 二、props1.Props 定义2.Props 作用3.特点4.代码演示 三、props校验1.思考2.作用3.语法4.代码演示 四、prop…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux系统编程第四天-Linux管道练习题(物联技术666)

更多配套资料CSDN地址:点赞+关注,功德无量。更多配套资料,欢迎私信。 物联技术666_嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记-CSDN博客物联技术666擅长嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记,等方面的知识,物联技术666关注机器学习,arm开发,物联网,嵌入式硬件,单片机…

爬虫网易易盾滑块案例:某乎

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、滑块初步分析 js运行 atob(‘aHR0cHM6Ly93d3cuemhpaHUuY29tL3NpZ25pbg’) 拿到网址&#xff0c;浏览器打开网站&#xff0…

Java十种经典排序算法详解与应用

数组的排序 前言 排序概念 排序是将一组数据&#xff0c;依据指定的顺序进行排列的过程。 排序是算法中的一部分&#xff0c;也叫排序算法。算法处理数据&#xff0c;而数据的处理最好是要找到他们的规律&#xff0c;这个规律中有很大一部分就是要进行排序&#xff0c;所以需…

python总结-装饰器

装饰器 装饰器解决日志问题&#xff08;分三个版本&#xff09;多个装饰器带参数的装饰器wraps装饰器内置装饰器property装饰器staticmethod装饰器classmethod装饰器 类装饰器 概念 装饰器来自 Decorator 的直译。什么叫装饰&#xff0c;就是装点、提供一些额 外的功能。在 pyt…

一个可能的网址服务器证书自动续期自动化实现脚本方案

需求背景&#xff1a; 目标: 您希望为您的网站启用HTTPS&#xff0c;以保护通信安全&#xff0c;并希望这个过程是免费的。 证书类型: 您需要获取SSL/TLS证书&#xff0c;并且希望证书能够自动续期&#xff0c;以确保网站不会因证书过期而停机。 服务器兼容性: 您希望生成的证…

idea2023连接gitee远程仓库

目录 1.在gitee创建远程仓库 2.在Idea里配置git 3.初始化本地仓库 4. 提交推送至远程仓库 注意&#xff1a;提前下好git工具、idea2023&#xff0c;注册gitee账号&#xff0c;本文不介绍 1.在gitee创建远程仓库 创建好后&#xff0c;复制远程仓库地址 2.在Idea里配置git ​ …

【MATLAB源码-第104期】基于matlab的MPSK和MQAM调制解调方式仿真,输出误码率曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 MPSK&#xff08;多相位键控&#xff09; MPSK是一种基于载波相位变化的数字调制技术。它的核心原理是通过改变载波的相位来表示不同的数字信息。这种技术可以分为几个不同的级别&#xff0c;其中最常见的包括&#xff1a; 1…