【Redis】实现缓存及相关问题

Redis实现缓存及相关问题

认识缓存

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

缓存的作用:

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

缓存的成本:

  1. 数据一致性成本
  2. 代码维护成本
  3. 运维成本

添加缓存

缓存作用模型

查询商铺缓存的流程

添加缓存业务代码

@Override
public List<UserDTO> getUserlist() {
    Gson gson = new Gson();
    // 1. 查询redis缓存
    String cache = redisTemplate.opsForValue().get(CACHE_LIST_PRE);
    // 2.1. 存在缓存
    if (StrUtil.isNotBlank(cache)) {
        // 3. 反序列化
        return gson.fromJson(cache, new TypeToken<List<UserDTO>>() {}.getType());
    }
    // 2.2. 不存在缓存
    // 3. 查询数据库
    List<User> userList = list();
    // 4. 信息脱敏
    ArrayList<UserDTO> userDTOList = new ArrayList<>();
    for (User user : userList) {
        UserDTO dto = new UserDTO();
        BeanUtil.copyProperties(user, dto);
        dto.setPhone(DesensitizedUtil.mobilePhone(dto.getPhone()));
        userDTOList.add(dto);
    }
    // 5. 保存到Redis
    redisTemplate.opsForValue()
        .set(CACHE_LIST_PRE, gson.toJson(userDTOList), 2, TimeUnit.MINUTES);
    return userDTOList;
}

缓存更新

缓存更新策略

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

主动更新策略

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

缓存穿透

什么是缓存穿透

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

缓存空对象

优点:实现简单,维护方便

缺点:额外的内存消耗、可能造成短期的不一致

业务实现

// 缓存空对象
@Override
public UserDTO getInfoById(Long id) {
    // 缓存查询
    String userString = redisTemplate.opsForValue()
        .get(CACHE_USER_PRE + id);
    if (StrUtil.isNotBlank(userString)) {
        // 有缓存 => 真实数据
        return gson.fromJson(userString, UserDTO.class);
    }
    if (userString != null) {
        // 有缓存 => 空对象
        throw new BusinessException(404, "用户不存在");
    }
    // 数据库查询
    User user = getById(id);
    if (user == null) {
        // 缓存空对象
        redisTemplate.opsForValue()
            .set(CACHE_USER_PRE + id, "", 2, TimeUnit.MINUTES);
        throw new BusinessException(404, "用户不存在");
    }
    // 信息脱敏
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    // 缓存真实数据
    redisTemplate.opsForValue()
        .set(CACHE_USER_PRE + id, gson.toJson(userDTO), 2, TimeUnit.MINUTES);
    return userDTO;
}

布隆过滤器

优点:内存占用较少,没有多余key

缺点:实现复杂、存在误判可能

image-20240117202714944

缓存雪崩

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

解决方案:

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

缓存击穿/热点Key

什么是缓存击穿

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

互斥锁

优点:没有额外的内存消耗、保证一致性、实现简单

缺点:线程需要等待,性能受影响、可能有死锁风险

互斥锁流程图

互斥锁业务代码

// 热点key-互斥锁
@Override
public List<UserDTO> getUserlist() throws InterruptedException {
    // 1. 查询redis缓存
    String cache = redisTemplate.opsForValue().get(CACHE_LIST_PRE);
    // 2.1. 存在缓存
    if (StrUtil.isNotBlank(cache)) {
        // 3. 反序列化
        return gson.fromJson(cache, new TypeToken<List<UserDTO>>() {}.getType());
    }
    // ⭐️ 获取互斥锁
    String lock = REDIS_LOCK_PRE + "userlist";
    Boolean flag = redisTemplate.opsForValue()
        .setIfAbsent(lock, "1", 30, TimeUnit.SECONDS);
    if (BooleanUtil.isFalse(flag)) {
        // ⭐️ 获取锁失败了 => 休眠 + 递归
        Thread.sleep(200);
        return getUserlist();
    }

    ArrayList<UserDTO> userDTOList = new ArrayList<>();
    try {
        // 2.2. 不存在缓存
        // 3. 查询数据库
        List<User> userList = list();
        log.info("查询数据库");
        // 4. 信息脱敏
        for (User user : userList) {
            UserDTO dto = new UserDTO();
            BeanUtil.copyProperties(user, dto);
            dto.setPhone(DesensitizedUtil.mobilePhone(dto.getPhone()));
            userDTOList.add(dto);
        }
        // 5. 保存到Redis
        redisTemplate.opsForValue()
            .set(CACHE_LIST_PRE, gson.toJson(userDTOList), 10, TimeUnit.SECONDS);
    } catch (Exception ignored) {
    } finally {
        // ⭐️ 释放锁
        redisTemplate.delete(lock);
    }
    return userDTOList;
}

逻辑过期

优点:线程无需等待,性能较好

缺点:不保证一致性、有额外内存消耗、实现复杂

逻辑过期流程图

逻辑过期业务代码

1、LogicalExpiration逻辑过期实体类,使用泛型使其通用化

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LogicalExpiration<T> {
    private T value;
    private Date date;
}

2、核心业务代码

public UserDTO getUserById(Long id) {
    // 1. 查询缓存
    String userString = redisTemplate.opsForValue().get(CACHE_USER_PRE + id);
    if (StrUtil.isBlank(userString)) {
        // 没有缓存 -> 查询数据
        User user = getById(id);
        // 没有数据 -> 报错
        if (user == null) {
            throw new BusinessException(500, "用户不存在");
        }
        // 数据脱敏
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 新建缓存
        Date date = new Date();
        date.setTime(System.currentTimeMillis() + 2 * 60 * 1000);
        redisTemplate.opsForValue().set(CACHE_USER_PRE + id, gson.toJson(
            new LogicalExpiration<>(userDTO, date)
        ));
        // 返回数据
        return userDTO;
    }
    // 存在缓存 => 反序列化拿到对象
    LogicalExpiration<UserDTO> logicalExpiration = gson.fromJson(
        userString, new TypeToken<LogicalExpiration<UserDTO>>() {}.getType());
    // 判断缓存是否过期
    if (logicalExpiration.getDate().before(new Date())) {
        // 已经过期 => 新建线程进行更新缓存
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            UserDTO userDTO = BeanUtil.copyProperties(getById(id), UserDTO.class);
            Date date = new Date();
            // 设置TTL为2min
            date.setTime(System.currentTimeMillis() + 2 * 60 * 1000);
            redisTemplate.opsForValue().set(CACHE_USER_PRE + id, gson.toJson(
                new LogicalExpiration<>(userDTO, date)
            ));
        });
    }
    // 返回数据
    return logicalExpiration.getValue();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PXE高效批量装机

一、系统安装 1. 系统装机的三种引导方式 1. 硬盘安装 2. 光驱&#xff08;u盘&#xff09;安装 3. 网络启动 pxe 2.系统安装过程 加载boot loader Boot Loader 是在操作系统内核运行之前运行的一段小程序。通过这段小程序&#xff0c;我们可以初始化硬件设备、建立内…

C#使用OpenCvSharp4库中5个基础函数-灰度化、高斯模糊、Canny边缘检测、膨胀、腐蚀

C#使用OpenCvSharp4库中5个基础函数-灰度化、高斯模糊、Canny边缘检测、膨胀、腐蚀 使用OpenCV可以对彩色原始图像进行基本的处理&#xff0c;涉及到5个常用的处理&#xff1a; 灰度化 模糊处理 Canny边缘检测 膨胀 腐蚀 1、测试图像lena.jpg 本例中我们采用数字图像处…

day39_mysql

今日内容 0 复习昨日 1 DML 2 约束 3 DQL 0 复习昨日 1 什么是数据库(Database)? 用来组织,存储,管理数据的仓库 2 什么是数据库管理系统(Database Management System-DBMS)? 用来管理数据库的一个软件 3 数据库分类 关系型数据库,Oracle,Mysql,SqlServer,DB2非关系数据库,Re…

深入理解Java中的ForkJoin框架原理

在现代多核处理器的时代&#xff0c;有效地利用并行计算可以极大地提高程序的性能。Java中的ForkJoin框架是Java 7引入的一个并行计算框架&#xff0c;它提供了一种简单而高效的方式来利用多核处理器。在本文中&#xff0c;我们将深入探讨ForkJoin框架的原理和工作方式。 一、什…

《区块链简易速速上手小册》第10章:区块链的未来与趋势(2024 最新版)

文章目录 10.1 区块链的未来展望10.1.1 基础知识10.1.2 主要案例&#xff1a;区块链在金融领域的发展10.1.3 拓展案例 1&#xff1a;区块链在供应链管理中的应用10.1.4 拓展案例 2&#xff1a;区块链在身份管理和隐私保护中的应用 10.2 新兴技术与区块链的融合10.2.1 基础知识1…

数据结构之图

图 图&#xff08;Graph&#xff09;是比树还要难以理解和学习的“多对多”数据结构&#xff0c;可以认为树也是图的一种。图的知识点众多&#xff0c;按照存储路径的方向分&#xff0c;可分为无向图和有向图&#xff0c;按照图的存储结构分&#xff0c;可分为完全图与有向完全…

InstantID: Zero-shot Identity-Preserving Generation in Seconds

文章目录 IntroductionMainReference 记录由国内首创的一个好玩的小项目&#xff0c;图像生成领域的新进展。但我希望现阶段计算机视觉领域的研究能更聚焦在 语义分割 和 三维视觉 上&#xff0c;这样能更方便与机器人等产品和工业实体结合。 Introduction InstantID 是一个基…

phpstudy安装并运行redis

对于一个菜鸟来说&#xff0c;任何一个小步骤都可能研究半天&#xff0c;比如“phpstudy安装并运行redis”这一问题&#xff0c;解决好后第一时间记录下来&#xff0c;方便日后查看&#xff0c;也为遇到同样困难的小伙伴提供个参考&#xff01; 一、phpstudy安装redis 1.打开…

部署monggodb副本集分片集群

分片技术,可以满足MongoDB数据量大量增长的需求。当MongoDB存储海量的数据时&#xff0c;一台机器可能不足以存储数据&#xff0c;也可能不足以提供可接受的读写吞吐量。这时&#xff0c;我们就可以通过在多台机器上分割数据&#xff0c;使得数据库系统能存储和处理更多的数据。…

不废话的将ts一篇文章写完

写在前面 网上很多写ts的教程的&#xff0c;但是我觉得写的太繁琐了&#xff0c;这里我直接将基础用法写上&#xff0c;包括编译后的js代码&#xff0c;以便于你们进行对比&#xff0c; 包括一些常见的报错信息&#xff0c;你们可以对比一下报错信息&#xff0c; 我尽量不废话的…

图形化编程:Scratch与6547网题库的奇妙结合

随着科技的飞速发展&#xff0c;编程教育逐渐成为孩子们不可或缺的技能。其中&#xff0c;图形化编程因其简单易懂的特性&#xff0c;受到了广大儿童的喜爱。Scratch&#xff0c;这一由麻省理工学院开发的编程工具&#xff0c;正是引领这一风潮的佼佼者。与此同时&#xff0c;6…

LeetCode202. 快乐数

202. 快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为…

HTML5的新特性

目录 一&#xff0c;新增语义化标签 二&#xff0c;新增的多媒体标签 三&#xff0c;新增input表单 四&#xff0c;新增的表单属性 一&#xff0c;新增语义化标签 二&#xff0c;新增的多媒体标签 1&#xff0c;音频&#xff1a;<audio>.。。用MP3 <audio src…

JavaScript 基础五 对象

JavaScript 基础五 对象 1. 对象2. 对象使用① 声明语法② 对象有属性和方法组成③ 属性对象属性的增删改查操作 ④ 方法 3. 对象遍历实例 4. 内置对象① 内置对象② 内置对象Math属性方法 引入&#xff1a;保存网站用户信息&#xff0c;比如姓名、年龄、电话号码&#xff0c;用…

ENG-2,可用于监测细胞内钠离子的动态变化

Replacement of Asante NaTrium Green-2 AM钠离子指示探针&#xff0c;ENG-2&#xff0c;可用于监测细胞内钠离子的动态变化 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Replacement of Asante NaTrium Green-2 AM钠离子指示探针&#xff0c;ENG-2 一、基本信…

如何对视频进行翻译

下载视频和翻译软件 视频和翻译软件点击下载就行了&#xff0c;下载之后解压&#xff0c;然后把两个exe点一下。接下来如果资金充裕或者要求比较高的可以使用各个api&#xff0c;网站里有视频介绍了。 经济适用视频翻译 原理简析 首先这个软件对视频的翻译的流程大致如下&a…

使用Python的Turtle模块简单绘制烟花效果

import turtle import random# 初始化屏幕 screen turtle.Screen() screen.bgcolor("black") screen.title("烟花模拟")# 创建一个Turtle来绘制烟花 firework turtle.Turtle() firework.hideturtle() firework.speed(0) # 设置绘图速度为最快# 绘制烟花…

如何系统的自学Python?通义千问、讯飞星火、文心一言及ChatGPT的回答

如何系统的自学Python&#xff1f;来看看通义千问、讯飞星火、文心一言及ChatGPT的回答. 第一个是马老师的通义千问 系统地自学Python是一个循序渐进的过程&#xff0c;从基础语法到实践项目&#xff0c;再到专业领域的深入学习。下面是一个详细的步骤指南&#xff1a; 了解Py…

vulhub靶机activemq环境下的CVE-2015-5254(ActiveMQ 反序列化漏洞)

影响范围 Apache ActiveMQ 5.x ~ Apache ActiveMQ 5.13.0 远程攻击者可以制作一个特殊的序列化 Java 消息服务 (JMS) ObjectMessage 对象&#xff0c;利用该漏洞执行任意代码。 漏洞搭建 没有特殊要求&#xff0c;请看 (3条消息) vulhub搭建方法_himobrinehacken的博客-CSD…

秦始皇帝陵K0007陪葬坑文物展览与文物预防性保护的璀璨交汇

秦始皇帝陵博物院近日迎来了一场引人注目的展览——“何止秦俑——秦陵苑囿之K0007陪葬坑”。此次展览首次集中展示了K0007陪葬坑出土的别具一格的陶俑、鲜活灵动的青铜水禽等珍贵文物。然而&#xff0c;这些文物的安全展出离不开高科技的监测平台与实时终端的24小时不间断保护…