一、缓存击穿

缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库造成巨大的冲击。 --引用哔哩哔哩UP主“黑马程序员”教程《Redis入门到实战教程》中的PPT内容

常见的解决方案有2中:

1.互斥锁

2.逻辑过期

二、互斥锁

互斥锁原理示意图(引用B站视频中的PPT):

简单来说,就是线程1查询缓存未命中,这时它会去获取互斥锁,然后查询数据库获取结果并将结果写入缓存中,最后释放锁。在线程1释放锁之前,其它线程都不能获取锁,只能睡眠一段时间后重试,如果能命中缓存,则返回数据,否则继续尝试获取互斥锁。

该解决方案的优点

1.没有额外的内存消耗

2.保证一致性

3.实现简单

缺点:

1.线程需要等待,性能受到影响

2.可能有死锁的风险

三、代码示例

现根据B站视频中的例子,自己参考写一个互斥锁的示例,根据城市行政区划代码查询城市信息。

首先放出maven依赖,可根据自己的实际情况做增减:

<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--集成mysql数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency><!--springboot中的redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- lettuce pool 缓存连接池--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><!--处理JSON格式--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.3</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.20</version></dependency><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId>  <exclusions><!-- 去掉springboot默认配置 -->  <exclusion>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-logging</artifactId>  </exclusion>  </exclusions>  </dependency><dependency> <!-- 引入log4j2依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><!-- swagger --><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency></dependencies>

配置文件:

server:port: 8000spring:application:name: my_webdatasource:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/my_web?characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: xxxxxxredis:host: 127.0.0.1port: 6379lettuce:pool:max-active: 100max-wait: 1max-idle: 10min-idle: 0timeout: 1000mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

在编写逻辑代码前,事先准备几个常量,放入一个常量类中:

package com.wl.standard.common.result.constants;/*** redis常量* @author wl* @date 2022/3/17 16:09*/
public interface RedisConstants {/*** 空值缓存过期时间(分钟)*/Long CACHE_NULL_TTL = 2L;/*** 城市redis缓存key*/String CACHE_CITY_KEY = "cache:city:";/*** 城市redis缓存过期时间(分钟)*/Long CACHE_CITY_TTL = 30L;/*** 城市redis互斥锁key*/String LOCK_CITY_KEY = "lock:city:";
}

Controller层:

package com.wl.standard.controller;import com.wl.standard.common.result.HttpResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import com.wl.standard.service.CityService;/*** @author wl* @date 2021/11/18*/
@Api(tags = "城市管理接口")
@RestController
@RequestMapping("/city")
public class CityController {private final CityService cityService;@Autowiredpublic CityController(CityService cityService) {this.cityService = cityService;}@GetMapping("/{id}")public HttpResult getCity(@PathVariable("id") String cityCode) {return HttpResult.success(cityService.getByCode(cityCode));}
}

Service层实现类:

编写查询逻辑前,先定义好获取互斥锁和释放锁的方法:

/*** 获取互斥锁* @return*/private Boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtils.isTrue(flag);}/*** 释放锁* @param key*/private void unLock(String key) {stringRedisTemplate.delete(key);}

其中,获取互斥锁和释放锁的传参都应传城市redis互斥锁key

然后编写通过互斥锁机制查询城市信息的方法:

/*** 通过互斥锁机制查询城市信息* @param key*/private City queryCityWithMutex(String key, String cityCode) {City city = null;// 1.查询缓存String cityJson = stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否有数据if (StringUtils.isNotBlank(cityJson)) {// 3.有,则返回city = JSONObject.parseObject(cityJson, City.class);return city;}// 4.无,则获取互斥锁String lockKey = RedisConstants.LOCK_CITY_KEY + cityCode;Boolean isLock = tryLock(lockKey);// 5.判断获取锁是否成功try {if (!isLock) {// 6.获取失败, 休眠并重试Thread.sleep(100);return queryCityWithMutex(key, cityCode);}// 7.获取成功, 查询数据库city = baseMapper.getByCode(cityCode);// 8.判断数据库是否有数据if (city == null) {// 9.无,则将空数据写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 10.有,则将数据写入redisstringRedisTemplate.opsForValue().set(key, JSONObject.toJSONString(city), RedisConstants.CACHE_CITY_TTL, TimeUnit.MINUTES);} catch (Exception e) {throw new RuntimeException(e);} finally {// 11.释放锁unLock(lockKey);}// 12.返回数据return city;}

Service层实现类完整代码:

package com.wl.standard.service.impl;import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wl.standard.common.result.constants.RedisConstants;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import com.wl.standard.mapper.CityMapper;
import com.wl.standard.entity.City;
import com.wl.standard.service.CityService;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.TimeUnit;/*** @author wl* @date 2021/11/18*/
@Service
@Slf4j
public class CityServiceImpl extends ServiceImpl<CityMapper, City> implements CityService{private StringRedisTemplate stringRedisTemplate;@Autowiredpublic CityServiceImpl(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic City getByCode(String cityCode) {String key = RedisConstants.CACHE_CITY_KEY+cityCode;return queryCityWithMutex(key, cityCode);}/*** 通过互斥锁机制查询城市信息* @param key*/private City queryCityWithMutex(String key, String cityCode) {City city = null;// 1.查询缓存String cityJson = stringRedisTemplate.opsForValue().get(key);// 2.判断缓存是否有数据if (StringUtils.isNotBlank(cityJson)) {// 3.有,则返回city = JSONObject.parseObject(cityJson, City.class);return city;}// 4.无,则获取互斥锁String lockKey = RedisConstants.LOCK_CITY_KEY + cityCode;Boolean isLock = tryLock(lockKey);// 5.判断获取锁是否成功try {if (!isLock) {// 6.获取失败, 休眠并重试Thread.sleep(100);return queryCityWithMutex(key, cityCode);}// 7.获取成功, 查询数据库city = baseMapper.getByCode(cityCode);// 8.判断数据库是否有数据if (city == null) {// 9.无,则将空数据写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 10.有,则将数据写入redisstringRedisTemplate.opsForValue().set(key, JSONObject.toJSONString(city), RedisConstants.CACHE_CITY_TTL, TimeUnit.MINUTES);} catch (Exception e) {throw new RuntimeException(e);} finally {// 11.释放锁unLock(lockKey);}// 12.返回数据return city;}/*** 获取互斥锁* @return*/private Boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtils.isTrue(flag);}/*** 释放锁* @param key*/private void unLock(String key) {stringRedisTemplate.delete(key);}
}

后续可将通用的方法抽取出来封装到一个工具类中,至此,代码编写完成,启动服务,清空缓存数据

通过Jmeter工具来进行并发测试,设置100个线程1秒钟跑完

点击start后查看后台日志,发现只查询了一次数据库

刷新缓存,数据已存入

Redis缓存击穿解决方案之互斥锁相关推荐

  1. Redis 缓存击穿

    Redis 缓存击穿,失效以及维度划分 缓存击穿 缓存击穿问题解决 1 互斥锁 2 key设置永不过期 图左侧为客户端直接调用存储层的架构,右侧为比较典型的缓存层+存储层架构. 收益: ①加速读写:因 ...

  2. Redis缓存击穿,缓存穿透,缓存雪崩,附解决方案

    前言 在日常的项目中,缓存的使用场景是比较多的.缓存是分布式系统中的重要组件,主要解决在高并发.大数据场景下,热点数据访问的性能问题,提高性能的数据快速访问.本文以Redis作为缓存时,针对常见的缓存 ...

  3. Redis 缓存击穿,缓存穿透,缓存雪崩原因+解决方案

    一.前言 在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是 ...

  4. Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?

    欢迎关注方志朋的博客,回复"666"获面试宝典 原始数据存储在 DB 中(如 MySQL.Hbase 等),但 DB 的读写性能低.延迟高. 比如 MySQL 在 4 核 8G 上 ...

  5. Redis缓存击穿,穿透,雪崩等问题

    雪崩(随机过期时间.永不过期). 穿透(表示恶意请求,在系统端判断是否符合规则,比如id<0,布隆过滤器). 击穿(查询加for update,永不过期) redis缓存穿透:查询一个数据库中不 ...

  6. 谈谈redis缓存击穿透和缓存击穿的区别,雪崩效应

    谈谈redis缓存击穿透和缓存击穿的区别,雪崩效应 面试经历 在很长的一段时间里,我以为缓存击穿和缓存穿透是一个东西,直到最近去腾讯面试,面试官问我缓存击穿和穿透的区别:我回答它俩是一样的,面试官马上 ...

  7. Redis缓存击穿问题及解决思路

    Redis缓存击穿问题及解决思路 1. 什么是缓存击穿 2. 解决方案 2.1 使用锁来解决(互斥锁) 2.2 逻辑过期 2.3 进行对比 1. 什么是缓存击穿 缓存击穿也叫热点key问题,就是一个被 ...

  8. 【分布式】缓存穿透、缓存雪崩,缓存击穿解决方案

    [分布式]缓存穿透.缓存雪崩,缓存击穿解决方案 参考文章: (1)[分布式]缓存穿透.缓存雪崩,缓存击穿解决方案 (2)https://www.cnblogs.com/dream-to-pku/p/9 ...

  9. Redis缓存击穿和缓存雪崩、缓存穿透以及对应的解决方案

    目录 缓存击穿 缓存击穿的解决方案 缓存雪崩 缓存雪崩的解决方案 缓存穿透 布隆过滤器 缓存击穿 一般我们会对缓存的key设置过期时间,在高并发下,如果在某一时刻这个key刚好过期,此时持续的大并发请 ...

最新文章

  1. java中字符串的创建_【转载】 Java中String类型的两种创建方式
  2. EOS 共识机制 (1)DPOS共识介绍
  3. ConcurrentHashMap的源码分析-tabAt
  4. vue项目没有router文件夹_vueRouter没有报错,但是页面渲染空白
  5. Linux之《荒岛余生》(二)CPU篇
  6. hdu2553 N皇后问题-dfs回溯剪枝+打表
  7. ICCV 2019 | 视频综合理解:行为识别、场景识别以及视频综述
  8. 【浙江大学PAT真题练习乙级】1006 换个格式输出整数 (15分) 真题解析
  9. UILabel显示html文本
  10. Gradle之SourceSet
  11. 图像入门——2. 数字图像发展历史与图像处理概述
  12. 双人联机五子棋html代码,双人联机聊天或五子棋游戏设计【高手请进】
  13. 沐阳Git笔记03文件重命名
  14. 手机手写签名 php,基于canvas实现手写签名(vue)
  15. android16进制编辑器,16进制编辑器app
  16. flume安装以及应用
  17. .pkl文件是什么?python
  18. 安卓开发个人小作品(2)- QQ简仿
  19. diskpart修改盘符
  20. R语言读取 文件 中文乱码,R语言画图 中文不显示

热门文章

  1. C语言结构体——位段概念的讲解
  2. 字节跳动张一鸣:“Stay hungry, Stay young”
  3. 弹性法计算方法的mck法_SAM4E单片机之旅——9、UART与MCK之MAINCK
  4. QT遇到资源文件不显示的问题这样解决
  5. iOS 访问控制权限【Swift】
  6. 新加坡打造绿色数据中心任重道远
  7. 谷歌小恐龙游戏源代码(1)
  8. 力扣908. 最小差值 I
  9. 奥钻机器人_供应TB190-16机器人行走伺服行星齿轮减速机
  10. [转]Windows Notes And Cheatsheet