最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis高并發(fā)分布鎖的示例

 更新時間:2024年03月08日 09:05:05   作者:楓吹過的柚  
在分布式系統(tǒng)中,實現(xiàn)分布式鎖是一項常見的需求,本文主要介紹了Redis高并發(fā)分布鎖的示例 ,具有一定的參考價值,感興趣的可以了解一下

問題場景

場景一: 沒有捕獲異常

// 僅僅加鎖
// 讀取 stock=15
Boolean ret = stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v)
// TODO 業(yè)務(wù)代碼 stock--
stringRedisTemplate.delete("lock_key");

**問題 **

以上場景在代碼出現(xiàn)異常的時候,會出現(xiàn)死鎖,導(dǎo)致后面的線程無法獲取鎖,會阻塞所有線程

場景二: 線程間交互刪除鎖

// 加鎖,且設(shè)置鎖過期時間
// 讀取 stock = 15
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", "1", 10, TimeUnit.SECONDS);
// TODO 業(yè)務(wù)代碼 stock--
stringRedisTemplate.delete(key);

問題

相對于場景一多了鎖的過期時間

假如線程A執(zhí)行業(yè)務(wù)代碼的時間是15s,而鎖的時間是10s,那么鎖過期后自動會被刪除,此時線程B獲取鎖,執(zhí)行業(yè)務(wù)代碼時間為8s,而這個時候線程A剛好執(zhí)行完業(yè)務(wù)代碼了,就會出現(xiàn)線程A把線程B的鎖刪除掉

// 加鎖,且(給每個線程)設(shè)置鎖過期時間, 刪除鎖時判斷是否當(dāng)前線程
// 讀取  stock = 15
String uuid = UUID.getUuid; 
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", uuid, 10, TimeUnit.SECONDS);
// TODO 業(yè)務(wù)代碼  stock--  15 -> 14
// 判斷是否當(dāng)前線程
if (uuid.equals(stringRedisTemplate.opsForValue().get(key)) {
    // 極端場景下:(執(zhí)行時間定格在9.99秒)突然卡頓 10ms or redis服務(wù)宕機?。?!
    // 此時剛好鎖過期,自動刪除
    // 其他線程獲取鎖,然后會把上個線程的鎖刪除,又會出現(xiàn)bug
	stringRedisTemplate.delete(key);
}

問題

當(dāng)線程A持有鎖,執(zhí)行完扣減庫存后,假設(shè)鎖過期時間是10s,恰好此時在執(zhí)行9.99s的時候出現(xiàn)卡頓等服務(wù)器反應(yīng)過來之間,鎖過期自動刪除了,這個時候線程B獲取鎖,然后執(zhí)行業(yè)務(wù)代碼,此時線程A剛好反應(yīng)過來,執(zhí)行鎖刪除,這樣就會把線程B的鎖刪除,要知道此時線程B是沒有執(zhí)行完業(yè)務(wù)代碼的,鎖刪除后,線程C又獲取鎖,此時線程B執(zhí)行完,又會把線程C的鎖刪除,依次類推

解決方案

方案: 使用Redisson分布式鎖

@Autowire
public Redisson redisson;
   
 public void stock () {
     String key = "key";
     RLock lock = redisson.getLock(key);
     try {
         lock.lock();
         // TODO: 業(yè)務(wù)代碼 
     } catch(Exception e) {
         lock.unlock();
     }
 }

優(yōu)點

  • 自帶鎖續(xù)命功能,默認(rèn)30s過期時間,可以自行調(diào)整過期時間
  • LUA腳本模擬商品減庫存
//模擬一個商品減庫存的原子操作
//lua腳本命令執(zhí)行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_stock_10016", "15");  // 初始化商品10016的庫存
String script = " local count = redis.call('get', KEYS[1]) " +
                " local a = tonumber(count) " +
                " local b = tonumber(ARGV[1]) " +
                " if a >= b then " +
                "   redis.call('set', KEYS[1], a-b) " +
                // 模擬語法報錯回滾操作
                "   bb == 0 " +
                "   return 1 " +
                " end " +
                " return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
System.out.println(obj);

Redisson實現(xiàn)

public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
   long threadId = Thread.currentThread().getId();
   Long ttl = this.tryAcquire(leaseTime, unit, threadId);
   if (ttl != null) {
       RFuture<RedissonLockEntry> future = this.subscribe(threadId);
        this.commandExecutor.syncSubscription(future);
       try {
           while(true) {
               ttl = this.tryAcquire(leaseTime, unit, threadId);
               if (ttl == null) {
                   return;
                }
               if (ttl >= 0L) {
                   this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
               } else {
                   this.getEntry(threadId).getLatch().acquire();
               }
           }
       } finally {
           this.unsubscribe(future, threadId);
       }
   }
}

LUA腳本適合用于做原子操作,在Redisson分布式鎖實現(xiàn)中,就有用到LUA腳本實現(xiàn)創(chuàng)建/獲取鎖的操作,而Redis的事務(wù)機制(multi/exec)非常雞肋,可以對相同的key通過不同的數(shù)據(jù)結(jié)構(gòu)做修改,比如事務(wù)開啟后,將String類型的key,再次使用hset修改,而且還能修改成功,這就意味著事務(wù)已失效,而且不支持事務(wù)回滾

Redisson分布式鎖流程

  • 高并發(fā)下Lua腳本保證了原子性
  • Schedule定期鎖續(xù)命
  • 未獲取鎖的線程先Subscribe channel
  • 自旋,再次嘗試獲取鎖
  • 如果還是未獲取鎖,則通過Semaphore->tryAcquire(ttl.TimeUnit)阻塞所有進(jìn)入自旋代碼塊的線程(這樣做的目的是為了不讓其他線程因為不停的自旋而給服務(wù)器造成壓力,所以讓其他線程先阻塞一段時間,等阻塞時間結(jié)束,再次自旋)
  • 獲取鎖的線程解鎖后,使用Redis的發(fā)布功能進(jìn)行發(fā)布消息,訂閱消息的線程調(diào)用release方法釋放阻塞的線程,再次嘗試獲取鎖
  • 如果是調(diào)用Redisson的tryAcquire(1000,TimeUnit.SECONDS)方法,那么未獲取到鎖的線程不用進(jìn)行自旋,因為時間一到,未獲取到鎖的線程就會自動往下走進(jìn)入業(yè)務(wù)代碼塊

Redisson分布式鎖流程.png

總結(jié)

Redis分布式鎖自己去實現(xiàn)可能會出現(xiàn)幾個問題

沒有在finally顯示釋放鎖,當(dāng)客戶端掛掉了,鎖沒有被及時刪除,這樣會導(dǎo)致死鎖問題,它這個是需要我們顯示的釋放鎖

假如此時我們設(shè)置過期時間,但是我們用的是同一個key,就可能出現(xiàn)下一個線程刪除上一個線程的鎖,但是上一個線程還沒有執(zhí)行完,它這個需要key是不能重復(fù)的

假如我們既設(shè)置了過期時間也指定了不同的key,此時可能因為網(wǎng)絡(luò)延遲出現(xiàn)上一個線程刪除下一個線程的鎖,也就是說業(yè)務(wù)執(zhí)行的時間超過了鎖過期的時間,它這個需要一個鎖續(xù)命的功能

對于Redis它也有事務(wù),但是它的事務(wù)非常雞肋,僅僅只能保證多個指令按照順序執(zhí)行,并不能保證原子性,而且key還能被其他指令修改對應(yīng)的數(shù)據(jù)結(jié)構(gòu),所以我們選擇Redisson來進(jìn)行分布式鎖的實現(xiàn),因為它提供了鎖續(xù)命的功能以及通過lua腳本保證了多個指令的原子操作,主要流程是這樣的

當(dāng)線程搶到了鎖,假如業(yè)務(wù)沒執(zhí)行完,會定時去進(jìn)行鎖續(xù)命,而其他線程會訂閱這個搶到鎖的線程的channel,然后自旋一定時間去嘗試獲取鎖,如果獲取鎖失敗,會被安排進(jìn)入隊列中阻塞,一旦線程釋放鎖,他們會被通知到,然后繼續(xù)去自旋一定時間去嘗試獲取鎖,重復(fù)此操作

到此這篇關(guān)于Redis高并發(fā)分布鎖的示例的文章就介紹到這了,更多相關(guān)Redis高并發(fā)分布鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • redis哈希類型_動力節(jié)點Java學(xué)院整理

    redis哈希類型_動力節(jié)點Java學(xué)院整理

    這篇文章主要介紹了redis哈希類型的常用方法及原理淺析,感興趣的朋友一起看看吧
    2017-08-08
  • 淺談Redis分片集群搭建及其原理

    淺談Redis分片集群搭建及其原理

    本文主要介紹了Redis分片集群搭建及其原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • 使用Grafana監(jiān)控Redis的操作方法

    使用Grafana監(jiān)控Redis的操作方法

    這篇文章主要介紹了使用Grafana監(jiān)控Redis,號稱下一代可視化監(jiān)控系統(tǒng),結(jié)合SpringBoot使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-04-04
  • Redis做預(yù)定庫存緩存功能設(shè)計使用

    Redis做預(yù)定庫存緩存功能設(shè)計使用

    這篇文章主要為大家介紹了Redis做預(yù)定庫存緩存功能設(shè)計使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 一文詳細(xì)介紹Redis7持久化機制RDB和AOF

    一文詳細(xì)介紹Redis7持久化機制RDB和AOF

    這篇文章主要給大家分享一下Redis的數(shù)據(jù)持久化方式,Reids是一個高性能的緩存中間件,它的高性能是因為它是基于內(nèi)存的,我們知道直接操縱內(nèi)存是比較快的,所以當(dāng)機器發(fā)生宕機,那么數(shù)據(jù)就會完全丟失,所以本文詳細(xì)介紹Redis7持久化機制RDB和AOF
    2023-07-07
  • Redis集群的搭建圖文教程

    Redis集群的搭建圖文教程

    下面小編就為大家分享一篇Redis集群的搭建圖文教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-11-11
  • jwt+redis實現(xiàn)登錄認(rèn)證的示例代碼

    jwt+redis實現(xiàn)登錄認(rèn)證的示例代碼

    在登錄業(yè)務(wù)代碼中,當(dāng)用戶登錄成功時,生成一個登錄憑證存儲到redis中,本文主要介紹了jwt+redis實現(xiàn)登錄認(rèn)證的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • 提高redis緩存命中率的方法

    提高redis緩存命中率的方法

    在本篇文章里小編給大家整理了關(guān)于怎么提高redis緩存命中率的相關(guān)知識點內(nèi)容,有興趣的朋友們跟著學(xué)習(xí)下。
    2019-06-06
  • python中使用redis用法詳解

    python中使用redis用法詳解

    Redis擁有豐富的數(shù)據(jù)結(jié)構(gòu),擁有事務(wù)功能,保證命令的原子性。由于是內(nèi)存數(shù)據(jù)庫,讀寫非常高速,可達(dá)10w/s的評率,所以一般應(yīng)用于數(shù)據(jù)變化快、實時通訊、緩存等。這篇文章給大家講解一下Python如何使用Redis,并進(jìn)行相關(guān)的實戰(zhàn)操作。
    2022-12-12
  • 淺析PHP分布式中Redis實現(xiàn)Session的方法

    淺析PHP分布式中Redis實現(xiàn)Session的方法

    這篇文章主要介紹了PHP分布式中Redis實現(xiàn)Session的方法,文中詳細(xì)介紹了兩種方法的使用方法,并給出了測試的示例代碼,有需要的朋友可以參考借鑒,下面來一起看看吧,
    2016-12-12

最新評論

塘沽区| 民权县| 江门市| 宣化县| 泰来县| 金秀| 含山县| 临洮县| 利津县| 兴义市| 通山县| 高邮市| 勃利县| 汉寿县| 安陆市| 辉县市| 阆中市| 平顶山市| 伽师县| 化德县| 礼泉县| 醴陵市| 雅江县| 驻马店市| 曲阜市| 东源县| 冕宁县| 义马市| 龙门县| 讷河市| 昌都县| 从江县| 北安市| 图木舒克市| 武定县| 绩溪县| 贡嘎县| 宁化县| 岳池县| 茌平县| 广宁县|