什么是緩存擊穿:
緩存擊穿是指在高并發(fā)環(huán)境下,某個(gè)熱點(diǎn)數(shù)據(jù)的緩存過(guò)期,導(dǎo)致大量請(qǐng)求同時(shí)訪問(wèn)后端存儲(chǔ)系統(tǒng),引起系統(tǒng)性能下降和后端存儲(chǔ)壓力過(guò)大的現(xiàn)象。
解決方案:
1. redisson分布式鎖
本質(zhì)上是緩存重建的過(guò)程中,大量的請(qǐng)求訪問(wèn)到后端的數(shù)據(jù)庫(kù)導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大
那么可以使用redisson分布式鎖來(lái)對(duì)緩存重建的過(guò)程加鎖
其它的線程只有緩存重建完畢之后才可以訪問(wèn)
缺點(diǎn):所有的請(qǐng)求都要等待拿到鎖的線程來(lái)進(jìn)行緩存重建
優(yōu)點(diǎn):數(shù)據(jù)擁有高一致性,適用于某些涉及“錢”的業(yè)務(wù),或者要求數(shù)據(jù)的強(qiáng)一致性的。
- 新建redisson子工程單獨(dú)作為微服務(wù)名字叫redisson-starter
- 引入redisson相關(guān)依賴
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
- 項(xiàng)目結(jié)構(gòu)
RedissonConfigProperties:一些redisson需要的配置項(xiàng),如果是集群此處不能用這種方式
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "redisson-lock")
public class RedissonConfigProperties {
private String redisHost;
private String redisPort;
}
RedissonConfig:配置RedissonClient,并且加入了一些自動(dòng)裝配的配置
import com.yonchao.redisson.service.RedissonLockService;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Data
@Configuration
@EnableConfigurationProperties({RedissonConfigProperties.class})
//當(dāng)引入Service接口時(shí)
@ConditionalOnClass(RedissonLockService.class)
public class RedissonConfig {
@Autowired
private RedissonConfigProperties redissonConfigProperties;
/**
* 對(duì) Redisson 的使用都是通過(guò) RedissonClient 對(duì)象
* @return
*/
@Bean(destroyMethod="shutdown") // 服務(wù)停止后調(diào)用 shutdown 方法。
public RedissonClient redisson() {
// 1.創(chuàng)建配置
Config config = new Config();
// 集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
// 2.根據(jù) Config 創(chuàng)建出 RedissonClient 示例。
config.useSingleServer().setAddress("redis://"+redissonConfigProperties.getRedisHost()+":"+redissonConfigProperties.getRedisPort());
return Redisson.create(config);
}
}
spring.factories:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-736528.html
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yonchao.redisson.service.RedissonLockService
業(yè)務(wù)類:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-736528.html
import com.yonchao.redisson.service.RedissonLockService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedissonLockServiceImpl implements RedissonLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 加鎖
* @param lockKey
* @return
*/
public boolean acquireLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 釋放鎖
* @param lockKey
* @return
*/
public void releaseLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
}
- 其它微服務(wù)通過(guò)pom引入redisson-starter微服務(wù)
- 重建緩存過(guò)程中使用分布式鎖
首先注入RedissonClient
接著判斷布隆過(guò)濾器
接著從緩存中讀數(shù)據(jù)
讀不到的話需要重建緩存
重建緩存:
首先獲取分布式鎖
獲取成功了就查詢數(shù)據(jù)庫(kù)并且重建緩存返回?cái)?shù)據(jù)最后釋放鎖
獲取分布式鎖失敗就等待1秒接著遞歸這個(gè)方法,直到有一個(gè)線程重建緩存成功。
/**
* 使用邏輯過(guò)期的方式
* @param id
* @return
*/
@Override
public ResponseResult selectArticleLogicalExpiration(Long id) {
// 首先經(jīng)過(guò)布隆過(guò)濾器
// 判斷這個(gè)id是不是在布隆過(guò)濾器中
boolean mightContain = bloomFilter.mightContain(id);
// 不存在直接返回
if (!mightContain) {
return ResponseResult.okResult();
}
// 首先從緩存中獲取數(shù)據(jù)
Object articleObj = redisTemplate.opsForValue().get(ARTICLE_KEY + id);
if (Objects.nonNull(articleObj)){
String articleJSON = (String) articleObj;
JSONObject jsonObject = JSON.parseObject(articleJSON);
Long expired = jsonObject.getLong("expired");
// 舊的文章對(duì)象
ApArticle article = JSON.parseObject(articleJSON, ApArticle.class);
if (Objects.nonNull(expired)){
// 未過(guò)期直接返回
if (expired - System.currentTimeMillis() > 0) {
return ResponseResult.okResult(article);
}
// 過(guò)期了進(jìn)行緩存的重建
boolean acquiredLock = redissonLockService.acquireLock(lockKeyRedisson);
// 拿到鎖了就 新開(kāi)一個(gè)線程 進(jìn)行緩存的重建 此處使用分布式鎖,只會(huì)有一個(gè)線程搶占到緩存重建所以不用使用線程池
if (acquiredLock) {
try {
new Thread(() -> {
// 直接重建緩存,不關(guān)心返回值
rebuildCache(id);
}).start();
} finally {
// 最后釋放鎖
redissonLockService.releaseLock(lockKeyRedisson);
}
}
// 開(kāi)啟多線程后直接返回舊的數(shù)據(jù)
return ResponseResult.okResult(article);
}
}
// 緩存中根本沒(méi)有,那么需要直接加鎖重建緩存,此時(shí)不能多線程的去重建緩存,只能通過(guò)分布式鎖的方式,
boolean lockAcquired = redissonLockService.acquireLock(lockKeyLogicalExpiration);
if (lockAcquired){
try {
ApArticle apArticle = rebuildCache(id);
if (Objects.isNull(apArticle)){
// 數(shù)據(jù)不存在就直接返回
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
// 返回獲得的文章數(shù)據(jù)
return ResponseResult.okResult(apArticle);
} finally {
// 最后釋放鎖
redissonLockService.releaseLock(lockKeyLogicalExpiration);
}
} else {
// 沒(méi)有獲取到鎖就等待一段時(shí)間然后再次嘗試獲取鎖
try {
Thread.sleep(1000);
}catch (Exception e){
log.error(e.getMessage());
}
// 等待一段時(shí)間重新校驗(yàn)有沒(méi)有緩存
return selectArticleLogicalExpiration(id);
}
}
public ApArticle rebuildCache(Long id){
// 重建緩存
ApArticle articleDatabase = getById(id);
// 重建緩存
if (Objects.nonNull(articleDatabase)) {
// 設(shè)置邏輯過(guò)期時(shí)間
// 轉(zhuǎn)為jsonString
String articleJsonString = JSON.toJSONString(articleDatabase);
// 轉(zhuǎn)為JSONObject
JSONObject articleJsonObject = JSON.parseObject(articleJsonString);
// 當(dāng)前時(shí)間戳加上 設(shè)置的過(guò)期時(shí)間*1000 因?yàn)闀r(shí)間戳是毫秒
articleJsonObject.put("expired", System.currentTimeMillis() + ARTICLE_EXPIRED * 1000);
redisTemplate.opsForValue().set(ARTICLE_KEY + id, JSON.toJSONString(articleJsonObject));
// 布隆過(guò)濾器過(guò)濾過(guò)的,這個(gè)肯定存在
return articleDatabase;
}
return null;
}
到了這里,關(guān)于redis緩存擊穿,redisson分布式鎖,redis邏輯過(guò)期的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!