您好,登錄后才能下訂單哦!
本篇內容主要講解“Spring Cache怎么使用Redisson分布式鎖解決緩存擊穿問題”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Spring Cache怎么使用Redisson分布式鎖解決緩存擊穿問題”吧!
一份熱點數據,它的訪問量非常大。在其緩存失效的瞬間,大量請求直達存儲層,導致服務崩潰。
在項目中,當共享資源出現競爭情況的時候,為了防止出現并發問題,我們一般會采用鎖機制來控制。在單機環境下,可以使用synchronized或Lock來實現;但是在分布式系統中,因為競爭的線程可能不在同一個節點上(同一個jvm中),所以需要一個讓所有進程都能訪問到的鎖來實現,比如mysql、redis、zookeeper。
Redisson是一個在Redis的基礎上實現的Java駐內存數據網格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還實現了可重入鎖(Reentrant Lock)、公平鎖(Fair Lock、聯鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)等,還提供了許多分布式服務。Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者能夠將精力更集中地放在處理業務邏輯上。
不再需要spring-boot-starter-data-redis依賴,但是都添加也不會報錯
<!--redisson--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.17.0</version> </dependency>
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服務器連接端口 host: localhost # Redis服務器地址 password: xxxxxx # Redis服務器連接密碼(默認為空) timeout: 5000 # 超時時間
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銷毀時關閉Redisson實例,但不關閉Redis服務 public RedissonClient redisson() { //創建配置 Config config = new Config(); /** * 連接哨兵:config.useSentinelServers().setMasterName("myMaster").addSentinelAddress() * 連接集群: config.useClusterServers().addNodeAddress() */ config.useSingleServer() .setAddress("redis://" + host + ":" + port) .setPassword(password) .setTimeout(5000); //根據config創建出RedissonClient實例 return Redisson.create(config); } @Bean public CacheManager RedisCacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // 解決查詢緩存轉換異常的問題 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); /** * 新版本中om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)已經被廢棄 * 建議替換為om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL) */ om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化解決亂碼的問題 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // 設置緩存過期時間 為解決緩存雪崩,所以將過期時間加隨機值 .entryTtl(Duration.ofSeconds(60 * 60 + new Random().nextInt(60 * 10))) // 設置key的序列化方式 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) // 設置value的序列化方式 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); // .disableCachingNullValues(); //為防止緩存擊穿,所以允許緩存null值 RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) // 啟用RedisCache以將緩存 put/evict 操作與正在進行的 Spring 管理的事務同步 .transactionAware() .build(); return cacheManager; } }
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 // 一定要設置sync = true開啟異步,否則會導致多個線程同時獲取到鎖 @Cacheable(cacheNames = "user", key = "#id", sync = true) public User findById(Long id) { /** * * 加了@Cacheable之后方法體執行說明緩存中不存在所查詢的數據 * 獲取一把鎖,只要鎖的名字一樣,就是同一把鎖 */ /** * 注意: 如果設置了lock.lock(10,TimeUnit.SECONDS) 鎖過期不會自動續期 * 1、如果我們傳遞了鎖的過期時間,就發送給redis執行腳本,進行占鎖,默認超時就是我們指定的時間 * 2、如果沒有指定鎖的超時時間,就使用30000L(LockWatchdogTimeout 看門狗的默認時間) * 可通過RedissonConfig-->getRedissonClient()-->config.setLockWatchdogTimeout()設置看門狗時間 * 只要占鎖成功就會啟動一個定時任務【就會重新給鎖設置過期時間,新的時間就是看門狗的默認時間】,每隔10s都會自動續期,續期成30s * 看門狗機制 * 1、鎖的自動續期,如果業務超長,運行期間自動給鎖續上新的30s。不用擔心因為業務時間長,鎖自動過期被刪除 * 2、加鎖的業務只要運行完成,就不會給當前鎖續期,即使不手動解鎖,鎖默認在30s以后自動刪除 * */ RLock lock = redissonClient.getLock("redissonClient-lock"); // 對第一個線程執行方法體的線程加鎖,加了@Cacheable,方法執行之后會將方法的返回值存入緩存,下一個線程直接讀取緩存 lock.lock(); User user = new User(); try { user = userMapper.selectById(id); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } return user; } }
到此,相信大家對“Spring Cache怎么使用Redisson分布式鎖解決緩存擊穿問題”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。