点评项目(Redis中间件)&第四部分&缓存常见问题

缓存穿透

请求的数据在Redis里面没有,这种请求直接打到数据库,然后数据库里面也没有这个数据,数据库只能返回空,如果继续请求,就会一直打到数据库,可能数据库就会因此被打崩。

缓存雪崩

缓存击穿

第二种办法保证了绝对不会因为过期问题使访问直接打到数据库,但是为了实现过期这个功能我们就得用更复杂的办法,额外维护一个过期时间来实现过期这个功能。

逻辑过期里面也有锁的使用,但是是交给别的线程,所以不在我们的对比行列里面。

互斥锁解决击穿问题

Redis设置锁的方法

SETNX 是 Redis 的一个字符串(String)操作命令,它的全称是 SET if Not eXists

  • 功能 :当且仅当指定的 key 不存在 时,为这个 key 设置一个值。

  • 返回值

    • 1:表示设置成功(key 原本不存在)。

    • 0:表示设置失败(key 已经存在)。

这是一个原子性 操作,意味着它是不可分割的。在并发环境下,多个客户端同时执行 SETNX 时,Redis 能确保只有一个客户端会成功。

关于拆箱

核心概念:值类型 vs 引用类型

许多语言中,数据类型分为两大类:

  • 值类型 :变量直接存储数据本身。例如:int, float, char, bool 等基本数据类型。

    • 就像你口袋里直接揣着现金。
  • 引用类型 :变量存储的是一个内存地址(引用),这个地址指向实际的数据所在的内存位置。例如:接口数组字符串

    • 就像你口袋里揣着一张银行卡,钱存在银行的保险柜里。

为什么需要装箱和拆箱?

有时候,我们需要在需要引用类型 的地方使用一个值类型

一个经典例子:Java 中的集合(如 ArrayList)

Java 的 ArrayList 类的 add 方法定义是 add(Object obj),它只能接收引用类型 (因为 Object 是所有类的超类)。但如果我们想往里面存一个整数(int,它是值类型),该怎么办?

这就需要 "装箱"------ 把值类型"包装"成一个引用类型的对象。

装箱

装箱 就是将值类型 转换为对应的引用类型的过程。

这个过程通常是隐式的(编译器自动完成)。

Java 示例(在 Java 中称为"自动装箱"):

拆箱

拆箱 是装箱的逆过程,它将引用类型 转换回对应的值类型

这个过程有时需要显式地进行(需要手动指定目标类型)。

Java 示例(在 Java 中称为"自动拆箱"):

拆箱操作本质上是一个强制类型转换。你告诉编译器:"相信我,这个引用类型变量里面装的一定是某个特定的值类型,现在请把它拿出来。"

编译器在编译时无法100%确定你的这个"信任"是否正确,所以它允许代码通过编译。但如果在运行时发现你的"信任"是错的,就会立即抛出一个异常来中断程序。

操作 代码 背后原理 比喻
手动装箱 Integer box = Integer.valueOf(100); 调用静态工厂方法 valueOf,创建一个新对象。 去工厂订做一个盒子。
自动装箱 Integer box = 100; 编译器帮你写 Integer.valueOf(100) 告诉助理"我要个盒子",助理帮你订做。
手动拆箱 int i = box.intValue(); 调用对象实例方法 intValue(),取出其内部值。 自己动手打开盒子拿钱。
自动拆箱 int i = box; 编译器帮你写 box.intValue()
特性 boolean (基本类型) Boolean (包装类)
数据类型 基本数据类型 (引用类型)
默认值 false null
存储位置 栈内存 堆内存(对象本身),栈上存引用
占用空间 约1位(实际按1字节处理) 一整个对象的内存开销(更大)
比较方式 == 比较 == 比较内存地址equals() 比较包装的值
功能 仅能表示 true/false 是一个类,拥有方法(如 toString(), parseBoolean()
允许为null 不允许 允许
用途 普通的条件判断、循环控制 需要对象的地方(如泛型、集合)
java 复制代码
public <R,ID> R queryWithPassThrough(
            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.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R 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);
        return r;
    }

逻辑过期解决击穿问题

相关推荐
AI2中文网12 小时前
AppInventor2 使用 SQLite(二)导入外部库文件
数据库·sqlite·appinventor·appinventor2·拓展·库文件导入·库文件导出
Tiny番茄12 小时前
146. LRU缓存
数据结构·leetcode·缓存
深耕AI12 小时前
【13/20】缓存与性能优化:Redis 在 Express 中的整合,实现用户数据缓存
redis·缓存·性能优化
wuyunhang12345613 小时前
MySQL----MVCC机制
数据库·mysql
淘源码d13 小时前
医院不良事件管理系统:提升医疗安全的智能化解决方案
大数据·数据库·人工智能·源码·医疗安全·不良事件管理
arbboter13 小时前
【代码】关于C#支持文件和文本框的简单日志实现
数据库·c#·日志·log·日志库
对不起初见i13 小时前
MyBatis-Plus 全方位深度指南:从入门到精通
java·数据库·mybatis
Code884813 小时前
[从青铜到王者] Spring Boot+Redis+Kafka电商场景面试全解析
spring boot·redis·微服务·kafka·java面试·电商架构
wow_DG13 小时前
【MySQL ✨】MySQL 入门之旅 · 第九篇:聚合函数与分组查询
数据库·mysql