文章原創於公眾號:程式猿周先森。本平臺不定時更新,喜歡我的文章,歡迎關注我的微信公眾號。
在實際專案開發中經常會遇到這樣一個業務場景:如果同一臺機器有多個執行緒搶奪同一個共享資源,同一個執行緒多次執行會出現異常,這種情況下就會出現非執行緒安全。我們解決方法通常使用鎖來解決。但是如果有多臺機器呢?這時候我們通常使用分散式鎖來解決分散式環境下共享資源的同步問題。實現分散式鎖常見有Redis,zookeeper等,今天主要就是講講如何使用Redis實現分散式鎖。
使用Redis實現分散式鎖的方案其實很簡單,首先我們先實現一個方案一:每次執行請求的時候,機器先查詢Redis中是否存在分散式鎖的key,如果不存在鎖的key,就以該鎖為key,value取隨機數寫入到Redis中,然後開始執行請求。
方案一看起來很簡單,但是這樣的處理邏輯不可避免的存在兩個致命的BUG:第一:如果一個程式成功取到鎖,但是這時候這個機器出現故障當機了,分散式的鎖沒有得到釋放,就造成了死鎖的產生了。第二:如果同一時間存在兩個機器同時查詢Redis,都發現Redis不存在鎖的key,於是都成功獲得了鎖。
這時候我們可以這麼處理改善方案一實現方案二:Redis有提供一個原子寫入操作的命令:setnx,setnx只有在鎖的key不存在的情況下才允許設定key值,所以說問題2同一個鎖在同一時間可能會被不同機器獲取到的問題就可以得到解決,而且setnx命令可以設定key值的超時時間,所以在寫入鎖的key時可以為鎖設定一個超時時間,如果超過超時時間鎖還未釋放就會釋放,則其他機器在key釋放後也可以繼續寫入key佔有鎖執行對應的請求。這樣問題1機器當機造成鎖無法及時釋放的問題也因此迎刃而解。
但是這又造成了另外一個潛在的問題:如果某個機器執行耗時操作,超時時間過去了請求還未執行完,鎖就會被釋放掉被新的機器佔有,等耗時任務結束時執行釋放鎖的操作,這時候釋放的鎖不是自己的鎖而是已經被其他機器佔有的鎖。
這時候我們可以將方案再做適當的修改變成方案三:當某個機器佔有鎖並在Redis中設定key時,將value設定為隨機數,在請求處理完畢需要釋放鎖之前加上一步操作:判斷key的value值是否等於之前設定的隨機數,如果是代表這個鎖佔有者還是自己,就可以執行釋放鎖操作,否則代表鎖已經被別人佔有,不能執行釋放鎖操作,這樣就可以解決可能誤操作釋放他人鎖的問題。但是由於查詢和釋放鎖的操作非原子性的,所以可能出現一種情況:在查詢key時發現key的value和機器本身設定的一直,但是還沒來得急釋放鎖時,鎖過期被釋放了,這時候執行釋放鎖操作就會導致釋放的依舊是其他機器佔有的鎖,所以我們方案三需要進一步的改進,也就是我們必須要保證查詢鎖和釋放鎖這兩步操作必須是原子性的,這時候我們就需要使用另一種方式:引入Jedis,使用Lua指令碼將查詢鎖和釋放鎖的兩部分邏輯寫成指令碼,於是Redis執行Lua指令碼時,其他機器的所有命令都必須等到Lua指令碼執行結束才能執行,所以不可能存在查詢鎖結束還未釋放就被其他機器佔領的情況。
到這裡我們介紹完了如何使用Redis實現分散式鎖,但是這是基於單機部署,如果Redis是使用多機部署,每個主節點還有有子節點,由於Redis主從複製是非同步操作,所在上述方案肯定會出現問題的,多機部署可以採用Redission實現分散式鎖,這是官方提供的元件,如果感興趣可以自己閱讀下文件:https://github.com/redisson/redisson
歡迎關注公眾號:程式猿周先森