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

news2024/11/26 20:35:25

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.coloradmin.cn/o/1426076.html

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

相关文章

jQuery为动态添加的子元素添加点击事件

如图&#xff0c;查看学员信息后&#xff0c;相对其信息做相应处理&#xff0c;给后面的管理添加点击功能 通过点击查看全部学员 $.each(pagedata, function (index, item) { // 性别转换 if(item.sex1){sex_1 "男"…

Banana Pi BPI-R4开源路由器开发板快速上手用户手册,采用联发科MT7988芯片设计

介绍 Banana Pi BPI-R4 路由器板采用 MediaTek MT7988A (Filogic 880) 四核 ARM Corex-A73 设计&#xff0c;4GB DDR4 RAM&#xff0c;8GB eMMC&#xff0c;板载 128MB SPI-NAND 闪存&#xff0c;还有 2x 10Gbe SFP、4x Gbe 网络端口&#xff0c;带 USB3 .2端口&#xff0c;M.2…

虚拟机安装archlinux

1、创建虚拟机 2、安装系统4、为了方便&#xff0c;修改密码并使用dos窗口连接 5、磁盘分区 由于新建虚拟机时是8G&#xff0c;所以只建一个分区就行 6、格式化分区并挂载 7、更新镜像 rootarchiso ~ # pacman -Sy 8、 pacstrap -i /mnt base base-devel linux linux-f…

RabbitMQ概念

一 、RabbitMQ概念 1 架构图 2 相关概念 Publisher - ⽣产者&#xff1a;发布消息到RabbitMQ中的Exchange Consumer - 消费者&#xff1a;监听RabbitMQ中的Queue中的消息 Broker&#xff1a;接收和分发消息的应用&#xff0c;RabbitMQ Server就是 Message Broker&#xf…

精酿啤酒:发酵过程中的温度控制与效果

在啤酒酿造过程中&#xff0c;发酵温度的控制重要&#xff0c;它不仅影响酵母菌的活性&#xff0c;还决定了啤酒的口感、香气和风味。对于Fendi Club啤酒来说&#xff0c;切确控制发酵温度是确保啤酒品质和口感的关键环节。 在Fendi Club啤酒的发酵过程中&#xff0c;温度控制尤…

dnslog在sql盲注

首先必须保证sql是在windows下 因为需要使用到UNC路径 保证mysql中的secure_file_priv为空 secure_file_priv为null&#xff0c;load_file则不能加载文件。 secure_file_priv为路径&#xff0c;可以读取路径中的文件&#xff1b; secure_file_priv为空&#xff0c;可以读取磁盘…

Android 12.0 应用中监听系统收到的通知

Android 12.0 通知简介https://blog.csdn.net/Smile_729day/article/details/135502031?spm1001.2014.3001.5502 1. 需求 在系统内置应用中或者在第三方应用中,获取Android系统收到的通知的内容. 2. NotificationListenerService 接口 Android 系统预留了专门的API, 即 No…

Unity - 调节camera物理相机参数(HDRP)

在 “Hierarchy” 右键 -> Volume -> Global Volume new 一个 profile, 设置Mode为Pysical Camera 再点击camera组件&#xff0c;这时候设置 ISO、Shutter Speed、Aperture等参数值还会有效。

[R] Why data manipulation is crucial and sensitive?

What does a data scientist really do? Identifying the pattern in cultural consumption, making fancy graph, engage a dialogue between data and the existing literature, refining hypothesis….(done within one months with three to four online meetings with p…

漏洞01-目录遍历漏洞/敏感信息泄露/URL重定向

目录遍历漏洞/敏感信息泄露/URL重定向 文章目录 目录遍历敏感信息泄露URL重定向 目录遍历 敏感信息泄露 于后台人员的疏忽或者不当的设计&#xff0c;导致不应该被前端用户看到的数据被轻易的访问到。 比如&#xff1a; ---通过访问url下的目录&#xff0c;可以直接列出目录下…

【实战】使用Helm在K8S集群安装MySQL主从

文章目录 前言技术积累什么是HelmStorageClass使用的工具版本 helm 安装 MySQL 1主2从1. 添加 bitnami 的仓库2. 查询 MySQL 资源3. 拉取 MySQL chart 到本地4. 对chart 本地 values-test.yaml 修改5. 对本地 templates 模板 修改6. 安装 MySQL 集群7. 查看部署的 MySQL 集群8.…

算法--数论

这里写目录标题 质数&#xff08;素数&#xff09;定义判断是否为质数暴力写法&#xff0c;试除法基本思想具体写法 优化基本思想&#xff08;时间复杂度根号n&#xff09;具体写法 分解质因数分析题意暴力写法基本思想具体代码 优化基本思想&#xff08;时间复杂度小于等于根号…

聊聊ClickHouse MergeTree引擎的固定/自适应索引粒度

前言 我们在刚开始学习ClickHouse的MergeTree引擎时&#xff0c;就会发现建表语句的末尾总会有SETTINGS index_granularity 8192这句话&#xff08;其实不写也可以&#xff09;&#xff0c;表示索引粒度为8192。在每个data part中&#xff0c;索引粒度参数的含义有二&#xf…

Camera | 15.闪光灯SGM3141概述

芯片说明 SGM3141是一种电流调节降压/升压电荷泵LED驱动器&#xff0c;能够驱动700M输出电流。它非常适合为相机闪光灯应用的高亮度LED供电。SGM3141具有1/2操作模式&#xff0c;用于控制闪光和火炬模式的输出电流。 电源电压在2.7V到5.5V之间工作&#xff0c;非常适合由1芯锂…

CDS view与替代对象

一&#xff0c;简介 替代对象是指用一个CDS view指派给一个透明表或常规数据库视图&#xff0c;使得透明表或常规数据库视图的访问重定向到该CDS view。 替代有诸多要求&#xff1a; 字段数量一致且同名对应&#xff0c;顺序可以不一致对应的字段数据类型长度等必须一致CDS v…

文心一言APP上线新功能,一张照片、三句话即可生成专属数字分身

只需一张照片、录制三句话&#xff0c;就能拥有一个自己的数字分身&#xff1f;这不是科幻电影&#xff0c;而是文心一言APP上线的新功能 - 数字分身。 目前&#xff0c;文心一言APP正在内测数字分身新功能&#xff0c;明天起&#xff0c;iOS和Android用户升级新版本后&#xf…

超简单设置Windows共享文件夹,传输文件无烦恼

前言 开始之前&#xff0c;先让小白感叹一下科技发展真快呀&#xff01;&#xff08;这句话纯粹是为了凑点字数&#xff09; 随着科技的发展&#xff0c;人们手上总会有各种各样的电子设备&#xff1a;电脑、平板、手机、游戏机、电视盒子等等&#xff5e; 有时候想要传输个文…

【Docker】【深度学习算法】在Docker中使用gunicorn启动多个并行算法服务,优化算法服务:从单进程到并行化

文章目录 优化算法服务&#xff1a;从单进程到并行化单个服务架构多并行服务架构Docker化并指定并行服务数量 扩展知识 优化算法服务&#xff1a;从单进程到并行化 在实际应用中&#xff0c;单个算法服务的并发能力可能无法满足需求。为了提高性能和并发处理能力&#xff0c;我…

MySQL基础(三)-学习笔记

一.innodb引擎&#xff1a; 1). 表空间&#xff1a;表空间是InnoDB存储引擎逻辑结构的最高层&#xff0c;启用了参数 innodb_file_per_table(在 8.0版本中默认开启) &#xff0c;则每张表都会有一个表空间&#xff08;xxx.ibd&#xff09;&#xff0c;一个mysql实例可以对应多个…

figure方法详解之清除图形内容

figure方法详解之清除图形内容 一 clf():二 clear():三 clear()方法和clf()方法的区别&#xff1a; 前言 Hello 大家好&#xff01;我是甜美的江。 在数据可视化中&#xff0c;Matplotlib 是一个功能强大且广泛使用的库&#xff0c;它提供了各种方法来创建高质量的图形。在 Mat…