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

解决缓存击穿的代码[最佳实践版]

介绍缓存击穿:

缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。(Key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期,一般都会从后端db加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端压垮)

解决方案:

1. 互斥锁(Mutex Lock)

2. 分布式锁

3. 缓存预热

4. 缓存更新策略

实践解决:

下方的代码是一个根据id查询帖子的代码,这里将对其使用redis进行改造,并给出一些能够预防缓存穿透的方案具体代码

/*** 根据 id 获取** @param id* @return*/
@GetMapping("/get/vo")
public BaseResponse<PostVO> getPostVOById(long id, HttpServletRequest request) {if (id <= 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}Post post = postService.getById(id);if (post == null) {throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);}return ResultUtils.success(postService.getPostVO(post, request));
}

1. 互斥锁(Mutex Lock)
方案:在缓存中没有数据时,使用互斥锁(如 synchronized 或 ReentrantLock)来确保只有一个线程去查询数据库,并将结果写入缓存。
优点:
确保只有一个线程查询数据库,减少数据库压力。
避免缓存击穿。
缺点:
增加了代码复杂度。
可能会导致短暂的性能下降。
示例:

    private static final long POST_EXPIRE_TIME = 60; // 缓存有效时间private static final long NULL_POST_EXPIRE_TIME = 5; // 未找到的缓存有效时间private final Lock lock = new ReentrantLock();@Autowiredpublic PostController(RedisTemplate<String, Object> redisTemplate, PostService postService) {this.redisTemplate = redisTemplate;this.postService = postService;}/*** 根据 id 获取** @param id* @return*/@GetMapping("/get/vo")public BaseResponse<PostVO> getPostVOById(@RequestParam long id, HttpServletRequest request) {if (id <= 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}Post post = (Post) redisTemplate.opsForValue().get(POST_BY_ID + id);if (post == null) {lock.lock();try {post = (Post) redisTemplate.opsForValue().get(POST_BY_ID + id);if (post == null) {post = postService.getById(id);if (post != null) {redisTemplate.opsForValue().set(POST_BY_ID + id, post, POST_EXPIRE_TIME, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(POST_BY_ID + id, null, NULL_POST_EXPIRE_TIME, TimeUnit.MINUTES);throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);}}} finally {lock.unlock();}}return ResultUtils.success(postService.getPostVO(post, request));}

2. 分布式锁
方案:使用分布式锁(如 Redis 的 SETNX 命令或 Redlock 算法)来确保在分布式环境中只有一个节点去查询数据库,并将结果写入缓存。
优点:
适用于分布式环境。
确保只有一个节点查询数据库,减少数据库压力。
缺点:
实现相对复杂。
需要额外的分布式锁管理。
示例:

private static final long POST_EXPIRE_TIME = 60; // 缓存有效时间private static final long NULL_POST_EXPIRE_TIME = 5; // 未找到的缓存有效时间private static final long LOCK_EXPIRE_TIME = 5; // 分布式锁过期时间@Autowiredpublic PostController(RedisTemplate<String, Object> redisTemplate, PostService postService) {this.redisTemplate = redisTemplate;this.postService = postService;}/*** 根据 id 获取** @param id* @return*/@GetMapping("/get/vo")public BaseResponse<PostVO> getPostVOById(@RequestParam long id, HttpServletRequest request) {if (id <= 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}Post post = (Post) redisTemplate.opsForValue().get(POST_BY_ID + id);if (post == null) {String lockKey = LOCK_KEY + id;Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_EXPIRE_TIME, TimeUnit.SECONDS);if (lockAcquired) {try {post = (Post) redisTemplate.opsForValue().get(POST_BY_ID + id);if (post == null) {post = postService.getById(id);if (post != null) {redisTemplate.opsForValue().set(POST_BY_ID + id, post, POST_EXPIRE_TIME, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(POST_BY_ID + id, null, NULL_POST_EXPIRE_TIME, TimeUnit.MINUTES);throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);}}} finally {redisTemplate.delete(lockKey);}} else {// 等待一段时间后重试try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}return getPostVOById(id, request);}}return ResultUtils.success(postService.getPostVO(post, request));}

3. 缓存预热
方案:在系统启动时,预先将所有可能被查询的数据加载到缓存中。
优点:
可以避免冷启动时的缓存击穿问题。
提高系统的响应速度。
缺点:
需要额外的初始化时间和资源。
如果数据量很大,可能会占用较多的内存。

    @Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate PostService postService;@PostConstructpublic void init() {List<Post> posts = postService.getAllPosts();for (Post post : posts) {redisTemplate.opsForValue().set("post:id:" + post.getId(), post, POST_EXPIRE_TIME, TimeUnit.MINUTES);}}

4. 缓存更新策略
方案:在缓存过期前,提前更新缓存,避免缓存过期后多个请求同时查询数据库。
优点:
减少缓存过期后的并发查询。
提高系统的稳定性。
缺点:
需要额外的逻辑来管理缓存更新。
示例:

    private static final long POST_EXPIRE_TIME = 60; // 缓存有效时间private static final long NULL_POST_EXPIRE_TIME = 5; // 未找到的缓存有效时间private final RedisTemplate<String, Object> redisTemplate;private final PostService postService;@Autowiredpublic PostController(RedisTemplate<String, Object> redisTemplate, PostService postService) {this.redisTemplate = redisTemplate;this.postService = postService;}/*** 根据 id 获取** @param id* @return*/@GetMapping("/get/vo")public BaseResponse<PostVO> getPostVOById(@RequestParam long id, HttpServletRequest request) {if (id <= 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR);}Post post = (Post) redisTemplate.opsForValue().get(POST_BY_ID + id);if (post == null) {post = postService.getById(id);if (post != null) {redisTemplate.opsForValue().set(POST_BY_ID + id, post, POST_EXPIRE_TIME, TimeUnit.MINUTES);} else {redisTemplate.opsForValue().set(POST_BY_ID + id, null, NULL_POST_EXPIRE_TIME, TimeUnit.MINUTES);throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);}}return ResultUtils.success(postService.getPostVO(post, request));}@Scheduled(fixedRate = 30000) // 每30秒执行一次public void updateCache() {List<Post> posts = postService.getAllPosts();for (Post post : posts) {redisTemplate.opsForValue().set(POST_BY_ID + post.getId(), post, POST_EXPIRE_TIME, TimeUnit.MINUTES);}}


http://www.mrgr.cn/news/65733.html

相关文章:

  • Nginx线程模型
  • 从 vue 源码看问题 — 如何理解 vue 响应式?
  • Apache 配置出错常见问题及解决方法
  • Qt:信号和槽
  • 电通旗下VeryStar连摘Campaign 亚太科技MVP及鼎革奖两项大奖
  • 正式开源:从 Greenplum 到 Cloudberry 迁移工具 cbcopy 发布
  • PD取电快充协议芯片,XSP08Q在灯具中的应用
  • RT-Thread学习
  • 【Linux探索学习】第十弹——Linux工具篇(五):详解Linux 中 Git 工具的使用与相关知识点
  • 【无标题】基于SpringBoot的母婴商城的设计与实现
  • Java flnalize垃圾回收
  • 如何安装 Vue.js:适合不同场景的方案
  • 企业CRM选型必看:2024年最佳CRM系统排行
  • 实体(Entity)详解
  • 再谈 TCP 连接的源端口选择
  • Machine Learning on the Edge
  • Uni商城-开源项目
  • 论文 | Evaluating the Robustness of Discrete Prompts
  • Leetcode328奇偶链表,Leetcode21合并两个有序链表,Leetcode206反转链表 三者综合题
  • 2024版最新kali linux手机版安装(非常详细)零基础入门到精通,收藏这篇就够了
  • 阿里云-部署CNI flannel集群网络
  • PyQt5实战——UTF-8编码器功能的实现(六)
  • 【018B】基于51单片机脉搏温度计
  • .Net Core Configuration用法
  • antdesignpro表单中高级的fieldProps属性
  • 【面试经典150】day 11