Redis实战篇(二)查询缓存

2023-11-01

一、什么是缓存

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

1、 缓存的作用:

  • 降低后端负载
  • 提高读写效率,降低响应时间

2、缓存的成本:

  • 数据一致性成本
  • 代码维护成本
  • 运维成本

二、添加Redis缓存

在这里插入图片描述

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String key = "code:shop:" + id;
        //1、从redis中获取缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2、判断是否存在
        if (StringUtil.isNotBlank(shopJson)) {
            //3、存在直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //4、不存在,查询数据库
        Shop shop = getById(id);
        //5、不存在,返回错误
        if (shop == null) {
            return Result.fail("未查询到数据");
        }
        //6、存在,写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        //7、返回
        return Result.ok(shop);
    }

三、缓存更新策略

1、三种更新策略

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

如何选择:
根据业务场景,当低一致性需求时,使用内存淘汰机制。例如店铺类型的查询缓存。
当高一致性需求时,使用主动更新,并以超时剔除作为辅助。例如商铺详情查询。

2、主动更新策略

策略 说明
Cache Aside Pattern 由缓存的调用者,在更新数据库的同时更新缓存
Read/Write Through Pattern 缓存和数据库整合成一个服务,由服务来维护一致性。
Write Behind Caching Pattern 调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库

在这里插入图片描述

3、总结

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

  1. 低一致性需求:使用Reids自带的内存淘汰机制
  2. 高一致性需求:主动更新,并以超时剔除作为辅助方案
    读操作:缓存命中直接返回;缓存未命中查询数据库,并写入缓存,设定超时时间
    写操作:先写数据库,再删除缓存;要确保数据库和缓存操作的原子性

四、缓存穿透

1、定义

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

2、解决方案

(1)缓存空对象

优点 缺点
实现简单,维护方便 额外的内存消耗;可能造成短期的不一致

在这里插入图片描述

(2)布隆过滤器

在这里插入图片描述

优点 缺点
内存占用较少,没有多余key 实现复杂;存在误判可能

五、缓存雪崩

1、定义

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

2、解决方案

(1)给不同的key的TTL添加随机值

(2)利用Redis集群提高服务的可用性

(3)给缓存业务添加降级限流策略

(4)给业务添加多级缓存

六、缓存击穿

1、定义

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

2、解决方案

在这里插入图片描述

在这里插入图片描述

3、基于互斥锁方式解决缓存击穿问题

在这里插入图片描述

    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, 
            Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(shopJson, type);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, 
                		dbFallback, time, unit);
            }
            // 4.4.获取锁成功,根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在,返回错误
            if (r == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue()
                		.set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return r;
    }

4、基于逻辑过期方式解决缓存击穿问题

在这里插入图片描述
线程 存数据到redis

    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, 
            Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return r;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock){
            // 6.3.成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    R newR = dbFallback.apply(id);
                    // 重建缓存
                    this.setWithLogicalExpire(key, newR, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    // 释放锁
                    unlock(lockKey);
                }
            });
        }
        // 6.4.返回过期的商铺信息
        return r;
    }
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue()
        				.setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
    public void setWithLogicalExpire(String key, Object value, 
    					Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now()
        					.plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

七、缓存工具封装

八、总结

请添加图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis实战篇(二)查询缓存 的相关文章

  • 有没有办法在 Redis 和关系数据库中使用带有 @RedisHash 的实体?

    我正在使用Spring引导 为了将我的实体保存在关系数据库上 我配置了一个数据源和我的域类 例如 Entity Table schema schema name name tb name public class table name ex
  • StackExchange.Redis 和 StackExchange.Redis.StrongName 之间有什么区别?

    当我关注Azure时文档 http azure microsoft com en us documentation articles cache dotnet how to use azure redis cache 关于如何在Azure
  • 使用 sidekiq 处理两个独立的 Redis 实例?

    下午好 我有两个独立但相关的应用程序 他们都应该有自己的后台队列 阅读 单独的 Sidekiq 和 Redis 进程 然而 我希望偶尔能够将工作推给app2的队列来自app1 从简单的队列 推送的角度来看 如果app1没有现有的 Sidek
  • 使用brew在MacOSx上安装Redis JSON

    如何使用brew 在 macOSx 上安装 RedisJSON 如何在不编译redis的情况下启用redis上的模块 我不想使用 docker 客户端 Redis Stack 可能是最简单的方法 它不仅仅是 RedisJSON 还包括 Re
  • Laravel - 缓存 Eloquent 并频繁更新

    是否可以对经常修改的对象使用缓存 例如 假设我们有一个 BlogPost 对象 并且有一个经常更改的 num of views 列 以及其他列 是否可以更新缓存和数据库中的 num of views 字段 而不破坏缓存对象并重新创建它 我可
  • 如何使用Spring Cache处理redis异常?

    我目前正在开发一个包含 Spring Data Redis 和 Spring Cache 的项目 在spring data redis中 我使用redis模板调用redis 我在 try catch 块中处理 redis 模板抛出的所有异常
  • Redis hash写入速度非常慢

    我面临一个非常奇怪的问题 使用 Redis 时 我的写入速度非常糟糕 在理想的情况下 写入速度应该接近 RAM 上的写入速度 这是我的基准 package redisbenchmark import redis clients jedis
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • Redis INCRBY 有限制

    我想知道是否有一种方法可以通过我的应用程序的单次往返在 Redis 中执行此操作 对于给定的键K 其可能值V是范围内的任意整数 A B 基本上 它有上限和下限 When an INCRBY or DECRBY发出命令 例如INCRBY ke
  • 如何将node.js管道传输到redis?

    我有很多数据要插入 SET INCR 到redis DB 所以我正在寻找pipeline http redis io topics pipelining 质量插入 http redis io topics mass insert通过node
  • Redis Docker compose无法处理RDB格式版本10

    我无法在 docker compose 文件中启动 redis 容器 我知道docker compose文件没问题 因为我的同事可以成功启动项目 我读到有一个删除 dump rdb 文件的解决方案 但我找不到它 我使用Windows机器 任
  • 有没有办法在 ruby​​ 中重新定义 []=+

    我正在尝试编写一个简单的 DSL 针对 Redis 并且我想自己定义 I have def key val redis zadd name val key end 我想定义 def key val redis zincrby name va
  • Node Js:Redis 作业在完成其任务后未完成

    希望你们做得很好 我在我的 Nodejs 项目中实现了 BullMQ Bull 的下一个主要版本 来安排发送电子邮件的作业 例如 发送忘记密码请求的电子邮件 所以 我编写了如下所示的代码 用户服务 await resetPasswordJo
  • Java 将字节转换为二进制安全字符串

    我有一些以字节为单位的数据 我想将它们放入Redis中 但是Redis只接受二进制安全字符串 而我的数据有一些二进制非安全字节 那么如何将这些字节转换为二进制安全字符串以便将它们保存到 Redis 中呢 Base64 对我有用 但它使数据更
  • 有没有办法用Lettuce自动发现Redis集群中新的集群节点IP

    我有一个Redis集群 3主3从 运行在一个库伯内斯簇 该集群通过Kubernetes 服务 Kube 服务 我将我的应用程序服务器连接到 Redis 集群 使用Kube 服务作为 URI 通过 Redis 的 Lettuce java 客
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • 如何使 Redis 缓存中数据层次结构(树)的部分内容无效

    我有一些产品数据 需要在 Redis 缓存中存储多个版本 数据由 JSON 序列化对象组成 获取普通 基本 数据的过程很昂贵 将其定制为不同版本的过程也很昂贵 因此我想缓存所有版本以尽可能进行优化 数据结构看起来像这样 BaseProduc
  • 在 Redis 上为 Django 和 Express.js 应用程序共享会话存储

    我想创建一个包含一些登录用户的 Django 应用程序 另一方面 由于我想要一些实时功能 所以我想使用 Express js 应用程序 现在的问题是 我不希望身份不明的用户访问 Express js 应用程序的日期 因此 我必须在 Expr
  • 想要在后台不间断地运行redis-server

    我已经下载了 redis 2 6 16 tar gz 文件并安装成功 安装后我运行 src redis server 它工作正常 但我不想每次都手动运行 src redis server 而是希望 redis server 作为后台进程持续
  • 如何在Redis中只保存一个数据库?

    我是 Redis 新手 有一个与备份相关的问题 目前 我有一个实例在 Windows 服务器上运行 在这个实例中 我当前有一项 工作 将数据存储在一个数据库中 我不想备份这些数据 我必须创造一份新工作 我的第一个想法是将数据存储在另一个数据

随机推荐

  • 响应式开发一招致胜 学习视频 分享

    转载于 https www cnblogs com ios9 p 8526562 html
  • VS远程调试与附加调试

    一 发送文件到目标机器 我的程序是x64 所以拷贝这个文件夹到目标机器即可 二 配置目标机器为远程调试状态 zheg 1 双击msvsmon exe 2 配置无需密码直接可以远程 工具 gt 选项 gt 选择无身份验证 允许任何用户进行调试
  • Python numpy函数:shape用法

    shape函数是numpy core fromnumeric中的函数 它的功能是读取矩阵的长度 比如shape 0 就是读取矩阵第一维度的长度 shape的输入参数可以是一个整数 表示维度 也可以是一个矩阵 以下例子可能会好理解一些 1 参
  • Python对比两个文件夹的文件差异并导出差异

    python脚本 coding utf 8 目录对比工具 包含子目录 并列出 1 A比B多了哪些文件 2 B比A多了哪些文件 3 二者相同的文件 md5比较 import os import time import difflib impo
  • vue使用axios发送post请求携带json body参数,后端使用@RequestBody进行接收

    前言 最近在做自己项目中 做一个非常简单的新增用户场景 但是使用原生axios发送post请求的时候 还是踩了不少坑的 唉 说多了都是泪 小小一个新增业务 在自己前后端一起开发的时候 硬是搞了好久 下面就把问题总结分享下 防止后人再踩坑 接
  • Python3---numpy的详细解读,小白疯狂收藏

    前言 近日梳理python3 的numpy的相关知识点 故自我整理成笔记发布 一是供大家评论和建议 查缺补漏自我知识框架 二是可以进一步熟悉知识 达到更好的融汇贯通的情况 希望看到的兄弟姐妹可以不吝赐教 感激不尽 1 维度 一维数组 二维数
  • 文件服务器fuse,分布式文件系统glusterfs安装步骤

    我的系统是 RHEL5 可能环境不一样 需要安装的第三方依赖不一样啊 反正大家在安装的过程中缺少什么就去安装什么 一般都会有提示的 下载 glusterfs 3 2 0 tar gz 源码包 随便解压到一个目录 glusterfs 需要 f
  • Dynamics CRM 2011/2013 通过Javascript给lookup字段赋值

    仅仅做下记录 因为老是用到但老是忘记 var value new Array value 0 new Object value 0 id idValue value 0 name textValue value 0 entityType t
  • hadoop:编写jpsall脚本错误bash: 行 1: jps: 未找到命令

    jpsall脚本 集群使用jps命令查看集群运行情况 bin bash for host in hadoop102 hadoop103 hadoop104 do echo host ssh host jps done 运行jpsall报错
  • caffe中forward过程总结 2

    前面http blog csdn net buyi shizi article details 51504276 总结的是caffe有和卷积有关的forward过程 下面我们总结一下卷积之后和全连接网络Inner Product Layer
  • Linux音视频编程学习(1)

    1 linux音视频概念 声音作为一种模拟信号 需要被A D转换器转换成数字信号 才能被存储在计算机中 因此A D转换视为3步 采样 量化和编码 采样 采样器每个一段时间来读取一次模拟信号 用这些离散的值来代表整个模拟信号的过程 单位时间内
  • MES系统产品规划

    某互联网科技有限公司 MES系统规划初稿 说明书V1 0 审核 批准清单 姓名 职位 签名 日期 撰写 张先生 产品总监 批准 版本修订历史 版本 版本日期 作者 公司 版本描述 A 2021 08 14 张先生 文档初稿 提交供内部修改
  • 点云梯度下采样

    点云下采样又称点云精简 均匀网格下采样 均匀网格下采样法是建立在空间包围盒精简算法之上对散乱点云快速简化的一种算法 其基本思想为 根据点云数据的密度确定最小三维网格 体素 的边长为 a b c a b c a b c 计
  • 2022春招前端最新面试题分享(途牛旅游网)

    途牛旅游网 公司及岗位信息 公司 途牛旅游网 岗位 前端校招 地点 南京 薪资 16k 14薪 面试结果 通过 一面 2022 04 26 自我介绍 介绍一下实习做过的项目 难点 收获 体会 TCP和UDP的区别 TCP如何保持可靠 TCP
  • Java多线程中join()方法和sleep()方法的区别

    这里写目录标题 Java多线程中join 方法和sleep 方法的区别 结论 Java多线程中join 方法和sleep 方法的区别 1 先声明MyThread子类继承Thread类 public class MyThread extend
  • K近邻算法&计算距离&scikit-learn数据集获取——机器学习

    一 K近邻算法 1 什么是K近邻算法 k Nearest Neighbours KNN 简介 最近邻算法是一种分类算法 1968年由Cover和Hart提出 应用场景有字符识别 文本分类 图像识别等领域 该算法的思想 一个样本与数据集中的k
  • java读取文件内容

    直接上代码 两个类 一个工具类 一个测试类 工具类代码 package org example study util import lombok extern slf4j Slf4j import org apache commons la
  • oVirt engine安装手册

    oVirt Engine安装需求 硬件需求 Resource Minimum Recommended CPU 双核CPU 四核或者多个双核CPU 内存 4G内存 不安装warehouse并且内存不被其他程序使用 16G 硬盘 25G可用空间
  • selenium 下载webdriver浏览器驱动

    自动化测试要自动调用浏览器时需要用到selenium模块 官网上的定义为 Selenium 通过使用 WebDriver 支持市场上所有主流浏览器的自动化 Webdriver 是一个 API 和协议 它定义了一个语言中立的接口 用于控制 w
  • Redis实战篇(二)查询缓存

    一 什么是缓存 缓存就是数据交换的缓冲区 是存贮数据的临时地方 一般读写性能较高 1 缓存的作用 降低后端负载 提高读写效率 降低响应时间 2 缓存的成本 数据一致性成本 代码维护成本 运维成本 二 添加Redis缓存 Resource p