單機多執行緒: 在Java 中,我們通常使用ReetrantLock 類別、synchronized 關鍵字這類本機鎖定來控制一個JVM 進程內的多個執行緒對本機共用資源的存取
分散式系統: 不同的服務/客戶端通常運行在獨立的JVM 進程上。如果多個 JVM 進程共享同一份資源的話,使用本地鎖就沒辦法實現資源的互斥存取權了。於是,分散式鎖就誕生了。
舉個例子:系統的訂單服務總共部署了 3 份,都對外提供服務。用戶下訂單前需要檢查庫存,為了防止超賣,這裡需要加鎖以實現對檢查庫存操作的同步存取。由於訂單服務位於不同的 JVM 進程中,因此本地鎖在這種情況下就沒辦法正常運作了。我們需要用到分散式鎖,這樣的話,即使多個執行緒不在同一個 JVM 進程中也能取得到同一把鎖,進而實現對共享資源的互斥存取。
一個最基本的分散式鎖定需要滿足:
#互斥:任一時刻,鎖定只能被一個線程持有;
高可用:鎖服務是高可用的。而且,即使客戶端的釋放鎖的程式碼邏輯出現問題,鎖最終一定還是會被釋放,不會影響其他執行緒對共享資源的存取。
可重入:一個節點取得了鎖定之後,還可以再次取得鎖定。
不論是本地鎖定還是分散式鎖,核心都在於==「互斥」==。
在 Redis 中, SETNX
指令是可以幫助我們實現互斥。 SETNX
即 SET if Not eXists (對應 Java 中的 setIfAbsent 方法),如果 key 不存在的話,才會設定 key 的值。如果 key 已經存在, SETNX 啥也不做。
> SETNX lockKey uniqueValue
(integer) 1
> SETNX lockKey uniqueValue
(integer) 0
釋放鎖定的話,直接透過DEL 指令刪除對應的key 即可
> DEL lockKey
(integer) 1
為了防止誤刪到其他的鎖,這裡我們建議使用Lua 腳本透過key 對應的value(唯一值)來判斷。
選用 Lua 腳本是為了保證解鎖操作的原子性。因為 Redis 在執行 Lua 腳本時,可以以原子性的方式執行,從而確保了鎖定釋放操作的原子性。
// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
這是一種最簡易的 Redis 分散式鎖定實現,實現方式比較簡單,效能也很有效率。不過,這種方式實作分散式鎖存在一些問題。就例如應用程式遇到一些問題例如釋放鎖的邏輯突然掛掉,可能會導致鎖無法被釋放,進而造成共享資源無法再被其他執行緒/進程存取。
主要為了避免鎖定無法被釋放
127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX
OK
lockKey :加鎖的鎖定名稱;
uniqueValue :能夠唯一標示鎖定的隨機字串;
NX :只有當lockKey 對應的key 值不存在的時候才能SET 成功;
EX :過期時間設定(秒為單位)EX 3 標示這個鎖有一個3 秒的自動過期時間。與 EX 對應的是 PX(毫秒為單位),這兩個都是過期時間設定。
一定要保證設定指定 key 的值和過期時間是一個原子運算! ! !不然的話,依然可能會出現鎖無法被釋放的問題。
這種解決方法同樣存在漏洞:
如果操作共享資源的時間大於過期時間,就會出現鎖定提前過期的問題,進而導致分散式鎖定直接失效
如果鎖的逾時時間設定過長,又會影響到效能
使用方式举例:
// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();
只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。
// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制 lock.lock(10, TimeUnit.SECONDS);
总的来说就是使用Redisson,它带有自动的续期机制
所谓可重入锁指的是在一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法即可重入 ,而无需重新获得锁。像 Java 中的 synchronized 和 ReentrantLock 都属于可重入锁。
可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。
以上是Java分散式鎖如何實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!