Redis做分佈式鎖真的靠譜嗎
Redis的分佈式鎖可以通過Lua進行實現,通過setnx和expire命令連用的方式 || 也可以使用高版本的方法同時設置失效時間,但是假如在以下情況下,就會造成無鎖的現象。
注:分佈式鎖能不用就不用,尤其是在高併發的情況下。
釋放了不該釋放的鎖:👇
有時候直接執行 del mylock 是有問題的,我們不能直接執行 del mylock 爲什麼?—— 會導致 “信號錯誤”,釋放了不該釋放的鎖 。
假設如下場景:
時間線 | 線程1 | 線程2 | 線程3 |
---|---|---|---|
時刻1 | 執行 setnx mylock val1 加鎖 | 執行 setnx mylock val2 加鎖 | 執行 setnx mylock val2 加鎖 |
時刻2 | 加鎖成功 | 加鎖失敗 | 加鎖失敗 |
時刻3 | 執行任務... | 嘗試加鎖... | 嘗試加鎖... |
時刻4 | 任務繼續(鎖超時,自動釋放了) | setnx 獲得了鎖(因爲線程1的鎖超時釋放了) | 仍然嘗試加鎖... |
時刻5 | 任務完畢,del mylock 釋放鎖 | 執行任務中... | 獲得了鎖(因爲線程1釋放了線程2的) |
... |
上面的表格中,有兩個維度,一個是縱向的時間線,一個是橫線的線程併發競爭。
我們可以發現線程 1 在開始的時候比較幸運,獲得了鎖,最先開始執行任務,但是,由於他比較耗時,最後鎖超時自動釋放了他都還沒執行完。
因此,線程 2 和線程3 的機會來了。
而這一輪,線程2 比較幸運,得到了鎖。
可是,當線程2正在執行任務期間,線程1 執行完了,還把線程2的鎖給釋放了。
這就相當於,本來你鎖着門在廁所裏邊尿尿,進行到一半的時候,別人進來了,因爲他配了一把和你一模一樣的鑰匙!這就亂套了啊
因此,我們需要安全的釋放鎖——“不是我的鎖,我不能瞎釋放”。
所以,我們在加鎖的時候,就需要標記“這是我的鎖”,在釋放的時候在判斷 “ 這是不是我的鎖?”。
這裏就需要在釋放鎖的時候加上邏輯判斷,合理的邏輯應該是這樣的:
1. 線程1 準備釋放鎖 , 鎖的key 爲 mylock 鎖的 value 爲 thread1_magic_num 2. 查詢當前鎖 current_value = get mylock 3. 判斷 if current_value == thread1_magic_num -- > 是 我(線程1)的鎖 else -- >不是 我(線程1)的鎖 4. 是我的鎖就釋放,否則不能釋放(而是執行自己的其他邏輯)。
爲了實現上面這個邏輯,我們是無法通過 redis 自帶的命令直接完成的。
如果,再寫複雜的代碼去控制釋放鎖,則會讓整體代碼太過於複雜了。
所以,我們引入了lua腳本。
結合Lua 腳本實現釋放鎖的功能,更簡單,redis 執行lua腳本也是原子的,所以更合適,讓合適的人幹合適的事,豈不更好。
Lua實現分佈式鎖 :👇
加鎖:
--[[ 思路: 1.用2個局部變量接受參數 2.由於redis內置lua解析器,執行加鎖命令 3.如果加鎖成功,則設置超時時間 4.返回加鎖命令的執行結果 ]] local key = KEYS[1] local value = KEYS[2] local rs1 = redis.call('SETNX',key,value) if rs1 == true then redis.call('SETEX', key,3600, value) end return rs1
解鎖:
--[[ 思路: 1.接受redis傳來的參數 2.判斷是否是自己的鎖,是則刪掉 3.返回結果值 ]] local key = KEYS[1] local value = KEYS[2] if redis.call('get',key) == value then return redis.call('del',key) else return false end
如此,我們便實現了鎖的安全釋放。
同時,我們還需要結合業務邏輯,進行具體健壯性的保證,比如如果結束了一定不能忘記釋放鎖,異常了也要釋放鎖,某種情況下是否需要回滾事務等。
總結這個分佈式鎖使用的過程便是:
- 加鎖時 key 同,value 不同。
- 釋放鎖時,根據value判斷,是不是我的鎖,不能釋放別人的鎖。
- 及時釋放鎖,而不是利用自動超時。
- 鎖超時時間一定要結合業務情況權衡,過長,過短都不行。
- 程序異常之處,要捕獲,並釋放鎖。如果需要回滾的,主動做回滾、補償。保證整體的健壯性,一致性。
用redis做分佈式鎖真的靠譜嗎???
上面的文字中,我們討論如何使用redis作爲分佈式鎖,並討論了一些細節問題,如鎖超時的問題、安全釋放鎖的問題。
目前爲止,似乎很完美的解決的我們想要的分佈式鎖功能。然而事情並沒有這麼簡單,用redis做分佈式鎖並不“靠譜”。
不靠譜的情況:👇
比如在 Sentinel 集羣中,主節點掛掉時,從節點會取而代之,客戶端上卻並沒有明顯感知。
原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了。
然後從節點變成了主節點,這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,立即就批准了。
這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生。
不過這種不安全也僅僅是在主從發生 failover 的情況下才會產生,而且持續時間極短,業務系統多數情況下可以容忍。
redlock:
爲了解決故障轉移情況下的缺陷,Antirez 發明了 Redlock 算法,使用redlock算法,需要多個redis實例,加鎖的時候,它會想多半節點發送 setex mykey myvalue 命令,只要過半節點成功了,那麼就算加鎖成功了。
釋放鎖的時候需要想所有節點發送del命令。
這是一種基於【大多數都同意】的一種機制。
感興趣的可以查詢相關資料。在實際工作中使用的時候,我們可以選擇已有的開源實現,python有redlock-py,java 中有Redisson redlock。
爲了使用 Redlock,需要提供多個 Redis 實例,這些實例之前相互獨立沒有主從關係。
同很多分佈式算法一樣,redlock 也使用「大多數機制」。
加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點 set成功,那就認爲加鎖成功。
釋放鎖時,需要向所有節點發送 del 指令。
不過 Redlock 算法還 需要考慮出錯重試、時鐘漂移等很多細節問題,同時因爲 Redlock 需要向多個節點進行讀寫,意味着相比單實例 Redis 性能會下降一些。
Redlock 使用場景:
如果你很在乎高可用性,希望掛了一臺 redis 完全不受影響,那就應該考慮 redlock。
不過代價也是有的,需要更多的 redis 實例,性能也下降了,代碼上還需要引入額外的library,運維上也需要特殊對待,這些都是需要考慮的成本,使用前請再三斟酌。
人生無常大腸包小腸
引文:https://www.cnblogs.com/dalianpai/p/12709408.html