当前位置: 首页 > news >正文

黑马点评-Redis缓存更新/穿透/雪崩/击穿

文章目录

        • 商户查询缓存
          • 缓存更新
            • 内存淘汰
            • 超时剔除
            • 主动更新
          • 缓存穿透
            • 缓存空对象
            • 布隆过滤
          • 缓存雪崩
          • 缓存击穿
            • 互斥锁
            • 逻辑过期

商户查询缓存
缓存更新

根据商铺id查询商铺信息,

在我们之前的想法就是,根据key查redis,redis里有的话直接返回其中的数据,如果没有,就查数据库的数据返回,并同时将数据库里查到的数据保存在redis里,这样下一次查询的速度就会更快。

但是我们要考虑到一个问题:redis里的数据并不是一直和数据库里的数据同步。当我们修改了数据库数据,但是redis里保存的还是原来的,这是就有错误了。

这里使用缓存更新策略

内存淘汰

不用自己维护,redis内部的淘汰机制会在内存不足时自动淘汰部分数据。

超时剔除

给缓存数据加上TTL时间,到期后自动删除缓存。

主动更新

编写业务逻辑,修改数据库同时更新缓存。

这里我们使用主动更新。

1.根据id查询店铺,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置缓存时间。

2.根据id修改店铺,先修改数据库,再删除缓存。

这里先修改数据库,再删缓存的顺序是怎么确定的呢?

我们这里直接看会出现问题的场景。

  • 假设先删缓存,再操作数据库(线程A)

A删除了缓存,B这时候来查没有,查到数据库(假设是20),然后写入缓存(20)。然后A继续它的步骤,更新数据库(假设是10),这个时候,缓存(20)和数据库(10)不一致。

在这里插入图片描述

  • 假设先操作数据库,再删缓存(线程B)

A先查询缓存,恰好失效了没命中,查数据库(10),但此时立刻来了B来更新数据库(改为20),然后删除缓存,最后A将之前查到的数据库(10)写入缓存,最后导致缓存(10)和数据库(20)不一致。

在这里插入图片描述

两种情况都会出现问题,但是由于第二种情况出现的概率较低,所以采用第二种方案。

代码:

@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("店铺id不能为空");}// 1.更新数据库updateById(shop);// 2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);return Result.ok();}
缓存穿透

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

如果恶意请求无数次,会使数据库崩溃,我们的解决方案:

缓存空对象

就是再次来请求时,直接返回null,这样就不会再次访问数据库。

简单粗暴,但造成额外的内存消耗和造成短期的不一致(请求的id加入此时真的插入了一条商铺数据,但我们已经给人家返回null导致不一致,只有当TTL过期后用户才能查到数据)(均可设置TTL尽量解决)

在这里插入图片描述

布隆过滤

一种算法思想,由一个二进制向量(或者说位数组)和多个哈希函数组成。

请求时先请求过滤器,发现数据不存在直接拒绝,存在后,则按照寻常步骤进行。

当然,这里过滤器里存的不是那些数据,而是将数据进行哈希处理后得到二进制位(如果所有位置都是1,就认为元素可能存在;如果有任何一个位置是0,则元素肯定不存在)以此判断。

但是仍有问题:不存在则是真不存在,存在的话但数据不一定真的存在(哈希冲突

缺点:实现复杂,存在误判

在这里插入图片描述

所以,我们这里选择缓存空对象方案。

 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;

这里需要说明:

StrUtil.isNotBlank(json)

从redis里查数据,返回的结果只要不是字符串,其他均为false.

所以可能返回空值‘’‘’和null。

缓存雪崩

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

解决方案:

  • 不同key添加一个随机TTL值

    避免同时失效

  • 利用redis集群提高服务可用性

  • 添加降级限流策略

  • 添加多级缓存

缓存击穿

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

在这里插入图片描述

解决方案:

互斥锁

当线程1第一个发现未命中时,获取互斥锁,查询数据库数据写入缓存后释放锁,而在这过程中,其他线程来也发现未命中,但他们获取锁失败,所以它们不能执行接下来的操作,而是一直重试等待,直到命中缓存。

优点:无额外内存消耗,保证数据一致性;实现简单

缺点:线程需要等待,影响性能;可能产生死锁

在这里插入图片描述

逻辑过期

因为问题出现在过期时间,所以我们这里就不设置TTL。这样我们可以看作key”永不过期“

物理过期:Redis自带的TTL机制,到了时间自动删除

逻辑过期:业务层面的控制,数据在Redis里不设置过期时间,但是存储的数据结构中包含一个过期时间字段

由于没有TTL,所有线程来都会命中(没命中说明数据是真的不存在)。当线程未命中时,它获取到锁,然后由它产生一个新线程负责去查询写入操作,而线程1和其他线程则在锁释放前直接返回旧数据。

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

缺点:不保证数据一致性;额外内存消耗;实现复杂

在这里插入图片描述

基于互斥锁来解决根据ID修改商铺问题:

这里我们的互斥锁采用的是redis里的setnx。

SETNX key value

指定的key不存在时,将key的值设为value,如果设置成功返回1,如果key已经存在,就不做任何操作,返回0。

多个客户端同时尝试设置同一个key,只有一个能成功,从而获得锁

 public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值if (shopJson != null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败,休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功,根据id查询数据库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);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}
private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}

基于逻辑过期来解决根据ID修改商铺问题:

 public <R, ID> R queryWithLogicalExpire(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.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return r;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 6.3.成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查询数据库R newR = dbFallback.apply(id);// 重建缓存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}
public class RedisData {private LocalDateTime expireTime;//逻辑过期时间private Object data;
}

在这里插入图片描述>W<

http://www.xdnf.cn/news/536293.html

相关文章:

  • Git上传项目到GitHub
  • Keepalived相关配置和高可用
  • 为什么 LoRaWAN CN470 采用上下异频设计?从协议架构、频谱规划到工程实现的全面解析
  • NHANES最新指标推荐:C-DII
  • 浅谈GC机制-三色标记和混合写屏障
  • 蓝桥杯分享经验
  • 【cursor指南】cursor免费续杯pro会员试用
  • UE 材质基础第二天
  • Java:logback-classic与slf4j版本对应关系
  • ROS 2动态负载均衡系统架构与跨主机外设访问方案j1900(工业机器人集群协同场景)
  • 大语言模型怎么进行记忆的
  • 大语言模型(LLM)如何通过“思考时间”(即推理时的计算资源)提升推理能力
  • 微店平台关键字搜索商品接口技术实现
  • OceanBase 开发者大会:详解 Data × AI 战略,数据库一体化架构再升级
  • rsync实现远程同步
  • vs code SSH配置免密登录
  • day017-磁盘管理-实战
  • 深入探讨Java中的上下文传递与ThreadLocal的局限性及Scoped Values的兴起
  • c++字符串常用语法特性查询示例文档(一)
  • 包装类(1)
  • 22-内部FLASH
  • java day13
  • JVM 垃圾回收器
  • EX文件浏览器:功能强大的安卓文件管理工具
  • 特征值与特征向量的计算——PCA的数学基础
  • 扫描项目依赖漏洞
  • Go语言八股文之分库分表
  • 中服云生产线自动化智能化调度生产系统:打造智能制造新标杆
  • 前端子项目打包集成主项目实战指南
  • 高校快递物流管理系统设计与实现(SpringBoot+MySQL)