標籤:tps 綁定 死結 組織 代碼 訪問共用 沒有 ali 特殊
前面學習了Redis的資料結構以及命令、Redis中的事務和Redis對Lua指令碼的支援。
這一章就對Redis這些特性做一下實戰性應用——基於Redis的分布式鎖實現。
Lock和Distributed Lock
在這之前先來認識下鎖(Lock)和分布式鎖(Distributed Lock):
In computer science, a lock or mutex (from mutual exclusion) is a synchronization mechanism for enforcing limits on access to a resource in an environment where there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy.
參考wiki的解釋:在電腦科學領域中,鎖是為了限制多線程環境訪問一個資源的一種同步機制。鎖被設計相互排斥的並發策略。
Lock的前提條件:
- 同一台機器上的共同資源;
- 多線程環境訪問共同資源;
Lock目標:
Lock的實現:
- 單核處理器上禁用中斷,使得同步資源能夠被訪問結束;
- 硬體支援的原子指令,“比較和交換”等等,用於測試鎖是否是空閑,如果空閑擷取鎖;
Operating systems use lock managers to organise and serialise the access to resources. A distributed lock manager (DLM) runs in every machine in a cluster, with an identical copy of a cluster-wide lock database. In this way a DLM provides software applications which are distributed across a cluster on multiple machines with a means to synchronize their accesses to shared resources.
參考wiki解釋分布式鎖:作業系統用鎖管理器實現有組織有順序的訪問資源。分布式鎖運行在叢集環境中的每台機器上,使得資料具有相同的副本。分布式鎖提供分布式軟體應用同步訪問共用資源。
Distribute Lock的前提條件:
Distribute Lock目標:
Distribute Lock實現方式:
- 基於Redis實現;
- 基於Zookeeper實現;
- 基於Etcd或者Consul實現;
- Google開發的Chubby(Lock Service);
獨佔式鎖的特點和影響
按照用途、情境劃分,鎖的類型非常多。如:排它鎖(獨佔式鎖)、共用鎖定,自旋鎖、互斥鎖,讀鎖、寫鎖。但是在分布式環境中的所謂的分布式鎖,大多數情況下都是指:分布式獨佔式鎖。
1.特點分析:
- 每次只能一個佔用鎖;
- 可以重複進入鎖;
- 只有佔用者才可以解鎖;
- 擷取鎖和釋放鎖都需要原子
- 不能產生死結
- 盡量滿足效能
本質:同步互斥,使得處理任務能夠一個一個逐步的過臨界資源。
造成的影響:
- 降低並發數,使得多任務處理,只能一個一個的進行;
- 任務的換進換出造成切換上的開銷;
本質:使得輸送量大打折扣。
基於Redis的實現Redis實現分布式鎖的基礎
1.Redis本身就是單線程:
- 單個命令執行具有原子性、無競態條件,這個特點符合一次只有一個用戶端爭用鎖;
2.Redis提供了set if not exists操作:
- 存在即不設定,這個特點符合鎖的獨佔性(排它特點);
下面來先來看下擷取鎖:
return jedis.set(lockKey, lockValue, NX, EX, expireTime) != null ? true : false;
這裡使用set指令,具有原子操作特點,不會被其他用戶端操作中斷,在分布式環境中,是安全的,沒有競態條件產生,一次只能有一個用戶端爭用鎖;使用nx,即存在不設定,符合獨佔特點;設定ex,有到期效果,不會產生永久獨佔即死結;最後設定了lockValue,這樣就和當前加鎖任務做了綁定,後面可以用其作為解鎖的鑰匙;
再來看下解鎖操作:
static final String RELEASE_LOCK_LUA = "if redis.call(‘get‘, KEYS[1]) == ARGV[1] " + "then return redis.call(‘del‘, KEYS[1]) else return 0 end"; Object result = jedis.eval(RELEASE_LOCK_LUA, 1, lockKey, lockValue);
這裡解鎖是用了Lua指令碼,上篇文章中介紹了Redis內建一個Lua解譯器,Redis調用解譯器執行Lua指令碼也是具有原子性的,即同一時刻只有一個用戶端的操作能被執行,所以這裡使用Lua指令碼解鎖無競態條件;解鎖符合是佔用鎖的任務釋放的原理;
但是以上實現的分布式鎖缺點是:
- 不具有重入性,即當前任務擷取了鎖,在下次擷取時將會死結;
- 不能自旋擷取,即擷取失敗時,將會立即返回失敗;
本人對其進行了改造,分別做了適應以上兩種情境的分布式鎖,詳情可以戮[Distributed Lock],歡迎大家一起來完善。
總結
本文從What、Features、How的角度分析了分布式鎖。總的來說,單機應用中的多線程或者多進程的鎖的放大版基本上就是分布式鎖了。萬變不離其宗,實現獨佔鎖的關鍵性要素:
- 目標:互斥同步,資源訪問原子化;
- 實現:一次只能有一個爭用到鎖,爭用過程是個原子過程,只能爭用到的解鎖,不會發生死結;
參考
Redis 分布式鎖的正確實現方式
Rewriting our lock
Lock
題外話
參考Rewriting our lock中使用setnx實現的分布式,嚴格意義上來說是有死結問題的。setnx和expire不具有原子性。當setnx成功後,expire前應用發生宕機,這會導致鎖永遠不會到期,別的應用始終爭用不到鎖。當然這種情況比較特殊,但是做代碼是一件嚴謹的事!
Redis(七)分布式鎖