詳解Spring Cache使用Redisson分布式鎖解決緩存擊穿問題
1 什么是緩存擊穿
一份熱點數(shù)據(jù),它的訪問量非常大。在其緩存失效的瞬間,大量請求直達(dá)存儲層,導(dǎo)致服務(wù)崩潰。
2 為什么要使用分布式鎖
在項目中,當(dāng)共享資源出現(xiàn)競爭情況的時候,為了防止出現(xiàn)并發(fā)問題,我們一般會采用鎖機(jī)制來控制。在單機(jī)環(huán)境下,可以使用synchronized或Lock來實現(xiàn);但是在分布式系統(tǒng)中,因為競爭的線程可能不在同一個節(jié)點上(同一個jvm中),所以需要一個讓所有進(jìn)程都能訪問到的鎖來實現(xiàn),比如mysql、redis、zookeeper。
3 什么是Redisson
Redisson是一個在Redis的基礎(chǔ)上實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還實現(xiàn)了可重入鎖(Reentrant Lock)、公平鎖(Fair Lock、聯(lián)鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)等,還提供了許多分布式服務(wù)。Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進(jìn)使用者對Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上。
4 Spring Boot集成Redisson
4.1 添加maven依賴
不再需要spring-boot-starter-data-redis依賴,但是都添加也不會報錯
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>4.2 配置yml
spring:
datasource:
username: xx
password: xxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=CTT
cache:
type: redis
redis:
database: 0
port: 6379 # Redis服務(wù)器連接端口
host: localhost # Redis服務(wù)器地址
password: xxxxxx # Redis服務(wù)器連接密碼(默認(rèn)為空)
timeout: 5000 # 超時時間4.3 配置RedissonConfig
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.Random;
@EnableCaching
@Configuration
public class RedissonConfig {
? ? @Value("${spring.redis.host}")
? ? private String host;
? ? @Value("${spring.redis.port}")
? ? private String port;
? ? @Value("${spring.redis.password}")
? ? private String password;
? ? @Bean(destroyMethod = "shutdown") ?// bean銷毀時關(guān)閉Redisson實例,但不關(guān)閉Redis服務(wù)
? ? public RedissonClient redisson() {
? ? ? ? //創(chuàng)建配置
? ? ? ? Config config = new Config();
? ? ? ? /**
? ? ? ? ?* ?連接哨兵:config.useSentinelServers().setMasterName("myMaster").addSentinelAddress()
? ? ? ? ?* ?連接集群: config.useClusterServers().addNodeAddress()
? ? ? ? ?*/
? ? ? ? config.useSingleServer()
? ? ? ? ? ? ? ? .setAddress("redis://" + host + ":" + port)
? ? ? ? ? ? ? ? .setPassword(password)
? ? ? ? ? ? ? ? .setTimeout(5000);
? ? ? ? //根據(jù)config創(chuàng)建出RedissonClient實例
? ? ? ? return Redisson.create(config);
? ? }
? ? @Bean
? ? public CacheManager RedisCacheManager(RedisConnectionFactory factory) {
? ? ? ? RedisSerializer<String> redisSerializer = new StringRedisSerializer();
? ? ? ? Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
? ? ? ? // 解決查詢緩存轉(zhuǎn)換異常的問題
? ? ? ? ObjectMapper om = new ObjectMapper();
? ? ? ? om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
? ? ? ? /**
? ? ? ? ?* 新版本中om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)已經(jīng)被廢棄
? ? ? ? ?* 建議替換為om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL)
? ? ? ? ?*/
? ? ? ? om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
? ? ? ? jackson2JsonRedisSerializer.setObjectMapper(om);
? ? ? ? // 配置序列化解決亂碼的問題
? ? ? ? RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
? ? ? ? ? ? ? ? // 設(shè)置緩存過期時間 ?為解決緩存雪崩,所以將過期時間加隨機(jī)值
? ? ? ? ? ? ? ? .entryTtl(Duration.ofSeconds(60 * 60 + new Random().nextInt(60 * 10)))
? ? ? ? ? ? ? ? // 設(shè)置key的序列化方式
? ? ? ? ? ? ? ? .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
? ? ? ? ? ? ? ? // 設(shè)置value的序列化方式
? ? ? ? ? ? ? ? .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
? ? ? ? // .disableCachingNullValues(); //為防止緩存擊穿,所以允許緩存null值
? ? ? ? RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
? ? ? ? ? ? ? ? .cacheDefaults(config)
? ? ? ? ? ? ? ? // 啟用RedisCache以將緩存 put/evict 操作與正在進(jìn)行的 Spring 管理的事務(wù)同步
? ? ? ? ? ? ? ? .transactionAware()
? ? ? ? ? ? ? ? .build();
? ? ? ? return cacheManager;
? ? }
}5 使用Redisson的分布式鎖解決緩存擊穿
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.company.dubbodemo.entity.User;
import com.company.dubbodemo.mapper.UserMapper;
import com.company.dubbodemo.service.UserService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
? ? ? ? implements UserService {
? ??
? ? @Resource
? ? private RedissonClient redissonClient;
? ? @Resource
? ? private UserMapper userMapper;
? ? @Override
? ? // 一定要設(shè)置sync = true開啟異步,否則會導(dǎo)致多個線程同時獲取到鎖
? ? @Cacheable(cacheNames = "user", key = "#id", sync = true)
? ? public User findById(Long id) {
? ? ? ? /**
? ? ? ? ?*
? ? ? ? ?* 加了@Cacheable之后方法體執(zhí)行說明緩存中不存在所查詢的數(shù)據(jù)
? ? ? ? ?* 獲取一把鎖,只要鎖的名字一樣,就是同一把鎖
? ? ? ? ?*/
? ? ? ? /**
? ? ? ? ?* 注意: 如果設(shè)置了lock.lock(10,TimeUnit.SECONDS) 鎖過期不會自動續(xù)期
? ? ? ? ?* ? ? ?1、如果我們傳遞了鎖的過期時間,就發(fā)送給redis執(zhí)行腳本,進(jìn)行占鎖,默認(rèn)超時就是我們指定的時間
? ? ? ? ?* ? ? ?2、如果沒有指定鎖的超時時間,就使用30000L(LockWatchdogTimeout 看門狗的默認(rèn)時間)
? ? ? ? ?* ? ? ?可通過RedissonConfig-->getRedissonClient()-->config.setLockWatchdogTimeout()設(shè)置看門狗時間
? ? ? ? ?* ? ? ? ? 只要占鎖成功就會啟動一個定時任務(wù)【就會重新給鎖設(shè)置過期時間,新的時間就是看門狗的默認(rèn)時間】,每隔10s都會自動續(xù)期,續(xù)期成30s
? ? ? ? ?* 看門狗機(jī)制
? ? ? ? ?* 1、鎖的自動續(xù)期,如果業(yè)務(wù)超長,運行期間自動給鎖續(xù)上新的30s。不用擔(dān)心因為業(yè)務(wù)時間長,鎖自動過期被刪除
? ? ? ? ?* 2、加鎖的業(yè)務(wù)只要運行完成,就不會給當(dāng)前鎖續(xù)期,即使不手動解鎖,鎖默認(rèn)在30s以后自動刪除
? ? ? ? ?*
? ? ? ? ?*/
? ? ? ? RLock lock = redissonClient.getLock("redissonClient-lock");
? ? ? ? // 對第一個線程執(zhí)行方法體的線程加鎖,加了@Cacheable,方法執(zhí)行之后會將方法的返回值存入緩存,下一個線程直接讀取緩存
? ? ? ? lock.lock();
? ? ? ? User user = new User();
? ? ? ? try {
? ? ? ? ? ? user = userMapper.selectById(id);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? ? ? return user;
? ? }
}到此這篇關(guān)于詳解Spring Cache使用Redisson分布式鎖解決緩存擊穿問題的文章就介紹到這了,更多相關(guān)Spring Cache 緩存擊穿內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用JAXBContext輕松實現(xiàn)Java和xml的互相轉(zhuǎn)換方式
這篇文章主要介紹了依靠JAXBContext輕松實現(xiàn)Java和xml的互相轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
實例分析Java中public static void main(String args[])是什么意思
這篇文章主要介紹了實例分析Java中public static void main(String args[])的意義,詳細(xì)分析了Java主函數(shù)main關(guān)鍵字聲明的具體含義和用法,需要的朋友可以參考下2015-12-12
Spring事務(wù)中的事務(wù)傳播行為使用方式詳解
Spring框架作為一個輕量級的開源框架,在企業(yè)應(yīng)用開發(fā)中被廣泛使用,在Spring事務(wù)管理中,事務(wù)傳播行為是非常重要的一部分,它定義了方法如何參與到已經(jīng)存在的事務(wù)中或者如何開啟新的事務(wù),本文將詳細(xì)介紹Spring事務(wù)中的幾種事務(wù)傳播行為,詳細(xì)講解具體使用方法2023-06-06
springboot @Value實現(xiàn)獲取計算機(jī)中絕對路徑文件的內(nèi)容
這篇文章主要介紹了springboot @Value實現(xiàn)獲取計算機(jī)中絕對路徑文件的內(nèi)容,具有很好的參考價值,希望對大家有所幫助。2021-09-09
獲取JPEGImageEncoder和JPEGCode這兩個類的方法
下面小編就為大家?guī)硪黄@取JPEGImageEncoder和JPEGCode這兩個類的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
springboot版本升級以及解決springsecurity漏洞的問題
這篇文章主要介紹了springboot版本升級以及解決springsecurity漏洞的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08

