詳解Spring Cache使用Redisson分布式鎖解決緩存擊穿問題
1 什么是緩存擊穿
一份熱點(diǎn)數(shù)據(jù),它的訪問量非常大。在其緩存失效的瞬間,大量請(qǐng)求直達(dá)存儲(chǔ)層,導(dǎo)致服務(wù)崩潰。
2 為什么要使用分布式鎖
在項(xiàng)目中,當(dāng)共享資源出現(xiàn)競(jìng)爭(zhēng)情況的時(shí)候,為了防止出現(xiàn)并發(fā)問題,我們一般會(huì)采用鎖機(jī)制來(lái)控制。在單機(jī)環(huán)境下,可以使用synchronized或Lock來(lái)實(shí)現(xiàn);但是在分布式系統(tǒng)中,因?yàn)楦?jìng)爭(zhēng)的線程可能不在同一個(gè)節(jié)點(diǎn)上(同一個(gè)jvm中),所以需要一個(gè)讓所有進(jìn)程都能訪問到的鎖來(lái)實(shí)現(xiàn),比如mysql、redis、zookeeper。
3 什么是Redisson
Redisson是一個(gè)在Redis的基礎(chǔ)上實(shí)現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對(duì)象,還實(shí)現(xiàn)了可重入鎖(Reentrant Lock)、公平鎖(Fair Lock、聯(lián)鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)等,還提供了許多分布式服務(wù)。Redisson提供了使用Redis的最簡(jiǎn)單和最便捷的方法。Redisson的宗旨是促進(jìn)使用者對(duì)Redis的關(guān)注分離(Separation of Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務(wù)邏輯上。
4 Spring Boot集成Redisson
4.1 添加maven依賴
不再需要spring-boot-starter-data-redis依賴,但是都添加也不會(huì)報(bào)錯(cuò)
<!--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 # 超時(shí)時(shí)間
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銷毀時(shí)關(guān)閉Redisson實(shí)例,但不關(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實(shí)例 ? ? ? ? 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è)置緩存過期時(shí)間 ?為解決緩存雪崩,所以將過期時(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開啟異步,否則會(huì)導(dǎo)致多個(gè)線程同時(shí)獲取到鎖 ? ? @Cacheable(cacheNames = "user", key = "#id", sync = true) ? ? public User findById(Long id) { ? ? ? ? /** ? ? ? ? ?* ? ? ? ? ?* 加了@Cacheable之后方法體執(zhí)行說明緩存中不存在所查詢的數(shù)據(jù) ? ? ? ? ?* 獲取一把鎖,只要鎖的名字一樣,就是同一把鎖 ? ? ? ? ?*/ ? ? ? ? /** ? ? ? ? ?* 注意: 如果設(shè)置了lock.lock(10,TimeUnit.SECONDS) 鎖過期不會(huì)自動(dòng)續(xù)期 ? ? ? ? ?* ? ? ?1、如果我們傳遞了鎖的過期時(shí)間,就發(fā)送給redis執(zhí)行腳本,進(jìn)行占鎖,默認(rèn)超時(shí)就是我們指定的時(shí)間 ? ? ? ? ?* ? ? ?2、如果沒有指定鎖的超時(shí)時(shí)間,就使用30000L(LockWatchdogTimeout 看門狗的默認(rèn)時(shí)間) ? ? ? ? ?* ? ? ?可通過RedissonConfig-->getRedissonClient()-->config.setLockWatchdogTimeout()設(shè)置看門狗時(shí)間 ? ? ? ? ?* ? ? ? ? 只要占鎖成功就會(huì)啟動(dòng)一個(gè)定時(shí)任務(wù)【就會(huì)重新給鎖設(shè)置過期時(shí)間,新的時(shí)間就是看門狗的默認(rèn)時(shí)間】,每隔10s都會(huì)自動(dòng)續(xù)期,續(xù)期成30s ? ? ? ? ?* 看門狗機(jī)制 ? ? ? ? ?* 1、鎖的自動(dòng)續(xù)期,如果業(yè)務(wù)超長(zhǎng),運(yùn)行期間自動(dòng)給鎖續(xù)上新的30s。不用擔(dān)心因?yàn)闃I(yè)務(wù)時(shí)間長(zhǎng),鎖自動(dòng)過期被刪除 ? ? ? ? ?* 2、加鎖的業(yè)務(wù)只要運(yùn)行完成,就不會(huì)給當(dāng)前鎖續(xù)期,即使不手動(dòng)解鎖,鎖默認(rèn)在30s以后自動(dòng)刪除 ? ? ? ? ?* ? ? ? ? ?*/ ? ? ? ? RLock lock = redissonClient.getLock("redissonClient-lock"); ? ? ? ? // 對(duì)第一個(gè)線程執(zhí)行方法體的線程加鎖,加了@Cacheable,方法執(zhí)行之后會(huì)將方法的返回值存入緩存,下一個(gè)線程直接讀取緩存 ? ? ? ? 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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中關(guān)于static和templates的注意事項(xiàng)以及webjars的配置
今天小編就為大家分享一篇關(guān)于SpringBoot中關(guān)于static和templates的注意事項(xiàng)以及webjars的配置,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01Java語(yǔ)言中的文件數(shù)據(jù)流示例詳解
這篇文章主要為大家介紹了Java語(yǔ)言中的文件數(shù)據(jù)流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Java 實(shí)戰(zhàn)項(xiàng)目錘煉之網(wǎng)上花店商城的實(shí)現(xiàn)流程
讀萬(wàn)卷書不如行萬(wàn)里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+servlet+mysql+ajax實(shí)現(xiàn)一個(gè)網(wǎng)上花店商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11Java集合操作之List接口及其實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java集合操作之List接口及其實(shí)現(xiàn)方法,詳細(xì)分析了Java集合操作中List接口原理、功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2015-07-07