欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

問題場(chǎng)景

場(chǎng)景一: 沒有捕獲異常

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

**問題 **

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

場(chǎng)景二: 線程間交互刪除鎖

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

問題

相對(duì)于場(chǎng)景一多了鎖的過期時(shí)間

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

// 加鎖,且(給每個(gè)線程)設(shè)置鎖過期時(shí)間, 刪除鎖時(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)) {
    // 極端場(chǎng)景下:(執(zhí)行時(shí)間定格在9.99秒)突然卡頓 10ms or redis服務(wù)宕機(jī)!?。?
    // 此時(shí)剛好鎖過期,自動(dòng)刪除
    // 其他線程獲取鎖,然后會(huì)把上個(gè)線程的鎖刪除,又會(huì)出現(xiàn)bug
	stringRedisTemplate.delete(key);
}

問題

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

  • 自帶鎖續(xù)命功能,默認(rèn)30s過期時(shí)間,可以自行調(diào)整過期時(shí)間
  • LUA腳本模擬商品減庫存
//模擬一個(gè)商品減庫存的原子操作
//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) " +
                // 模擬語法報(bào)錯(cuò)回滾操作
                "   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實(shí)現(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分布式鎖實(shí)現(xiàn)中,就有用到LUA腳本實(shí)現(xiàn)創(chuàng)建/獲取鎖的操作,而Redis的事務(wù)機(jī)制(multi/exec)非常雞肋,可以對(duì)相同的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)入自旋代碼塊的線程(這樣做的目的是為了不讓其他線程因?yàn)椴煌5淖孕o服務(wù)器造成壓力,所以讓其他線程先阻塞一段時(shí)間,等阻塞時(shí)間結(jié)束,再次自旋)
  • 獲取鎖的線程解鎖后,使用Redis的發(fā)布功能進(jìn)行發(fā)布消息,訂閱消息的線程調(diào)用release方法釋放阻塞的線程,再次嘗試獲取鎖
  • 如果是調(diào)用Redisson的tryAcquire(1000,TimeUnit.SECONDS)方法,那么未獲取到鎖的線程不用進(jìn)行自旋,因?yàn)闀r(shí)間一到,未獲取到鎖的線程就會(huì)自動(dòng)往下走進(jìn)入業(yè)務(wù)代碼塊

Redisson分布式鎖流程.png

總結(jié)

Redis分布式鎖自己去實(shí)現(xiàn)可能會(huì)出現(xiàn)幾個(gè)問題

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

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

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

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

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

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

相關(guān)文章

  • Redis配置文件代碼講解

    Redis配置文件代碼講解

    在本篇文章里小編給大家整理的是一篇關(guān)于Redis配置文件的說明內(nèi)容,需要的朋友們可以學(xué)習(xí)下。
    2020-03-03
  • redis中事務(wù)機(jī)制及樂觀鎖的實(shí)現(xiàn)

    redis中事務(wù)機(jī)制及樂觀鎖的實(shí)現(xiàn)

    這篇文章主要介紹了redis中事務(wù)機(jī)制及樂觀鎖的相關(guān)內(nèi)容,通過事務(wù)的執(zhí)行分析Redis樂觀鎖,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-10-10
  • Ubuntu系統(tǒng)中Redis的安裝步驟及服務(wù)配置詳解

    Ubuntu系統(tǒng)中Redis的安裝步驟及服務(wù)配置詳解

    本文主要記錄了Ubuntu服務(wù)器中Redis服務(wù)的安裝使用,包括apt安裝和解壓縮編譯安裝兩種方式,并對(duì)安裝過程中可能出現(xiàn)的問題、解決方案進(jìn)行說明,以及在手動(dòng)安裝時(shí),服務(wù)器如何添加自定義服務(wù)的問題,需要的朋友可以參考下
    2024-12-12
  • Redis設(shè)置密碼的實(shí)現(xiàn)步驟

    Redis設(shè)置密碼的實(shí)現(xiàn)步驟

    本文主要介紹了Redis設(shè)置密碼的實(shí)現(xiàn)步驟,主要包括兩種方法:臨時(shí)密碼和持久密碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • redis分布式鎖優(yōu)化的實(shí)現(xiàn)

    redis分布式鎖優(yōu)化的實(shí)現(xiàn)

    本文主要介紹了redis分布式鎖優(yōu)化的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • RedisDesktopManager無法遠(yuǎn)程連接Redis的完美解決方法

    RedisDesktopManager無法遠(yuǎn)程連接Redis的完美解決方法

    下載RedisDesktopManager客戶端,輸入服務(wù)器IP地址,端口(缺省值:6379);點(diǎn)擊Test Connection按鈕測(cè)試連接,連接失敗,怎么回事呢?下面小編給大家?guī)砹薘edisDesktopManager無法遠(yuǎn)程連接Redis的完美解決方法,一起看看吧
    2018-03-03
  • Linux、Windows下Redis的安裝即Redis的基本使用詳解

    Linux、Windows下Redis的安裝即Redis的基本使用詳解

    Redis是一個(gè)基于內(nèi)存的key-value結(jié)構(gòu)數(shù)據(jù)庫,Redis 是互聯(lián)網(wǎng)技術(shù)領(lǐng)域使用最為廣泛的存儲(chǔ)中間件,這篇文章主要介紹了Linux、Windows下Redis的安裝即Redis的基本使用詳解,需要的朋友可以參考下
    2022-09-09
  • redis的hash類型操作方法

    redis的hash類型操作方法

    Hash 是一個(gè) String 類型的 field(字段) 和 value(值) 的映射表,hash 特別適合用于存儲(chǔ)對(duì)象,這篇文章主要介紹了redis的hash類型的詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-06-06
  • Redis list 類型學(xué)習(xí)筆記與總結(jié)

    Redis list 類型學(xué)習(xí)筆記與總結(jié)

    這篇文章主要介紹了Redis list 類型學(xué)習(xí)筆記與總結(jié),本文著重講解了關(guān)于List的一些常用方法,比如lpush 方法、lrange 方法、rpush 方法、linsert 方法、 lset 方法等,需要的朋友可以參考下
    2015-06-06
  • 解決Redis分布式鎖的誤刪問題和原子性問題

    解決Redis分布式鎖的誤刪問題和原子性問題

    Redis的分布式鎖是通過利用Redis的原子操作和特性來實(shí)現(xiàn)的,為了保證數(shù)據(jù)的一致性和避免沖突,可以使用分布式鎖來進(jìn)行同步控制,本文給大家介紹了如何解決Redis分布式鎖的誤刪問題和原子性問題,需要的朋友可以參考下
    2024-02-02

最新評(píng)論