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

简历_熟悉缓存高并发场景处理方法,如缓存穿透、缓存击穿、缓存雪崩

系列博客目录


文章目录

  • 系列博客目录
  • 1.缓存穿透
    • 总结
  • 2.缓存雪崩
  • 3.缓存击穿
  • 代码总结


1.缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
在这里插入图片描述

常见的解决方案有两种:

  1. 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:额外的内存消耗、可能造成短期的不一致
  2. 布隆过滤
    • 优点:内存占用较少,没有多余key
    • 缺点:实现复杂、存在误判可能

这里选择缓存空对象。

在这里插入图片描述

总结

  1. 缓存穿透产生的原因是什么?

    用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力

  2. 缓存穿透的解决方案有哪些?

    • 缓存null值
    • 布隆过滤
    • 增强id的复杂度,避免被猜测id规律
    • 做好数据的基础格式校验
    • 加强用户权限校验
    • 做好热点参数的限流

2.缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
在这里插入图片描述

解决方案:

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

这部分后面代码总结中没有,因为就是简单的为TTL设置随机值。

3.缓存击穿

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

在这里插入图片描述

常见的解决方案有两种:互斥锁、逻辑过期

在这里插入图片描述
在这里插入图片描述

案例:基于逻辑过期方式解决缓存击穿问题

需求:修改根据id查询商铺的业务,基于逻辑过期方式来解决缓存击穿问题

在这里插入图片描述
这里最左边缓存未命中,返回空而不进行其他操作,比如查数据库回设缓存,是为了主要掌握逻辑过期的逻辑,而不注重于其他。

代码总结

package com.hmdp.service.impl;import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.*;/*** <p>*  服务实现类* </p>** @author 虎哥* @since 2021-12-22*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {@ResourceStringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 实现缓存穿透// Shop shop = queryWithPassThrough(id);// 利用互斥锁解决缓存击穿// Shop shop = queryWithMutex(id);// 利用逻辑过期解决缓存击穿Shop shop = queryWithLogicalExpire(id);if (shop == null) {return  Result.fail("店铺不存在");}// 7.返回return Result.ok(shop);}private static  final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public Shop queryWithLogicalExpire(Long  id){String key = CACHE_SHOP_KEY + id;// 1.从Redis查询商户缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if(StrUtil.isBlank(shopJson)){// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);JSONObject data = (JSONObject) redisData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())){// 5.1 未过期,直接返回店铺信息return shop;}// 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 {//重建缓存this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);} finally {// 释放锁unLock(lockKey);}});}// 6.4 不成功,返回过期商铺信息return shop;}public Shop queryWithMutex(Long  id){String key = CACHE_SHOP_KEY + id;// 1.从Redis查询商户缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if(StrUtil.isNotBlank(shopJson)){// 3.存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return shop;}// 命中的是否是空值if(shopJson != null){// 返回一个错误信息 , 现在是空值return null;}// 实现缓存重建// 4.1 获取互斥锁String lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判断获取互斥锁是否成功if(!isLock){// 4.3 失败,休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 4.4 成功, 根据id查询数据库shop = getById(id);// 模拟重建延迟Thread.sleep(200);// 5.不存在,返回错误if(shop == null){// 将空值写入RedisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放互斥锁unLock(lockKey);}// 8.返回return shop;}public Shop queryWithPassThrough(Long  id){String key = CACHE_SHOP_KEY + id;// 1.从Redis查询商户缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if(StrUtil.isNotBlank(shopJson)){// 3.存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return shop;}// 命中的是否是空值if(shopJson != null){// 返回一个错误信息 , 现在是空值return null;}// 4.不存在,根据id查询数据库Shop shop = getById(id);// 5.不存在,返回错误if(shop == null){// 将空值写入RedisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);// 7.返回return shop;}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);}public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {// 1.查询店铺数据Shop shop = getById(id);// 为了更好观察逻辑过期设置睡眠时间Thread.sleep(200);// 2.封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 3.写入RedisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}@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 + shop.getId());return Result.ok();}}
http://www.lryc.cn/news/516219.html

相关文章:

  • 阿里云电商平台用户行为分析与人群画像系统设计与实现
  • Go语言的 的输入/输出流(I/O Streams)核心知识
  • 57.在 Vue 3 中使用 OpenLayers 点击选择 Feature 设置特定颜色
  • 数据结构C语言描述8(图文结合)--哈希、哈希冲突、开放地址法、链地址法等实现
  • 自动化立体库安全使用管理制度完整版
  • 云打印之拼多多打印组件交互协议
  • TCP 演进之路:软硬件跷跷板与新征程
  • React最小状态管理Jotai
  • 计算机网络 —— 网络编程(TCP)
  • 字玩FontPlayer开发笔记4 性能优化 首屏加载时间优化
  • RabbitMQ案例
  • 智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之13 方案再探之4:特定于领域的模板 之 div模型(完整版)
  • WebRtc02:WebRtc架构、目录结构、运行机制
  • 数据结构复习 (顺序查找,对半查找,斐波那契查找,插值查找,分块查找)
  • el-input输入框需要支持多输入,最后传输给后台的字段值以逗号分割
  • C# 枚举格式字符串
  • 【51单片机-零基础chapter1】
  • 记录:导出功能:接收文件流数据进行导出(vue3)
  • 基于Spring Boot + Vue3实现的在线汽车保养维修预约管理系统源码+文档
  • PHP框架+gatewayworker实现在线1对1聊天--接收消息(7)
  • 18.1、网络安全策略分类 流程 内容
  • 深入理解连接池:从数据库到HTTP的优化之道
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue智慧养老医护系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】
  • 关于使用vue-cropperjs上传一张图后,再次上传时,裁剪的图片不更新的问题
  • 学习threejs,导入VTK格式的模型
  • 大麦抢票科技狠活
  • PostgreSQL 表达式
  • WPF区域导航+导航参数使用+路由守卫+导航日志
  • Springboot启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘
  • qt-C++笔记之动画框架(Qt Animation Framework)入门