網站首頁 編程語言 正文
一、緩存擊穿
緩存擊穿問題也叫熱點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: 8000
spring:
application:
name: my_web
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/my_web?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: xxxxxx
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 100
max-wait: 1
max-idle: 10
min-idle: 0
timeout: 1000
mybatis-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;
@Autowired
public 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.無,則將空數據寫入redis
stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
// 10.有,則將數據寫入redis
stringRedisTemplate.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;
@Autowired
public CityServiceImpl(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public 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.無,則將空數據寫入redis
stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
// 10.有,則將數據寫入redis
stringRedisTemplate.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后查看后臺日志,發現只查詢了一次數據庫
刷新緩存,數據已存入
?
?
原文鏈接:https://blog.csdn.net/wl_Honest/article/details/123556902
相關推薦
- 2022-03-12 使用xshell連接linux服務器_Linux
- 2022-09-22 vrrp協議與keepalived淺析
- 2023-04-08 C#字符串和Acsii碼相互轉換_C#教程
- 2022-04-18 create-react-app 中支持sass,怎么搞?
- 2022-07-12 element表格循環校驗,確保數據不重復
- 2022-07-21 CUICatalog: Invalid asset name supplied: ‘‘
- 2022-05-01 Python中的datetime包與time包包和模塊詳情_python
- 2022-05-24 淺談C#中Action和Func回調的常用方式_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支