点评项目(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 | 不允许 | 允许 |
用途 | 普通的条件判断、循环控制 | 需要对象的地方(如泛型、集合) |
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) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);return r;}