1、什么是缓存穿透?
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,下一次查询时,也会查询到数据库。
2、缓存穿透的解决办法
(1)缓存空对象 (2)布隆过滤
缓存空对象:顾名思义,为了防止下一次查询不查询数据库,当发现查询redis和数据库都不存在 数据时,可以存入一个value长度为0的字符串到redis中。 布隆过滤: 布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走 哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在, 则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一 定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,假设布 隆过滤器判断这个数据不存在,则直接返回
布隆过滤优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突。
这里我就说以下思想,不实现布隆过滤了,挺复杂的。
3、优缺点
缓存空对象
优点:实现简单,维护方便
缺点:
额外的内存消耗
可能造成短期的不一致
布隆过滤
优点:内存占用较少,没有多余key
缺点:
实现复杂
存在误判可能
4、具体实现
例:根据id查询商铺类型。
//建表语句
CREATE TABLE `tb_shop_type` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称',
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标',
`sort` int(3) UNSIGNED NULL DEFAULT NULL COMMENT '顺序',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
流程图:
代码实现:
//缓存穿透
public Result queryByType(Long id){
//查询缓存
String strJson = redisTemplate.opsForValue().get("cache_shop_type_id_"+id);
//判断是否命中缓存
//***命中,返回数据
if(!StrUtil.isEmpty(strJson)){
ShopType st = JSONUtil.toBean(strJson, ShopType.class);
return Result.success(st);
}
//缓存缓存穿透后需要判断strJson,这里不能使用strJson.length() == 0 或者 strJson == "",
//如果strJson=Null,可能是因为redis里没有数据,而不是查到了缓存击穿的数据
if(strJson != null)
return Result.error("无此商店类型ss");
//***没命中
//数据库查询
ShopType st = mapper.queryById(id);
if(st==null){
//缓存穿透,添加 值为“”的数据
stringRedisTemplate.opsForValue().set( "cache_shop_type_id_"+id,"",10, TimeUnit.MINUTES);
return Result.error("无此商店类型");
}
//将数据加入缓存,有效期20分钟
stringRedisTemplate.opsForValue().set("cache_shop_type_id_"+id,JSONUtil.toJsonStr(st),20L, TimeUnit.MINUTES);
return Result.success(st);
}
}
这是我的Result:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}