国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

redis分布式鎖的9種實(shí)現(xiàn)方式

這篇具有很好參考價(jià)值的文章主要介紹了redis分布式鎖的9種實(shí)現(xiàn)方式。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

前言

1.為什么要用分布式鎖

如果是單機(jī)情況下(單JVM),線(xiàn)程之間共享內(nèi)存,只要使用線(xiàn)程鎖就可以解決并發(fā)問(wèn)題。但如果是分布式情況下(多JVM),線(xiàn)程A和線(xiàn)程B很可能不是在同一JVM中,這樣線(xiàn)程鎖就無(wú)法起到作用了,這時(shí)候就要用到分布式鎖來(lái)解決。分布式鎖其實(shí)就是,控制分布式系統(tǒng)不同進(jìn)程共同訪(fǎng)問(wèn)共享資源的一種鎖的實(shí)現(xiàn)。如果不同的系統(tǒng)或同一個(gè)系統(tǒng)的不同主機(jī)之間共享了某個(gè)臨界資源,往往需要互斥來(lái)防止彼此干擾,以保證一致性。

2.分布式鎖有哪些實(shí)現(xiàn)方式

  • 基于緩存(Redis等)實(shí)現(xiàn)分布式鎖;

  • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖; 例如唯一性約束排它鎖,version 樂(lè)觀鎖

  • 基于Zookeeper實(shí)現(xiàn)分布式鎖;

    從性能角度(從高到低)

    緩存 > Zookeeper >= 數(shù)據(jù)庫(kù)

3.使用redis作為分布鎖的好處

  • Redis為單進(jìn)程單線(xiàn)程模式,采用隊(duì)列模式將并發(fā)訪(fǎng)問(wèn)變成串行訪(fǎng)問(wèn),且多客戶(hù)端對(duì)Redis的連接并不存在競(jìng)爭(zhēng)關(guān)系

  • 純內(nèi)存操作,單線(xiàn)程操作,避免了頻繁的上下文切換 非堵塞 I/O 多路復(fù)用 等特點(diǎn)使得 速度快

4.我們先來(lái)看下,一把靠譜的分布式鎖應(yīng)該有哪些特征

redis分布式鎖如何實(shí)現(xiàn),面試,java,jvm,redis

  • 「互斥性」: 任意時(shí)刻,只有一個(gè)客戶(hù)端能持有鎖。

  • 「鎖超時(shí)釋放」:持有鎖超時(shí),可以釋放,防止不必要的資源浪費(fèi),也可以防止死鎖。

  • 「可重入性」:一個(gè)線(xiàn)程如果獲取了鎖之后,可以再次對(duì)其請(qǐng)求加鎖,防止死鎖。

  • 「高性能和高可用」:加鎖和解鎖需要開(kāi)銷(xiāo)盡可能低,同時(shí)也要保證高可用,避免分布式鎖失效。

  • 「安全性」:鎖只能被持有的客戶(hù)端刪除,不能被其他客戶(hù)端刪除

Redis分布式鎖方案

Redis分布式鎖方案一:SETNX + EXPIRE

提到Redis的分布式鎖,很多小伙伴馬上就會(huì)想到setnx+ expire命令。即先用setnx來(lái)?yè)屾i,如果搶到之后,再用expire給鎖設(shè)置一個(gè)過(guò)期時(shí)間,防止鎖忘記了釋放。

SETNX 是SET IF NOT EXISTS的簡(jiǎn)寫(xiě).日常命令格式是SETNX key value,如果 key不存在,則SETNX成功返回1,如果這個(gè)key已經(jīng)存在了,則返回0。

假設(shè)某電商網(wǎng)站的某商品做秒殺活動(dòng),key可以設(shè)置為key_resource_id,value設(shè)置任意值,偽代碼如下:

if(jedis.setnx(key_resource_id,lock_value)==1)
 ?  { //加鎖
 ? ? ? ?expire(key_resource_id,100); //設(shè)置過(guò)期時(shí)間
 ? ? ? ?try {
 ? ? ? ? ? ?do something ?//業(yè)務(wù)請(qǐng)求
 ? ? ?  } catch () {
 ? ? ?  } finally {
 ? ? ? ? ? ?jedis.del(key_resource_id); //釋放鎖
 ? ? ?  }
 ?  }

但是這個(gè)方案中,setnxexpire兩個(gè)命令分開(kāi)了,「不是原子操作」。如果執(zhí)行完setnx加鎖,正要執(zhí)行expire設(shè)置過(guò)期時(shí)間時(shí),進(jìn)程crash或者要重啟維護(hù)了,那么這個(gè)鎖就“長(zhǎng)生不老”了,「別的線(xiàn)程永遠(yuǎn)獲取不到鎖啦」。

Redis分布式鎖方案二:SETNX + value值是(系統(tǒng)時(shí)間+過(guò)期時(shí)間)

為了解決方案一,「發(fā)生異常鎖得不到釋放的場(chǎng)景」,有小伙伴認(rèn)為,可以把過(guò)期時(shí)間放到setnx的value值里面。如果加鎖失敗,再拿出value值校驗(yàn)一下即可。加鎖代碼如下:

long expires = System.currentTimeMillis() + expireTime; //系統(tǒng)時(shí)間+設(shè)置的過(guò)期時(shí)間
String expiresStr = String.valueOf(expires);
 
// 如果當(dāng)前鎖不存在,返回加鎖成功
if (jedis.setnx(key_resource_id, expiresStr) == 1) {
 ? ? ? ?return true;
} 
// 如果鎖已經(jīng)存在,獲取鎖的過(guò)期時(shí)間
String currentValueStr = jedis.get(key_resource_id);
 
// 如果獲取到的過(guò)期時(shí)間,小于系統(tǒng)當(dāng)前時(shí)間,表示已經(jīng)過(guò)期
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
 
 ? ? // 鎖已過(guò)期,獲取上一個(gè)鎖的過(guò)期時(shí)間,并設(shè)置現(xiàn)在鎖的過(guò)期時(shí)間
 ? ?String oldValueStr = jedis.getSet(key_resource_id, expiresStr);
 ? ?
 ? ?if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
 ? ? ? ? // 考慮多線(xiàn)程并發(fā)的情況,只有一個(gè)線(xiàn)程的設(shè)置值和當(dāng)前值相同,它才可以加鎖
 ? ? ? ? return true;
 ?  }
}
 ? ? ? ?
//其他情況,均返回加鎖失敗
return false;
}

這個(gè)方案的優(yōu)點(diǎn)是,巧妙移除expire單獨(dú)設(shè)置過(guò)期時(shí)間的操作,把「過(guò)期時(shí)間放到setnx的value值」里面來(lái)。解決了方案一發(fā)生異常,鎖得不到釋放的問(wèn)題。但是這個(gè)方案還有別的缺點(diǎn):

  • 過(guò)期時(shí)間是客戶(hù)端自己生成的(System.currentTimeMillis()是當(dāng)前系統(tǒng)的時(shí)間),必須要求分布式環(huán)境下,每個(gè)客戶(hù)端的時(shí)間必須同步。

  • 如果鎖過(guò)期的時(shí)候,并發(fā)多個(gè)客戶(hù)端同時(shí)請(qǐng)求過(guò)來(lái),都執(zhí)行jedis.getSet(),最終只能有一個(gè)客戶(hù)端加鎖成功,但是該客戶(hù)端鎖的過(guò)期時(shí)間,可能被別的客戶(hù)端覆蓋

  • 該鎖沒(méi)有保存持有者的唯一標(biāo)識(shí),可能被別的客戶(hù)端釋放/解鎖。

Redis分布式鎖方案三:使用Lua腳本(包含SETNX + EXPIRE兩條指令)

實(shí)際上,我們還可以使用Lua腳本來(lái)保證原子性(包含setnx和expire兩條指令),lua腳本如下:

if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
 ? redis.call('expire',KEYS[1],ARGV[2])
else
 ? return 0
end;

加鎖代碼如下:

 String lua_scripts = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
 ? ? ? ? ? ?" redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; ? 
Object result = jedis.eval(lua_scripts, Collections.singletonList(key_resource_id), Collections.singletonList(values));
//判斷是否成功
return result.equals(1L);

Redis支持LUA腳本的主要優(yōu)勢(shì)

高效性:減少網(wǎng)絡(luò)開(kāi)銷(xiāo)及時(shí)延,多次redis服務(wù)器網(wǎng)絡(luò)請(qǐng)求的操作,使用LUA腳本可以用一個(gè)請(qǐng)求完成

數(shù)據(jù)可靠性:Redis會(huì)將整個(gè)腳本作為一個(gè)整體執(zhí)行,中間不會(huì)被其他命令插入。

原子操作:將腳本作為一個(gè)整體執(zhí)行,中間不會(huì)插入其他命令,無(wú)需使用事務(wù)

可嵌入性:可嵌入JAVA,C#等多種編程語(yǔ)言,支持不同操作系統(tǒng)跨平臺(tái)交互

復(fù)用:客戶(hù)端發(fā)送的腳本永久存在redis中,其他客戶(hù)端可以復(fù)用腳本

Redis分布式鎖方案方案四:SET的擴(kuò)展命令(SET EX PX NX)

除了使用,使用Lua腳本,保證SETNX + EXPIRE兩條指令的原子性,我們還可以巧用Redis的SET指令擴(kuò)展參數(shù)?。?code>SET key value[EX seconds][PX milliseconds][NX|XX]),它也是原子性的!

SET key valueEX seconds[NX|XX]

  • NX :表示key不存在的時(shí)候,才能set成功,也即保證只有第一個(gè)客戶(hù)端請(qǐng)求才能獲得鎖,而其他客戶(hù)端請(qǐng)求只能等其釋放鎖,才能獲取。

  • EX seconds :設(shè)定key的過(guò)期時(shí)間,時(shí)間單位是秒。

  • PX milliseconds: 設(shè)定key的過(guò)期時(shí)間,單位為毫秒

  • XX: 僅當(dāng)key存在時(shí)設(shè)置值

偽代碼demo如下:

但是呢,

 ? ?if(jedis.set(key_resource_id, lock_value, "NX", "EX", 100s) == 1){ //加鎖
 ? ? ? ?try {
 ? ? ? ? ? ?do something ?//業(yè)務(wù)處理
 ? ? ?  }catch(){
 ? ?  }
 ? ? ?finally {
 ? ? ? ? ? jedis.del(key_resource_id); //釋放鎖
 ? ? ?  }
 ?  }

這個(gè)方案還是可能存在問(wèn)題:

  • 問(wèn)題一:「鎖過(guò)期釋放了,業(yè)務(wù)還沒(méi)執(zhí)行完」。假設(shè)線(xiàn)程a獲取鎖成功,一直在執(zhí)行臨界區(qū)的代碼。但是100s過(guò)去后,它還沒(méi)執(zhí)行完。但是,這時(shí)候鎖已經(jīng)過(guò)期了,此時(shí)線(xiàn)程b又請(qǐng)求過(guò)來(lái)。顯然線(xiàn)程b就可以獲得鎖成功,也開(kāi)始執(zhí)行臨界區(qū)的代碼。那么問(wèn)題就來(lái)了,臨界區(qū)的業(yè)務(wù)代碼都不是嚴(yán)格串行執(zhí)行的啦。

  • 問(wèn)題二:「鎖被別的線(xiàn)程誤刪」。假設(shè)線(xiàn)程a執(zhí)行完后,去釋放鎖。但是它不知道當(dāng)前的鎖可能是線(xiàn)程b持有的(線(xiàn)程a去釋放鎖時(shí),有可能過(guò)期時(shí)間已經(jīng)到了,此時(shí)線(xiàn)程b進(jìn)來(lái)占有了鎖)。那線(xiàn)程a就把線(xiàn)程b的鎖釋放掉了,但是線(xiàn)程b臨界區(qū)業(yè)務(wù)代碼可能都還沒(méi)執(zhí)行完呢。

方案五:SET EX PX NX + 校驗(yàn)唯一隨機(jī)值,再刪除

既然鎖可能被別的線(xiàn)程誤刪,那我們給value值設(shè)置一個(gè)標(biāo)記當(dāng)前線(xiàn)程唯一的隨機(jī)數(shù),在刪除的時(shí)候,校驗(yàn)一下,不就OK了嘛。偽代碼如下:

uni_request_id = uuid;
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖
 ? ?try {
 ? ? ? ?do something ?//業(yè)務(wù)處理
 ?  }catch(){
  }
 ?finally {
 ? ? ? //判斷是不是當(dāng)前線(xiàn)程加的鎖,是才釋放
 ? ? ? if (uni_request_id.equals(jedis.get(key_resource_id))) {
 ? ? ? ?jedis.del(lockKey); //釋放鎖
 ? ? ?  }
 ?  }
}

在這里,「判斷是不是當(dāng)前線(xiàn)程加的鎖」「釋放鎖」不是一個(gè)原子操作。如果調(diào)用jedis.del()釋放鎖的時(shí)候,可能這把鎖已經(jīng)不屬于當(dāng)前客戶(hù)端,會(huì)解除他人加的鎖。

redis分布式鎖如何實(shí)現(xiàn),面試,java,jvm,redis

為了更嚴(yán)謹(jǐn),一般也是用lua腳本代替。lua腳本如下:

if redis.call('get',KEYS[1]) == ARGV[1] then 
 ? return redis.call('del',KEYS[1]) 
else
 ? return 0
end;

方案六:解決可重入性問(wèn)題可采用計(jì)數(shù)器的方式解決。

解決可重入性的問(wèn)題

可重入問(wèn)題舉例

看下面的demo

public class Demo1 {

public synchronized void functionA(){

System.out.println("iAmFunctionA");

functionB();

}

public synchronized void functionB(){

System.out.println("iAmFunctionB");

}

}

代碼解釋

functionA()和functionB()都是同步方法,當(dāng)線(xiàn)程進(jìn)入funcitonA()會(huì)獲得該類(lèi)的對(duì)象鎖,這個(gè)鎖"new Demo1()",在functionA()對(duì)方法functionB()做了調(diào)用,但是functionB()也是同步的,因此該線(xiàn)程需要再次獲得該對(duì)象鎖(new Demo1())。其他線(xiàn)程是無(wú)法獲該對(duì)象鎖的。

這就是可重入鎖。

可重入鎖的作用就是為了避免死鎖,java中synchronized和ReentrantLock都是可重入鎖

@Service
public class RedisLockImpl implements RedisLock{
 ? ?
 ? ?public static ThreadLocal<CountDTO> threadLocal = new ThreadLocal<>();
 ? ?/**
 ? ?*上鎖
 ? ?*/
 ? ?@Override
 ? ?public boolean tryLock(String key, String value, Long time) {
 ? ? ? ?CountDTO countDTO = threadLocal.get();
 ? ? ? ?Boolean lock
 ? ? ? ?if (countDTO == null){
 ? ? ? ? ? ?String clientId = UUID.randomUUID().toString();
 ? ? ? ? ? ?countDTO.setClientId(clientId);
 ? ? ? ? ? ?countDTO.setCount(new AtomicInteger(1));
 ? ? ? ? ? ?lock = (jedis.set(key, countDTO.getClientId(), "NX", "EX", time) == 1);
 ? ? ?  } else {
 ? ? ? ? ? ?// ++i
 ? ? ? ? ? 
 ? ? ? ? ? ?countDTO.setCount(new AtomicInteger(countDTO.getCount().incrementAndGet()));
 ? ? ? ? ? ?return true;
 ? ? ?  }
?
 ? ? ? 
 ? ? ? ?return lock;
 ? ? ?  }
 ?  }
 ? ?/**
 ? ?*釋放鎖
 ? ?*/
 ? ?@Override
 ? ?public void releaseLock(String key) {
 ? ? ? ?try {
 ? ? ? ? ? ?if (threadLocal.get() != null 
 ? ? ? ? ? ? ? ? ? ?&& threadLocal.get().getClientId().equals(jedis.get(key))){
 ? ? ? ? ? ? ? ?if (threadLocal.get().getCount().get() == 0){
 ? ? ? ? ? ? ? ? ? ? jedis.evalsha(unlockLua, Arrays.asList(key), Arrays.asList(threadLocal.get().getClientId()));
 ? ? ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ? ? ?CountDTO countDTO = threadLocal.get();
 ? ? ? ? ? ? ? ? ? ?countDTO.setCount(new AtomicInteger(countDTO.getCount().decrementAndGet()));
 ? ? ? ? ? ? ? ? ? ?threadLocal.set(countDTO);
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  } finally {
 ? ? ? ? ? ?if (threadLocal.get() != null){
 ? ? ? ? ? ? ? ?if (threadLocal.get().getCount().get() == 0){
 ? ? ? ? ? ? ? ? ? ?threadLocal.remove();
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }
 private ?final String unlockLua = jedis.scriptLoad("local lock_key=KEYS[1]\n" +
 ? ? ? ? ? ?"local lock_value=ARGV[1]\n" +
 ? ? ? ? ? ?"\n" +
 ? ? ? ? ? ?"local current_value=redis.call('get',lock_key)\n" +
 ? ? ? ? ? ?"local result=0\n" +
 ? ? ? ? ? ?"if lock_value==current_value then\n" +
 ? ? ? ? ? ?" ?  redis.call('del',lock_key)\n" +
 ? ? ? ? ? ?" ?  result=1\n" +
 ? ? ? ? ? ?"end\n" +
 ? ? ? ? ? ?" ?  return result");
}

方案七:增加自旋,變成自旋鎖

減少未獲取到鎖的失敗次數(shù)

@Service
public class RedisLockImpl implements RedisLock{

    public static ThreadLocal<CountDTO> threadLocal = new ThreadLocal<>();
    /**
    *上鎖
    * timeout 等待最大超時(shí)時(shí)間
    */
    @Override
    public boolean tryLock(String key, String value, Long time, Long timeout) {
        CountDTO countDTO = threadLocal.get();
        if (countDTO == null){
            String clientId = UUID.randomUUID().toString();
            countDTO.setClientId(clientId);
            countDTO.setCount(new AtomicInteger(1));
            long tryTime = System.currentTimeMillis() + timeout * 1000L;
        } else {
            countDTO.setCount(new AtomicInteger(countDTO.getCount().incrementAndGet()));
            return true;
        }
         while (System.currentTimeMillis() < tryTime) {
            if (jedis.set(key, countDTO.getClientId(), "NX", "EX", time) == 1) {
                logger.info("get lock success ,key=" + key + ", expire seconds=" + time);
                return true;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    @Override
    public void releaseLock(String key) {
        try {
            if (threadLocal.get() != null 
                    && threadLocal.get().getClientId().equals(jedis.get(key))){
                if (threadLocal.get().getCount().get() == 0){
                    jedis.evalsha(unlockLua, Arrays.asList(key), Arrays.asList(threadLocal.get().getClientId()));
                } else {
                    CountDTO countDTO = threadLocal.get();
                    countDTO.setCount(new AtomicInteger(countDTO.getCount().decrementAndGet()));
                    threadLocal.set(countDTO);
                }
            }
        } finally {
            if (threadLocal.get() != null){
                if (threadLocal.get().getCount().get() == 0){
                    threadLocal.remove();
                }
            }
        }
    }
    private  final String unlockLua = jedis.scriptLoad("local lock_key=KEYS[1]\n" +
            "local lock_value=ARGV[1]\n" +
            "\n" +
            "local current_value=redis.call('get',lock_key)\n" +
            "local result=0\n" +
            "if lock_value==current_value then\n" +
            "    redis.call('del',lock_key)\n" +
            "    result=1\n" +
            "end\n" +
            "    return result");

}

方案八:增加看門(mén)狗

如果key超時(shí)了也會(huì)使得鎖在執(zhí)行過(guò)程中失效,那么怎么解決呢,watchDog看門(mén)狗機(jī)制其實(shí)就是應(yīng)對(duì)這種情況,實(shí)現(xiàn)鎖的一個(gè)續(xù)命處理

@Service
public class RedisLockImpl implements RedisLock {

    public static ThreadLocal<CountDTO> threadLocal = new ThreadLocal<>();
    //定期執(zhí)行任務(wù) 
    private ScheduledThreadPoolExecutor scheduledExecutorService = new ScheduledThreadPoolExecutor(1);

    @Override
    public boolean tryLock(String key, String value, Long time, Long timeout) {
        CountDTO countDTO = threadLocal.get();
        if (countDTO == null) {
            String clientId = UUID.randomUUID().toString();
            countDTO.setClientId(clientId);
            countDTO.setCount(new AtomicInteger(1));
        } else {
            countDTO.setCount(new AtomicInteger(countDTO.getCount().incrementAndGet()));
             return true;
        }
        long tryTime = System.currentTimeMillis() + timeout * 1000L;
        while (System.currentTimeMillis() < tryTime) {
            if (jedis.set(key, countDTO.getClientId(), "NX", "EX", time) == 1) {
                logger.info("get lock success ,key=" + key + ", expire seconds=" + time);
                this.watchDog(key, countDTO.getClientId(), time);
                return true;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    @Override
    public void releaseLock(String key) {
        try {
            if (threadLocal.get() != null
                    && threadLocal.get().getClientId().equals(jedis.get(key))) {
                if (threadLocal.get().getCount().get() == 0) {
                    //shutdown方法:平滑的關(guān)閉ExecutorService,當(dāng)此方法被調(diào)用時(shí),ExecutorService停止接收新的任務(wù)并且等待已經(jīng)提交的任務(wù)(包含提交正在執(zhí)行和提交未執(zhí)行)執(zhí)行完成。當(dāng)所有提交任務(wù)執(zhí)行完畢,線(xiàn)程池即被關(guān)閉。
                    scheduledExecutorService.shutdown();
                    jedis.evalsha(unlockLua, Arrays.asList(key), Arrays.asList(threadLocal.get().getClientId()));
                } else {
                    CountDTO countDTO = threadLocal.get();
                    countDTO.setCount(new AtomicInteger(countDTO.getCount().decrementAndGet()));
                    threadLocal.set(countDTO);
                }
            }
        } finally {
            if (threadLocal.get() != null) {
                if (threadLocal.get().getCount().get() == 0) {
                    threadLocal.remove();
                }
            }
        }
    }

    /**
     * 看門(mén)狗執(zhí)行邏輯
     */
    private void watchDog(String key, String value, long ttl) {
        //獲取續(xù)命速率
        long rate = getRate(ttl);
        if (scheduledExecutorService.isShutdown()) {
            scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
        }
        //周期性執(zhí)行,根據(jù)rate進(jìn)行執(zhí)行
        scheduledExecutorService.scheduleAtFixedRate(new watchDogThread(scheduledExecutorService, Arrays.asList(key), Arrays.asList(value, String.valueOf(ttl))),
                1, rate, TimeUnit.SECONDS);
    }

    private long getRate(long ttl) {
        if (ttl - 5 > 0) {
            return ttl - 5;
        } else if (ttl - 1 > 0) {
            return ttl - 1;
        }
        throw new RuntimeException("ttl 不允許小于1");
    }

    class watchDogThread implements Runnable {
        private ScheduledThreadPoolExecutor poolExecutor;
        private List<String> keys;
        private List<String> args;

        public watchDogThread(ScheduledThreadPoolExecutor poolExecutor, List<String> keys, List<String> args) {
            this.poolExecutor = poolExecutor;
            this.keys = keys;
            this.args = args;
        }

        @Override
        public void run() {
            logger.info("進(jìn)行續(xù)期");
            if ((long) jedis.evalsha(watchLua, keys, args) == 0) {
                //續(xù)期失敗 可能是業(yè)務(wù)系統(tǒng)發(fā)生異常并且沒(méi)有進(jìn)行異常捕捉,沒(méi)有進(jìn)行釋放鎖操作
                poolExecutor.shutdown();
            }
        }
    }

    private final String watchLua = jedis.scriptLoad("local lock_key=KEYS[1]\n" +
            "local lock_value=ARGV[1]\n" +
            "local lock_ttl=ARGV[2]\n" +
            "local current_value=redis.call('get',lock_key)\n" +
            "local result=0;\n" +
            "if lock_value==current_value then\n" +
            "    result=1;\n" +
            "    redis.call('expire',lock_key,lock_ttl)\n" +
            "end\n" +
            "return result");

    private final String unlockLua = jedis.scriptLoad("local lock_key=KEYS[1]\n" +
            "local lock_value=ARGV[1]\n" +
            "\n" +
            "local current_value=redis.call('get',lock_key)\n" +
            "local result=0\n" +
            "if lock_value==current_value then\n" +
            "    redis.call('del',lock_key)\n" +
            "    result=1\n" +
            "end\n" +
            "    return result");

}

方案九:Redisson框架

Redisson是架設(shè)在Redis基礎(chǔ)上的一個(gè)Java駐內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory Data Grid)。 Redisson在基于NIO的Netty框架上,生產(chǎn)環(huán)境使用分布式鎖。

public String discount() throws IOException{
    String key = "lock001";
    //加鎖
    DistributedRedisLock.acquire(key);
    //執(zhí)行具體業(yè)務(wù)邏輯
    dosoming
    //釋放鎖
    DistributedRedisLock.release(key);
    //返回結(jié)果
    return soming;
 }

執(zhí)行步驟

  1. 根據(jù)負(fù)載均衡策略選擇一個(gè)redis主節(jié)點(diǎn)。

  2. 執(zhí)行l(wèi)ua腳本加鎖,加鎖成功返回null,否則返回當(dāng)前鎖的剩余過(guò)期時(shí)間。

  3. watchlog機(jī)制,定時(shí)線(xiàn)程給當(dāng)前鎖每隔10s執(zhí)行一次續(xù)命。

  4. 獲取鎖成功 則return,失敗則自旋獲取鎖。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-607404.html

到了這里,關(guān)于redis分布式鎖的9種實(shí)現(xiàn)方式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入理解PHP+Redis實(shí)現(xiàn)分布式鎖的相關(guān)問(wèn)題

    PHP使用分布式鎖,受語(yǔ)言本身的限制,有一些局限性。 通俗理解單機(jī)鎖問(wèn)題:自家的鎖鎖自家的門(mén),只能保證自家的事,管不了別人家不鎖門(mén)引發(fā)的問(wèn)題,于是有了分布式鎖。 分布式鎖概念:是針對(duì)多個(gè)節(jié)點(diǎn)的鎖。避免出現(xiàn)數(shù)據(jù)不一致或者并發(fā)沖突的問(wèn)題,讓每個(gè)節(jié)點(diǎn)確保

    2024年03月23日
    瀏覽(28)
  • Redis實(shí)戰(zhàn)案例14-分布式鎖的基本原理、不同實(shí)現(xiàn)方法對(duì)比以及基于Redis進(jìn)行實(shí)現(xiàn)思路

    Redis實(shí)戰(zhàn)案例14-分布式鎖的基本原理、不同實(shí)現(xiàn)方法對(duì)比以及基于Redis進(jìn)行實(shí)現(xiàn)思路

    基于數(shù)據(jù)庫(kù)的分布式鎖:這種方式使用數(shù)據(jù)庫(kù)的特性來(lái)實(shí)現(xiàn)分布式鎖。具體流程如下: 獲取鎖:當(dāng)一個(gè)節(jié)點(diǎn)需要獲得鎖時(shí),它嘗試在數(shù)據(jù)庫(kù)中插入一個(gè)特定的唯一鍵值(如唯一約束的主鍵),如果插入成功,則表示獲得了鎖。 釋放鎖:當(dāng)節(jié)點(diǎn)完成任務(wù)后,通過(guò)刪除該唯一鍵

    2024年02月13日
    瀏覽(49)
  • Zookeeper 和 Redis 哪種更好? 為什么使用分布式鎖? 1. 利用 Redis 提供的 第二種,基于 ZK 實(shí)現(xiàn)分布式鎖的落地方案 對(duì)于 redis 的分布式鎖而言,它有以下缺點(diǎn):

    關(guān)于這個(gè)問(wèn)題,我們 可以從 3 個(gè)方面來(lái)說(shuō): 為什么使用分布式鎖? 使用分布式鎖的目的,是為了保證同一時(shí)間只有一個(gè) JVM 進(jìn)程可以對(duì)共享資源進(jìn)行操作。 根據(jù)鎖的用途可以細(xì)分為以下兩類(lèi): 允許多個(gè)客戶(hù)端操作共享資源,我們稱(chēng)為共享鎖 這種鎖的一般是對(duì)共享資源具有

    2024年01月16日
    瀏覽(24)
  • 【Redis】Redis分布式鎖的10個(gè)坑

    【Redis】Redis分布式鎖的10個(gè)坑

    日常開(kāi)發(fā)中,經(jīng)常會(huì)碰到秒殺搶購(gòu)等業(yè)務(wù)。為了避免并發(fā)請(qǐng)求造成的庫(kù)存超賣(mài)等問(wèn)題,我們一般會(huì)用到Redis分布式鎖。但是使用Redis分布式鎖,很容易踩坑哦~ 本文將給大家分析闡述,Redis分布式鎖的10個(gè)坑~ 一說(shuō)到實(shí)現(xiàn)Redis的分布式鎖,很多小伙伴馬上就會(huì)想到setnx+ expire命令。

    2024年02月05日
    瀏覽(26)
  • 【征服redis15】分布式鎖的功能與整體設(shè)計(jì)方案

    【征服redis15】分布式鎖的功能與整體設(shè)計(jì)方案

    目錄 ?1. 分布式鎖的概念 2.基于數(shù)據(jù)庫(kù)做分布式鎖 2.1 基于表主鍵唯一做分布式鎖 2.2 基于表字段版本號(hào)做分布式鎖 2.3 基于數(shù)據(jù)庫(kù)排他鎖做分布式鎖 3.使用Redis做分布式鎖 3.1 redis實(shí)現(xiàn)分布式鎖的基本原理 3.2 問(wèn)題一:增加超時(shí)機(jī)制,防止長(zhǎng)期持有的情況 3.3 問(wèn)題2:重入的問(wèn)題

    2024年01月22日
    瀏覽(23)
  • Redis實(shí)現(xiàn)分布式鎖原理(面試重點(diǎn))

    Redis實(shí)現(xiàn)分布式鎖原理(面試重點(diǎn))

    一、為什么使用分布式鎖? 本地鎖的局限性( synchronized ): 本地鎖只能鎖住當(dāng)前服務(wù),只能保證自己的服務(wù),只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn),但是在服務(wù)眾多的分布式環(huán)境下,其實(shí)是有多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)的同一個(gè)數(shù)據(jù),這顯然是不符合要求的。 ·分布式鎖的概念: 分布式鎖指的是,

    2024年02月10日
    瀏覽(17)
  • Redis學(xué)習(xí)(八)Java三種方式實(shí)現(xiàn)分布式鎖

    在分布式服務(wù)中,經(jīng)常有例如 定時(shí)任務(wù) 這樣的場(chǎng)景。 在定時(shí)任務(wù)中,如果不使用 quartz 這樣的分布式定時(shí)工具,只是簡(jiǎn)單使用 @Schedule 注解來(lái)實(shí)現(xiàn)定時(shí)任務(wù), 在服務(wù)分布式部署中 ,就有可能存在 定時(shí)任務(wù)并發(fā)重復(fù)執(zhí)行問(wèn)題 。 對(duì)于解決以上場(chǎng)景中的問(wèn)題,我們引入了 分布式

    2024年02月12日
    瀏覽(27)
  • ZooKeeper分布式鎖的實(shí)現(xiàn)與應(yīng)用

    ZooKeeper是一種分布式應(yīng)用程序協(xié)調(diào)服務(wù),它可以管理大規(guī)模的集群,并提供可靠的、有序的、高效的數(shù)據(jù)通信。其中,ZooKeeper提供的分布式鎖是一種常見(jiàn)的分布式鎖實(shí)現(xiàn),本文將對(duì)其進(jìn)行詳細(xì)介紹。 在分布式系統(tǒng)中,多個(gè)進(jìn)程或節(jié)點(diǎn)可能需要同時(shí)訪(fǎng)問(wèn)共享資源。為了確保數(shù)據(jù)

    2024年02月02日
    瀏覽(21)
  • redis如何實(shí)現(xiàn)分布式鎖?

    首先,“分布式鎖”的概念,是相對(duì)“本地鎖”而言。 本地鎖比如java中的synchronized 這類(lèi) JDK 自帶的 本地鎖 ,來(lái)控制一個(gè) JVM 進(jìn)程內(nèi)的多個(gè)線(xiàn)程對(duì)本地共享資源的訪(fǎng)問(wèn)。 同一時(shí)刻只有一個(gè)線(xiàn)程可以獲取到本地鎖訪(fǎng)問(wèn)共享資源。 分布式系統(tǒng)下,不同的服務(wù)/客戶(hù)端通常運(yùn)

    2024年02月06日
    瀏覽(21)
  • 高并發(fā)緩存問(wèn)題分析以及分布式鎖的實(shí)現(xiàn)

    高并發(fā)緩存問(wèn)題分析以及分布式鎖的實(shí)現(xiàn)

    在高并發(fā)的環(huán)境下,比如淘寶,京東不定時(shí)的促銷(xiāo)活動(dòng),大量的用戶(hù)訪(fǎng)問(wèn)會(huì)導(dǎo)致數(shù)據(jù)庫(kù)的性能下降,進(jìn)而有可能數(shù)據(jù)庫(kù)宕機(jī)從而不能產(chǎn)生正常的服務(wù),一般一個(gè)系統(tǒng)最大的性能瓶頸,就是數(shù)據(jù)庫(kù)的io操作,如果發(fā)生大量的io那么他的問(wèn)題也會(huì)隨之而來(lái)。從數(shù)據(jù)庫(kù)入手也是調(diào)優(yōu)性?xún)r(jià)比最高

    2024年01月19日
    瀏覽(34)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包