1. 程式人生 > 資料庫 >「摩擦面試官」Redis實現分散式鎖這一篇就夠了

「摩擦面試官」Redis實現分散式鎖這一篇就夠了

每日一問

靈魂拷問之☞用電鰻的電去電電鰻會電到電鰻嗎?
各位巨佬們把答案留在評論區吧
上車上車

分散式鎖簡介

是一種用來解決多個執行執行緒,訪問共享資源時出現錯誤或資料不一致問題的工具。
在JAVA中可以使用synchronized關鍵字或者使用java.util.concurrent.locks.Lock的實現類,例如java.util.concurrent.locks.ReentrantLock
在分散式、微服務的架構體系下,不能再使用相關的關鍵字或者鎖的實現類來達到序列執行,相關的關鍵字或者鎖的實現類只能適用於單機的情況下,需要在一個JVM體系內才能實現鎖,這個時候他就出現了,不知道分散式鎖這個名字是誰命名的。好傢伙,那麼該怎麼樣實現分散式鎖呢?

應用場景

1.避免不同節點重複的執行某一塊邏輯,比如說我們的定時任務,不加鎖的情況下在某一時間點這個定時任務可能會執行多次
2.防止表單重複提交
3.避免破壞資料的正確性,當多個執行緒同時某一塊邏輯時,可能會出現資料的錯亂或者不一致

JAVA實現

1.基於Redis,不熟悉Redis的可以看,也是今天的主題
2.基於資料庫,一般資料都有實現悲觀鎖,當然也可以用版本號或者其他樂觀鎖來實現,悲觀鎖的話主要還是for update 關鍵字
3.基於Zookeeper,原理就是利用了Zookeeper的臨時節點來實現。
推薦的話使用Zookeeper來實現分散式鎖,主要是因為Zookeeper相關的特性非常適合用來做分散式鎖,後續再來講這一塊的東西。當然,前提是專案中有使用到Zookeeper,不然就光為了實現一個分散式鎖而搭建一套Zookeeper的叢集那就得不償失了。redis則不一樣,基本上的專案都會使用,當時使用redis會有一些問題。下面就進入正題吧

Redis實現原理

使用Redis實現分散式鎖主要是運用了Redis的SETNX(SET if Not eXists) 指令和lua指令碼來實現,那為什麼要用到lua指令碼呢?
原因只要是我們要給鎖加一個過期時間並沒有一個API能提供,一般都需要拆分成兩個步驟,第一個將key存入Redis,第二部給key設定過期時間,這個時候就會導致執行緒不安全性。因為 Lua 指令碼可以保證多個指令的原子性執行(單執行緒基於I\O多了複用和對列執行命令)。
RedisLockRegistry lua指令碼
KEYS[1]代表當前鎖的key值,ARGV[1]代表當前的客戶端標識,ARGV[2]代表過期時間。
基本邏輯就是拿著KEYS[1]去redis中獲取值,如果值等於ARGV[1]就表示這條資料已經被上鎖了,並且延長鎖的過期時間,如果想要獲取鎖鎖就要等待拿到鎖的程序釋放鎖;如果這個鍵KEYS[1]不存在,那麼設定KEYS[1]的值為ARGV[1],並且設定過期時間為ARGV[2],即當前程序就獲取到這個資料的鎖,並設定過期時間。從而達到原子命令的效果

問題點

鎖超時

假設現在我們有的服務A和B,其中 A 服務在 獲取鎖之後由於某種原因掛了,那麼 B 服務就永遠無法獲取到鎖了,這個時候就需要加一個超時時間來釋放鎖,但是加了超時時間後又出現一個新的問題就是如果在加鎖和釋放鎖之間的邏輯執行得太長,以至於超出了鎖的超時限制,這時鎖已經被釋放了,但是實際上執行邏輯還沒有處理完,可能出現多個執行緒同時執行的情況。
解決方案:
  1.不解決,不要打我(手動狗頭),解決不了我就加入他, 也就是儘量避免。Redis 分散式鎖不要用於較長時間的任務。如果真的偶爾出現了問題,造成的資料小錯亂可能就需要人工的干預。
  2.安全點的方式手動釋放鎖,將鎖的 value 值設定為一個隨機數,釋放鎖時先匹配隨機數是否一致,然後再刪除 key,這是為了確保當前執行緒佔有的鎖不會被其他執行緒釋放,除非這個鎖是因為過期了而被伺服器自動釋放的。這種方式也會存在問題 ,因為匹配value和刪除key在 Redis 中並不是一個原子性的操作,所以也是不安全的,當然可以使用lua指令碼來保證。
  3.使用Redisson框架,原理我們下面會提到,好哥哥們不用著急

單點/多點問題

如果 Redis 採用單機部署模式,那就意味著當 Redis 故障了,就會導致整個服務不可用。而如果採用主從模式部署,我們想象一個這樣的場景:服務 A 申請到一把鎖之後,如果作為主機的 Redis 宕機了,那麼 服務 B 在申請鎖的時候就會從從機那裡獲取到這把鎖,為了解決這個問題,Redis 作者提出了一種 RedLock 紅鎖 的演算法

Redisson實現原理

1.加鎖機制:就是上面提到的SETNX(SET if Not eXists)指令和lua指令碼來實現
2.watch dog自動延期機制,就是會啟動一個後臺執行緒,定時比如說10秒檢測當前執行緒是否還持有了鎖,是的話就延期。
3.可重入性,Redis儲存鎖的資料型別是 Hash型別並且Hash資料型別的key值包含了當前執行緒資訊。
4.互斥性,通過Redis資料結構來保證分散式鎖的唯一性
5.避免死鎖,通過Redis的超時時間保證
redisson執行原理

Redisson存在的問題

1.當Redis做的是Cluster叢集的情況下,如果客戶端對某個master節點寫入了Redisson鎖,此時會非同步複製給對應的 slave節點。但是這個過程中一旦發生 master節點宕機,主備切換,slave節點從變為了 master節點。這時客戶端2來嘗試加鎖的時候,由於客戶端1已經宕機的master節點加鎖成功,而客戶端2在新的master節點上也能加鎖,此時就會導致多個客戶端對同一個分散式鎖完成了加鎖。
2.在哨兵模式或者主從模式下,如果 master例項宕機的時候,可能導致多個客戶端同時完成加鎖。
解決方案:
  1.不解決,不要打我(手動狗頭),解決不了我就加入他,沒想到吧,又是我
  2.使用Zookeeper實現,拋棄它(你個渣男)。 這也是我推薦使用Zookeeper來實現分散式鎖的原因,沒有那麼多問題。 後續再來講這個玩意(彩蛋)

本期到這裡啦,寫的不對的地方巨佬們多多指點,喜歡的話來一個一鍵三連吧