一,場景概述:
在高并發(fā)的環(huán)境下,比如淘寶,京東不定時的促銷活動,大量的用戶訪問會導致數(shù)據(jù)庫的性能下降,進而有可能數(shù)據(jù)庫宕機從而不能產(chǎn)生正常的服務,一般一個系統(tǒng)最大的性能瓶頸,就是數(shù)據(jù)庫的io操作,如果發(fā)生大量的io那么他的問題也會隨之而來。從數(shù)據(jù)庫入手也是調(diào)優(yōu)性價比最高的切入點。因此需要對我們的程序進行優(yōu)化.一般采取兩種方案:
①從數(shù)據(jù)庫自身出發(fā):優(yōu)化sql,通過分析sql給sql建立索引,優(yōu)化查詢效率
②盡量避免直接查詢數(shù)據(jù)庫:使用緩存來實現(xiàn)
此文主要以緩存來解決高并發(fā)的效率問題來闡述,工欲善其事必先利其器,我們從原理和實戰(zhàn)兩方面入手來完成優(yōu)化
二,原理
1.緩存的基本使用流程
如圖所示:用戶發(fā)起請求到達服務器,處理業(yè)務的同時,如果涉及到持久層,會經(jīng)歷以下幾個步驟:
①首先到達緩存查詢,查看緩存是否存在
②如果存在,則直接返回
③如果不存在,則前往數(shù)據(jù)庫查詢
④將DB查詢到的結(jié)果返回給客戶端,同時將查詢到的結(jié)果放入緩存一份
⑤再次查詢,直接從緩存里面即可拿到對應的數(shù)據(jù).
但是在高并發(fā)的情況下,大量的請求過來又會導致一系列問題的產(chǎn)生,因此針對不同的問題,我們又有不同的解決方案,詳情如下:
2.緩存產(chǎn)生的問題以及解決方案
在高并發(fā)條件下,無數(shù)的請求并發(fā)會產(chǎn)生以下問題:
①緩存雪崩
②緩存穿透
③緩存擊穿
............
2.1緩存雪崩
我們可以簡單的理解為:由于原有緩存失效,新緩存未到期間(例如:我們設(shè)置緩存時采用了相同的過期時間,在同一時刻出現(xiàn)大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數(shù)據(jù)庫了,而對數(shù)據(jù)庫CPU和內(nèi)存造成巨大壓力,嚴重的會造成數(shù)據(jù)庫宕機。從而形成一系列連鎖反應,造成整個系統(tǒng)崩潰。
解決方案:
①加鎖
②對于緩存的key隨機的設(shè)置過期時間,保證讓緩存不在同一時間失效
③采用隊列方式保證來保證不會有大量的線程對數(shù)據(jù)庫一次性進行讀寫,從而避免失效時大量的并發(fā)請求落到底層存儲系統(tǒng)上
2.2緩存擊穿
如圖所示:緩存穿透指的是對于某個熱點key,在高峰期大量的并發(fā)請求過來的時候,該熱點key正好過期,導致所有的請求一瞬間打在了DB上,從而導致DB宕機或性能下降.
解決方案:
①加鎖
②隊列
2.3緩存穿透
如圖所示:緩存穿透指的是,查詢一個不存在的key,緩存為空,同時數(shù)據(jù)庫也為空,相當于做了無用功,意義不大,如果被別人利用,大量的請求null,將會對數(shù)據(jù)庫的性能產(chǎn)生巨大的影響
解決辦法:
①緩存空值,并給空值key設(shè)置小于5分的過期時間
②采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。
2.4緩存預熱
緩存預熱這個應該是一個比較常見的概念,相信很多小伙伴都應該可以很容易的理解,緩存預熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。這樣就可以避免在用戶請求的時候,先查詢數(shù)據(jù)庫,然后再將數(shù)據(jù)緩存的問題!用戶直接查詢事先被預熱的緩存數(shù)據(jù)!
解決辦法:
①直接寫個緩存刷新頁面,上線時手工操作下;
②數(shù)據(jù)量不大,可以在項目啟動的時候自動進行加載;
③定時刷新緩存;
2.5緩存更新
除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6種策略可供選擇),我們還可以根據(jù)具體的業(yè)務需求進行自定義的緩存淘汰,常見的策略有兩種:
①定時去清理過期的緩存;
②當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統(tǒng)得到新數(shù)據(jù)并更新緩存。
兩者各有優(yōu)劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜!具體用哪種方案,可以根據(jù)自己的應用場景來權(quán)衡。
2.6、緩存降級
當訪問量劇增、服務出現(xiàn)問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統(tǒng)可以根據(jù)一些關(guān)鍵數(shù)據(jù)進行自動降級,也可以配置開關(guān)實現(xiàn)人工降級。降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結(jié)算)。
總結(jié):針對于上面提到的問題不同的場景有不同的解決方案,一般情況下,我們選擇加鎖的方式來完成對緩存產(chǎn)生問題的優(yōu)化,鎖的選擇有多種:
①本地鎖(不能解決集群下產(chǎn)生的問題)
②分布式鎖(redis,zookeper,數(shù)據(jù)庫鎖)
以下將采用加鎖的方式分別闡述解決緩存帶來的問題
三,準備工作
3.1創(chuàng)建springboot工程,導入redis的依賴
springboot版本選擇2.3.6.RELEASE
<!--?redis?-->
????????<dependency>
????????????<groupId>org.springframework.boot</groupId>
????????????<artifactId>spring-boot-starter-data-redis</artifactId>
????????</dependency>
????????<!--?spring2.X集成redis所需common-pool2-->
????????<dependency>
????????????<groupId>org.apache.commons</groupId>
????????????<artifactId>commons-pool2</artifactId>
????????????<version>2.6.0</version>
????????</dependency>
????????<dependency>
????????????<groupId>org.springframework.boot</groupId>
????????????<artifactId>spring-boot-starter-web</artifactId>
????????</dependency>
3.2添加配置文件并創(chuàng)建啟動類(略)
redis:
????host:?192.168.17.166
????port:?6379
????database:?0
????timeout:?1800000
????password:
????lettuce:
??????pool:
????????max-active:?20?#最大連接數(shù)
????????max-wait:?-1????#最大阻塞等待時間(負數(shù)表示沒限制)
????????max-idle:?5????#最大空閑
????????min-idle:?0?????#最小空閑
3.3配置類 ?
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 序列號key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
四,實戰(zhàn)模擬
4.1不加鎖帶來的問題
num的值為0,提前在redis客戶端里面存儲
創(chuàng)建controller和service
啟動測試:
單獨的測試,每次發(fā)請求都沒有問題,但是如果面對大量的請求呢,又會發(fā)生什么問題呢?
使用ab工具進行壓測
在linux里面安裝yum?-y?install?httpd-tools
測試5000個請求,每次請求100個會是什么情況呢?
預想值:5000
實際值:148
為什么呢?
原因是多個線程搶占同一資源,會有不同的線程在同一時刻搶占到同一個值,來回的切換修改,導致了,數(shù)據(jù)不一致的問題
如何解決:上鎖,加入synchronized關(guān)鍵字,再次進行測試
4.2本地鎖
synchonized又叫同步鎖,它通過持有同步監(jiān)視器對象來判斷是否自動上鎖還是解鎖.
①實例方法:同步監(jiān)視器是this
②靜態(tài)方法:同步監(jiān)視器是類.class
③同步代碼塊:同步監(jiān)視器是括號里面的內(nèi)容
??@Override
????public?synchronized??void?testRedisRefresh()?{
????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????String?value?=?redisTemplate.opsForValue().get("num");
????????//校驗
????????if(StringUtils.isEmpty(value)){
????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????return;
????????}
????????//存在將num轉(zhuǎn)換為int
????????int?num?=?Integer.parseInt(value);
????????//存入緩存
????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????}
可以看到,此時的結(jié)果正確,這是因為我們使用了synchronized給方法上了同步鎖,那么我的方法每次只處理一個線程的請求,其余方法只能在外面等著,當我的一個線程處理完,自動釋放鎖之后,其他方法才會依次進入,這樣就可以保證原子性.
但是,為了減少服務器的性能,會將服務器搭建集群,那么本地鎖還能夠適用嗎,我們拭目以待.
搭建同樣的8206,8216,8226三個微服務,使用gatway網(wǎng)關(guān)統(tǒng)一訪問測試:
可以看到數(shù)據(jù)再次出現(xiàn)了不一致,導致出現(xiàn)這種情況的原因在于我們的三個微服務有三把鎖,三把鎖同時工作,
每個線程占到的鎖不一樣,因此會導致數(shù)據(jù)不一致的情況.
本地鎖只能鎖住同一工程內(nèi)的資源,在分布式系統(tǒng)里面都存在局限性。
解決這個問題的辦法在于給集群的程序設(shè)置一把鎖,此時需要分布式鎖。
4.3分布式鎖
4.3.1分布式鎖的解決方案
隨著業(yè)務發(fā)展的需要,原單體單機部署的系統(tǒng)被演化成分布式集群系統(tǒng)后,由于分布式系統(tǒng)多線程、多進程并且分布在不同機器上,這將使原單機部署情況下的并發(fā)控制鎖策略失效,單純的Java?API并不能提供分布式鎖的能力。為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題!
分布式鎖主流的實現(xiàn)方案:
-
基于數(shù)據(jù)庫實現(xiàn)分布式鎖
-
基于緩存(Redis等)
-
基于Zookeeper
每一種分布式鎖解決方案都有各自的優(yōu)缺點:
-
性能:redis最高
-
可靠性:zookeeper最高
這里,我們基于redis實現(xiàn)分布式鎖。
4.3.2redis實現(xiàn)分布式鎖
在redis中可以通過setnx來對一個key上鎖,通過刪除這個key來解鎖,因此基于redis的這個特性我們可以將它做成全局分布式鎖
(1)版本一
修改service代碼如下所示
?@Override
????public???void?testRedisRefresh()?{
????????//鎖定key
????????Boolean?absent?=?redisTemplate.opsForValue().setIfAbsent("lock",?"111");
????????if?(absent){
????????????//處理業(yè)務
????????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????????String?value?=?redisTemplate.opsForValue().get("num");
????????????//校驗
????????????if(StringUtils.isEmpty(value)){
????????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????????return;
????????????}
????????????//存在將num轉(zhuǎn)換為int
????????????int?num?=?Integer.parseInt(value);
????????????//存入緩存
????????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????}else{
????????????//未拿到鎖等待
????????????try?{
????????????????Thread.sleep(100);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????//自旋
????????????this.testRedisRefresh();
????????}
????}
再次啟動三個微服務測試
通過測試發(fā)現(xiàn)結(jié)果為5000,與預想的結(jié)果值一樣
再次分析:
(2)版本二,優(yōu)化
基于2.6.1版本之后的特性我們在使用java執(zhí)行setnx的時候可以給key設(shè)置一個過期時間
?@Override
????public???void?testRedisRefresh()?{
????????//鎖定key
????????Boolean?absent?=?redisTemplate.opsForValue().setIfAbsent("lock",?"111",3L,TimeUnit.SECONDS);
????????if?(absent){
????????????//處理業(yè)務
????????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????????String?value?=?redisTemplate.opsForValue().get("num");
????????????//校驗
????????????if(StringUtils.isEmpty(value)){
????????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????????return;
????????????}
????????????//存在將num轉(zhuǎn)換為int
????????????int?num?=?Integer.parseInt(value);
????????????//存入緩存
????????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????????//釋放鎖
???????????redisTemplate.delete("lock");
????????}else{
????????????//未拿到鎖等待
????????????try?{
????????????????Thread.sleep(100);
????????????????//自旋
????????????????this.testRedisRefresh();
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
測試發(fā)現(xiàn)依然沒問題,再次分析
問題:可能會釋放其他服務器的鎖。
場景:如果業(yè)務邏輯的執(zhí)行時間是7s。執(zhí)行流程如下
-
index1業(yè)務邏輯沒執(zhí)行完,3秒后鎖被自動釋放。
-
index2獲取到鎖,執(zhí)行業(yè)務邏輯,3秒后鎖被自動釋放。
-
index3獲取到鎖,執(zhí)行業(yè)務邏輯
-
index1業(yè)務邏輯執(zhí)行完成,開始調(diào)用del釋放鎖,這時釋放的是index3的鎖,?導致index3的業(yè)務只執(zhí)行1s就被別人釋放。
最終等于沒鎖的情況。
解決:setnx獲取鎖時,設(shè)置一個指定的唯一值(例如:uuid);釋放前獲取這個值,判斷是否自己的鎖
(3)版本三優(yōu)化(uuid防止誤刪)
?@Override
????public???void?testRedisRefresh()?{
????????//設(shè)置唯一表示,防止誤刪除
????????String?uuid?=?UUID.randomUUID().toString().replace("-",?"");
????????//鎖定key
????????Boolean?absent?=?redisTemplate.opsForValue().setIfAbsent("lock",?uuid,3L,TimeUnit.SECONDS);
????????if?(absent){
????????????//處理業(yè)務
????????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????????String?value?=?redisTemplate.opsForValue().get("num");
????????????//校驗
????????????if(StringUtils.isEmpty(value)){
????????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????????return;
????????????}
????????????//存在將num轉(zhuǎn)換為int
????????????int?num?=?Integer.parseInt(value);
????????????//存入緩存
????????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????????
????????????//判斷是否是自己的鎖,防誤刪
???????????if?(uuid.equals(redisTemplate.opsForValue().get("lock"))){
???????????????//釋放鎖
???????????????redisTemplate.delete("lock");
???????????}
????????}else{
????????????//未拿到鎖等待
????????????try?{
????????????????Thread.sleep(100);
????????????????//自旋
????????????????this.testRedisRefresh();
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????}
由于刪除操作不具有原子性因此需要再次優(yōu)化
(4)終極版本(使用lua腳本)
代碼實現(xiàn):
@Override
????public???void?testRedisRefresh()?{
????????//①上鎖
????????//設(shè)置唯一表示,防止誤刪除
????????String?uuid?=?UUID.randomUUID().toString().replace("-",?"");
????????//鎖定key
????????Boolean?absent?=?redisTemplate.opsForValue().setIfAbsent("lock",?uuid,3L,TimeUnit.SECONDS);
????????//②業(yè)務
????????if?(absent){
????????????//處理業(yè)務
????????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????????String?value?=?redisTemplate.opsForValue().get("num");
????????????//校驗
????????????if(StringUtils.isEmpty(value)){
????????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????????return;
????????????}
????????????//存在將num轉(zhuǎn)換為int
????????????int?num?=?Integer.parseInt(value);
????????????//存入緩存
????????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????//③解鎖
????????????????//lua腳本
????????????String?script="if?redis.call(\"get\",KEYS[1])?==?ARGV[1]\n"?+
????????????????????"then\n"?+
????????????????????"????return?redis.call(\"del\",KEYS[1])\n"?+
????????????????????"else\n"?+
????????????????????"????return?0\n"?+
????????????????????"end";
????????????????//創(chuàng)建對象
????????????DefaultRedisScript<Long>?redisScript?=?new?DefaultRedisScript<>();
????????????redisScript.setScriptText(script);
????????????redisScript.setResultType(Long.class);
????????????//執(zhí)行刪除
????????????redisTemplate.execute(redisScript,Arrays.asList("lock",uuid));
????????????//④自旋
????????}else{
????????????//未拿到鎖等待
????????????try?{
????????????????Thread.sleep(100);
????????????????//自旋
????????????????this.testRedisRefresh();
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
4.3.3redision實現(xiàn)分布式鎖
(1)概述:Redisson是一個在Redis的基礎(chǔ)上實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory?Data?Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務。其中包括(BitSet,?Set,?Multimap,?SortedSet,?Map,?List,?Queue,?BlockingQueue,?Deque,?BlockingDeque,?Semaphore,?Lock,?AtomicLong,?CountDownLatch,?Publish?/?Subscribe,?Bloom?filter,?Remote?service,?Spring?cache,?Executor?service,?Live?Object?service,?Scheduler?service)?Redisson提供了使用Redis的最簡單和最便捷的方法。Redisson的宗旨是促進使用者對Redis的關(guān)注分離(Separation?of?Concern),從而讓使用者能夠?qū)⒕Ω械胤旁谔幚順I(yè)務邏輯上。
(2)基本使用
導入依賴
1.導入依賴?service-util
<!--?redisson?-->
<dependency>
???<groupId>org.redisson</groupId>
???<artifactId>redisson</artifactId>
???<version>3.15.3</version>
</dependency>
配置redission,加載redis連接數(shù)據(jù)
@Data
@Configuration
@ConfigurationProperties("spring.redis")
public?class?RedissonConfig?{
????private?String?host;
????private?String?password;
????private?String?port;
????private?int?timeout?=?3000;
????private?static?String?ADDRESS_PREFIX?=?"redis://";
????/**
?????*?自動裝配
?????*/
????@Bean
????RedissonClient?redissonSingle()?{
????????Config?config?=?new?Config();
????????if(StringUtils.isEmpty(host)){
????????????throw?new?RuntimeException("host?is??empty");
????????}
????????SingleServerConfig?serverConfig?=?config.useSingleServer()
????????????????.setAddress(ADDRESS_PREFIX?+?this.host?+?":"?+?port)
????????????????.setTimeout(this.timeout);
????????if(!StringUtils.isEmpty(this.password))?{
????????????serverConfig.setPassword(this.password);
????????}
????????return?Redisson.create(config);
????}
}
(3)實現(xiàn)
@Override
????public???void?testRedisRefresh()?{
????????//上鎖
????????String?skuId="31";
????????String?lockKey="lock:"+skuId;
????????//獲取鎖
????????RLock?lock?=?redissonClient.getLock(lockKey);
????????lock.lock();
????????//處理業(yè)務
????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????String?value?=?redisTemplate.opsForValue().get("num");
????????//校驗
????????if(StringUtils.isEmpty(value)){
????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????return;
????????}
????????//存在將num轉(zhuǎn)換為int
????????int?num?=?Integer.parseInt(value);
????????//????????????//存入緩存
????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????//解鎖
????????lock.unlock();
????}
五,總結(jié)
經(jīng)過分析發(fā)現(xiàn)分布式鎖的實現(xiàn)在一定程度上還是會存在一系列的問題,通過這些問題的解決可以在很大程度的避免數(shù)據(jù)不一致的情況,關(guān)于分布式鎖的實現(xiàn)可以抽取為模板,有業(yè)務場景需要的時候二次修改復用即可.
5.1redis實現(xiàn)分布式鎖四部曲
①上鎖
②判斷true處理業(yè)務
③解鎖(lua腳本)
④自旋
@Override
????public???void?testRedisRefresh()?{
????????//①上鎖
????????//設(shè)置唯一表示,防止誤刪除
????????String?uuid?=?UUID.randomUUID().toString().replace("-",?"");
????????//鎖定key
????????Boolean?absent?=?redisTemplate.opsForValue().setIfAbsent("lock",?uuid,3L,TimeUnit.SECONDS);
????????//②業(yè)務
????????if?(absent){
????????????//處理業(yè)務
????????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????????String?value?=?redisTemplate.opsForValue().get("num");
????????????//校驗
????????????if(StringUtils.isEmpty(value)){
????????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????????return;
????????????}
????????????//存在將num轉(zhuǎn)換為int
????????????int?num?=?Integer.parseInt(value);
????????????//存入緩存
????????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????//③解鎖
????????????????//lua腳本
????????????String?script="if?redis.call(\"get\",KEYS[1])?==?ARGV[1]\n"?+
????????????????????"then\n"?+
????????????????????"????return?redis.call(\"del\",KEYS[1])\n"?+
????????????????????"else\n"?+
????????????????????"????return?0\n"?+
????????????????????"end";
????????????????//創(chuàng)建對象
????????????DefaultRedisScript<Long>?redisScript?=?new?DefaultRedisScript<>();
????????????redisScript.setScriptText(script);
????????????redisScript.setResultType(Long.class);
????????????//執(zhí)行刪除
????????????redisTemplate.execute(redisScript,Arrays.asList("lock",uuid));
????????????//④自旋
????????}else{
????????????//未拿到鎖等待
????????????try?{
????????????????Thread.sleep(100);
????????????????//自旋
????????????????this.testRedisRefresh();
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
5.redission實現(xiàn)分布式鎖三部曲
①上鎖
②業(yè)務邏輯文章來源:http://www.zghlxwxcb.cn/news/detail-806108.html
③解鎖文章來源地址http://www.zghlxwxcb.cn/news/detail-806108.html
?@Override
????public???void?testRedisRefresh()?{
????????//上鎖
????????String?skuId="31";
????????String?lockKey="lock:"+skuId;
????????//獲取鎖
????????RLock?lock?=?redissonClient.getLock(lockKey);
????????lock.lock();
????????//處理業(yè)務
????????//查詢緩存是否存在(預先客戶端設(shè)置num的值為0)
????????String?value?=?redisTemplate.opsForValue().get("num");
????????//校驗
????????if(StringUtils.isEmpty(value)){
????????????//不存在(查詢數(shù)據(jù)庫,并且將數(shù)據(jù)存入redis)
????????????return;
????????}
????????//存在將num轉(zhuǎn)換為int
????????int?num?=?Integer.parseInt(value);
????????//????????????//存入緩存
????????redisTemplate.opsForValue().set("num",String.valueOf(++num));
????????//解鎖
????????lock.unlock();
????}
到了這里,關(guān)于高并發(fā)緩存問題分析以及分布式鎖的實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!