(1)分布式鎖和分布式事務的區別
1.分布式鎖是在集群環境下,用來控制不同機器對全局共享資源的訪問。
2.分布式事務是在集群環境下,用來保證全局事務的一致性,保證多個數據庫的數據整體上能正確的從一個一致性狀態轉到另一個一致性狀態。
(2)分布式鎖應用場景
在我們的某個jvm應用程序中,如果需要對某個共享變量進行多線程同步訪問,可以使用java多線程的同步工具,例如ReentrantLock、Synchronized等等。但是當我們的系統由單機部署演化成為分布式集群系統后,在不同機器上原來單機的并發控制鎖策略就會失效了,這時就需要引入分布式鎖。
舉個簡單的例子,比如某個外賣騎士每天需要購買一份保險,某天在購買保險時,這個騎士多次點擊了買保險的按鈕,前端沒有做控制,導致同時向服務器發送了多個買保險請求,那這個時候就會導致騎士多次購買保險,為了解決這個問題,我們可以使用分布式鎖,保證在某個時間段內,只能有一臺機器執行買保險操作,這時就能保障騎士不會多次購買保險。分布式鎖主要有三種實現方式:
- 基于數據庫實現
- 基于redis實現
- 基于ZooKeeper實現
(3)分布式鎖實現方式
1.基于數據庫實現
我們可以通過數據庫表來實現分布式鎖,表結構如下:
表中的每一條記錄就代表一個鎖,當我們需要獲取鎖時就去數據庫中查詢是否有該共享資源的記錄。
(1)如果有則獲取其中的node_info,與自己的機器信息進行比較,如果發現是自己已經獲取過的鎖,那直接使用共享資源,將count數加1,在使用完數據后將count數減1,當count數為0時將記錄刪除掉。否則就返回錯誤信息,提示已經有人獲取了鎖。
(2)如果獲取不到就建立一條記錄,那就自己建立一條記錄,并記錄下自己的機器信息,然后使用共享資源。
上面其實就是利用數據庫實現鎖的簡單方式。這種方式存在比較嚴重的問題:1.數據庫機器可能存在單點、2.沒有超時時間,數據庫宕掉后可能導致無法獲取鎖、3.數據庫是磁盤文件,獲取鎖的時間和性能并不好。
2.基于redis實現
基于redis的分布式鎖,主要依賴于redis的幾個命令:
- setnx():set if not exists,其主要有兩個參數 setnx(key, value)。該方法是原子的,如果 key 不存在,則設置當前 key 成功,返回 1;如果當前 key 已經存在,則設置當前 key 失敗,返回 0。
- expire():expire 設置過期時間,要注意的是 setnx 命令不能設置 key 的超時時間,只能通過 expire() 來對 key 設置。
使用步驟:
1、setnx(lockkey, 1) 如果返回 0,則獲取鎖失敗;如果返回 1,則說明獲取鎖成功。
2、expire() 命令對 lockkey 設置超時時間,為的是避免死鎖。
3、執行完業務代碼后,可以通過 delete 命令刪除 key。
使用問題:
上面的使用方式是能滿足我們大部分的業務需求的,但是在某些極端的情況下,還是會存在問題。
- 問題: 當節點A執行完setnx后,節點A就宕掉了,還沒來得及執行expire,那么會導致其他節點一直無法獲取鎖。
解決方案: 使用set命令代替setnx和expire。
// NX表示:key不存在就返回true,存在就返回false
// PX表示:指定的過期時間
SET lockName value NX PX
- 問題: 節點A獲取了鎖,并設置了30秒的過期時間,但是由于某些原因業務代碼執行了很久(超過了30秒),delete操作還沒執行。這時因為節點A獲取的鎖過期了,節點B獲取了鎖,而節點A執行了delete操作,釋放了節點B的鎖。
解決方案: 在通過delete操作釋放鎖之前判斷當前的鎖是不是自己加的。
當然我們也可以使用RedLock,RedLock是redis實現的分布式鎖算法(RedLock),可以有效防止單點故障 。另外還有基于Zookeeper的實現,對于Zookeeper我不咋懂,所以這里就不寫了。