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

Redis(四):缓存击穿及其解决方案(SpringBoot+mybatis-plus)

一、概念

 * 缓存击穿* 定义:指一个热点key在缓存过期的瞬间,同时有大量请求访问这个key,导致所有请求都直接打到数据库上,造成数据库压力激增* 解决方案:* (1)互斥锁 (Mutex Lock)* 核心思想:只允许一个线程去查询数据库,其他线程等待* 实现方式:使用Redis的setnx命令或Redisson分布式锁* (2)热点数据永不过期* 核心思想:对热点数据不设置过期时间,通过其他机制更新* 实现方式:逻辑过期时间 + 异步更新* (3)接口限流与降级* 核心思想:控制访问数据库的并发量* 实现方式:使用限流组件如Sentinel

二、代码

2.1 controller层

package com.study.sredis.stept001.controller;import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.service.User5Service;
import com.study.sredis.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/snow")
public class CacheTest5Controller {@Autowiredpublic User5Service user5Service;/*** 互斥锁方案** @param user* @return*/@PostMapping("/mutexById")public R getUserWithMutex(@RequestBody User user) {try {User user1 = user5Service.getUserWithMutex(user);return R.ok(user1);} catch (Exception e) {return R.fail("用户信息不存在");}}/*** Redisson锁方案*/@PostMapping("/redissonById")public R getUserWithRedisson(@RequestBody User user) {try {User userInfo = user5Service.getUserWithRedissonLock(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}/*** 逻辑过期方案*/@PostMapping("/logicalByid")public R getUserWithLogical(@RequestBody User user) {try {User userInfo = user5Service.getUserWithLogicalExpire(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}/*** 热点数据方案*/@PostMapping("/hotByid")public R getHotUser(@RequestBody User user) {try {User userInfo = user5Service.getHotUser(user);return R.ok(userInfo);} catch (Exception e) {return R.fail("用户信息不存在");}}/*** 更新产品信息*/@PostMapping("/updateUser")public R updateUser(@RequestBody User user) {try {boolean success = user5Service.updateUser(user);return R.ok(success);} catch (Exception e) {return R.fail("修改失败");}}
}

2.2 service层

2.2.1 service接口层

package com.study.sredis.stept001.service;import com.study.sredis.stept001.domain.User;public interface User5Service {/*** 互斥锁方案* @param user* @return*/User getUserWithMutex(User user);/*** Redisson锁方案* @param user* @return*/User getUserWithRedissonLock(User user);/*** 逻辑过期方案* @param user* @return*/User getUserWithLogicalExpire(User user);/*** 热点数据方案* @param user* @return*/User getHotUser(User user);/*** 更新产品信息* @param user* @return*/boolean updateUser(User user);
}

2.2.2 service实现类

package com.study.sredis.stept001.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.sredis.stept001.domain.User;
import com.study.sredis.stept001.dto.RedisData;
import com.study.sredis.stept001.mapper.userMapper;
import com.study.sredis.stept001.service.User5Service;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;/*** 缓存击穿* 定义:指一个热点key在缓存过期的瞬间,同时有大量请求访问这个key,导致所有请求都直接打到数据库上,造成数据库压力激增* 解决方案:* (1)互斥锁 (Mutex Lock)* 核心思想:只允许一个线程去查询数据库,其他线程等待* 实现方式:使用Redis的setnx命令或Redisson分布式锁* (2)热点数据永不过期* 核心思想:对热点数据不设置过期时间,通过其他机制更新* 实现方式:逻辑过期时间 + 异步更新* (3)接口限流与降级* 核心思想:控制访问数据库的并发量* 实现方式:使用限流组件如Sentinel*/
@Service
public class User5ServiceImpl extends ServiceImpl<userMapper, User> implements User5Service {@Autowiredprivate userMapper userMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;private static final String PRODOUCT_KEY_PREFIX = "product";private static final String LOCK_KEY_PREFIX = "lock:product";private static final Duration CACHE_TIMEOUT = Duration.ofMinutes(30);private static final Duration LOCK_TIMEOUT = Duration.ofSeconds(10);/*** 解决方案一:互斥锁** @param user* @return*/@Overridepublic User getUserWithMutex(User user) {String cachekey = PRODOUCT_KEY_PREFIX + user.getId();//        1.从缓存查询User user1 = getFromCache(cachekey);if (user1 != null && user1.getId() != null) {return user1;}
//        2.获取分布式锁String lockKey = LOCK_KEY_PREFIX + user.getId();boolean locked = false;try {
//            尝试获取锁locked = tryLock(lockKey);if (locked) {
//                3.双重检查缓存user1 = getFromCache(cachekey);if (user1 != null && user1.getId() != null) {return user1;}
//                4.查询数据库user1 = this.getById(user.getId());if (user1 != null) {
//                    写入缓存setCache(cachekey, user1, CACHE_TIMEOUT);} else {
//              缓存空值防止穿透setCache(cachekey, new User(), Duration.ofMinutes(5));}return user1;} else {
//                    获取锁失败,等待后重试Thread.sleep(50);return getUserWithMutex(user);}} catch (Exception e) {Thread.currentThread().interrupt();throw new RuntimeException("获取用户信息失败", e);} finally {if (locked) {releaseLock(lockKey);}}}private User getFromCache(String key) {try {return (User) redisTemplate.opsForValue().get(key);} catch (Exception e) {return null;}}private void setCache(String key, Object value, Duration timeout) {try {redisTemplate.opsForValue().set(key, value, timeout);} catch (Exception e) {}}private boolean tryLock(String lockKey) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_TIMEOUT));}private void releaseLock(String lockKey) {try {redisTemplate.delete(lockKey);} catch (Exception e) {}}/*** 解决方案二:分布式锁** @param user* @return*/@Overridepublic User getUserWithRedissonLock(User user) {String cacheKey = PRODOUCT_KEY_PREFIX + user.getId();
//        1.从缓存中查询User user1 = getFromCache(cacheKey);if (user1 != null && user1.getId() != null) {return user1;}String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);try {
//            尝试加锁,等待时间3秒,锁超时时间10秒boolean isLock = lock.tryLock(3, 10, TimeUnit.SECONDS);if (isLock) {try {
//                    3.双重检查user1 = getFromCache(cacheKey);if (user1 != null && user1.getId() != null) {return user1;}
//                    4.查询数据库user1 = this.getById(user.getId());if (user1 != null) {setCache(cacheKey, user1, CACHE_TIMEOUT);} else {setCache(cacheKey, new User(), Duration.ofMinutes(5));}return user1;} finally {lock.unlock();}} else {
//                获取锁失败,等待后重试Thread.sleep(100);return getUserWithRedissonLock(user);}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("获取用户信息失败", e);}}/*** 解决方案三:逻辑过期** @param user* @return*/@Overridepublic User getUserWithLogicalExpire(User user) {String cacheKey = PRODOUCT_KEY_PREFIX + user.getId();
//        1.从缓存查询包装数据RedisData<User> redisData = getRedisDataFromCache(cacheKey);if (redisData == null) {
//            缓存不存在,直接查询并设置return getAndSetProduct(user);}User user1 = redisData.getData();LocalDateTime expireTime = redisData.getExpireTime();
//        2.判断是否逻辑过期if (expireTime.isAfter(LocalDateTime.now())) {
//            未过期,直接返回return user1;}//        3.已过期, 尝试获取锁重建缓存String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);if (lock.tryLock()) {try {
//                4.双重检查redisData = getRedisDataFromCache(cacheKey);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return redisData.getData();}
//                5.异步重建缓存CompletableFuture.runAsync(() -> {rebuildUserCache(user);});} finally {lock.unlock();}}//      返回旧数据return user1;}private User getAndSetProduct(User user) {User user1 = this.getById(user.getId());if (user1 != null) {RedisData<User> redisData = new RedisData<>(user1,LocalDateTime.now().plusMinutes(30)  //逻辑过期时间30分钟);setRedisDataCache(PRODOUCT_KEY_PREFIX + user.getId(), redisData);}return user1;}@Asyncpublic void rebuildUserCache(User user) {String lockKey = LOCK_KEY_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);if (lock.tryLock()) {try {
//                再次检查,防止重复更新User user1 = this.getById(user.getId());if (user1 != null) {RedisData<User> redisData = new RedisData<>(user1,LocalDateTime.now().plusMinutes(30));setRedisDataCache(PRODOUCT_KEY_PREFIX + user.getId(), redisData);}} catch (Exception e) {} finally {lock.unlock();}}}private RedisData<User> getRedisDataFromCache(String key) {try {return (RedisData<User>) redisTemplate.opsForValue().get(key);} catch (Exception e) {return null;}}private void setRedisDataCache(String key, RedisData<User> redisData) {try {
//            永不过期,使用逻辑过期时间控制redisTemplate.opsForValue().set(key, redisData);} catch (Exception e) {}}@Overridepublic User getHotUser(User user) {return null;}@Overridepublic boolean updateUser(User user) {return false;}
}
http://www.dtcms.com/a/564538.html

相关文章:

  • 突破局域网限制,Websocket 服务远程访问cpolar新方案
  • 科普网站建设方案网站容易被百度收录
  • 深圳营销网站建设服务wordpress 语言选择器
  • 广西建设厅关公网站中国能建电子商务平台
  • 9.OpenStack管理(三)
  • 大模型应用02 || 检索增强生成Retrieval-Augmented Generation || RAG概念、应用以及现有挑战
  • 【春秋云镜】CVE-2018-19518
  • [手机AI开发sdk] 安卓上的Linux环境
  • Pandas-之时间序列处理
  • 用 Spark Shell 做交互式数据分析从入门到自包含应用
  • WindowsXP Window7等老系统和Linux Ubuntu等系统在VM虚拟机中安装VM Toools工具实现宿主机虚拟机共用粘贴板
  • 第十二章:终极叩问:我是谁,我往何方?(3)
  • 校园网站建设的缺陷怎么做考试资料网站
  • 【Android Studio】webview 组件在android TV中进行加载,始终是客户端网页的方式进行加载,解决?
  • 应对不规则负载的异步ML模型服务AWS架构设计
  • Docker、Kubernetes与AWS中控机是什么?
  • AWS Bedrock + DeepSeek-R1:开启企业级 AI 开发的新篇章
  • C++ 类似pytorch的库,工具包,或者机器学习的生态
  • 关于手表的网站精品课程网站的建设
  • 正点原子【第四期】Linux之驱动开发学习笔记-10.1 Linux 内核定时器实验
  • Go语言设计模式:命令模式详解
  • Dropout提升模型泛化能力【动手学深度学习:PyTorch版 4.6 暂退法】
  • 网站开发用什么软件有哪些安徽安庆
  • 能够沟通业务的网站彩票网站开发 违法
  • 【机器学习13】异常检测优化、推荐系统、协同过滤
  • can‘t read /etc/apt/sources.list: No such file or directory
  • 深入理解 DNS 与 ICMP:网络世界的地址解析与连通性探测
  • MCU中的RC电路(Resistor-Capacitor Circuit)
  • Flink SQL 调优
  • CISP-PTE认证考试靶场