Redis(七)分佈式鎖

前面學習了Redis的數據結構以及命令、Redis中的事務和Redis對Lua腳本的支持。redis

這一章就對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的前提條件:app

  • 同一臺機器上的共同資源;
  • 多線程環境訪問共同資源;

Lock目標:分佈式

  • 保證多線程訪問資源的一致性;

Lock的實現:ide

  • 單核處理器上禁用中斷,使得同步資源可以被訪問結束;
  • 硬件支持的原子指令,「比較和交換」等等,用於測試鎖是不是空閒,若是空閒獲取鎖;

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前應用發生宕機,這會致使鎖永遠不會過時,別的應用始終爭用不到鎖。固然這種狀況比較特殊,可是作代碼是一件嚴謹的事!

相關文章
相關標籤/搜索