前言
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)該有哪些特征
-
「互斥性」: 任意時(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è)方案中,setnx
和expire
兩個(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ì)解除他人加的鎖。
為了更嚴(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í)行步驟
-
根據(jù)負(fù)載均衡策略選擇一個(gè)redis主節(jié)點(diǎn)。
-
執(zhí)行l(wèi)ua腳本加鎖,加鎖成功返回null,否則返回當(dāng)前鎖的剩余過(guò)期時(shí)間。
-
watchlog機(jī)制,定時(shí)線(xiàn)程給當(dāng)前鎖每隔10s執(zhí)行一次續(xù)命。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-607404.html
-
獲取鎖成功 則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)!