我司使用了六年的分布式鎖

2022-01-22 11:08:31 字數 1713 閱讀 4861

前言

提到資料一致性、操作原子性,諸如此類的一些與併發有關的詞彙時不知道你第一時間會聯想到什麼呢?我相信大多數人可能會想到「鎖」,為什麼是鎖呢,這個我不多說,大家心裡應該都明白。在單體應用時代,我們使用jvm提供的鎖就可以很好的工作,但是到了分布式應用時代,jvm提供的鎖就行不通了,那麼勢必要借助一些跨jvm的臨界資源來支援鎖的相關語義,比如redis,zookeeper等。

步入正題

我今天就來分享下我司基於redis來實現的分布式鎖,2023年投入使用,也算是久經沙場。但是也存在一些設計上的缺陷,這個我後面也會提到,希望大家秉著互相學習的態度文明交流,別一上來就說這不行那不行,還是那句話「適合自己的才是最好的」。

加鎖過程分析

我第一次讀**的時候,有這麼幾個疑惑:

q1:為什麼不使用 set key value [expiration ex seconds|px milliseconds] [nx|xx]  這個指令來實現key的自動過期呢,反而放到應用**判斷key是否過期?

a1:我們的分布式鎖開發的時候set命令還不支援nx、px,所以才想出這種辦法來實現key過期,nx、px在2.6.12以後開始支援;

q2:已經判斷了當前key對應的時間戳已經過期了,為什麼還要使用getset再獲取一次呢,直接使用set指令覆蓋不可以嗎?

a2:這裡其實牽扯到併發的一些事情,如果直接使用set,那有可能多個客戶端會同時獲取到鎖,如果使用getset然後判斷舊值是否過期就不會有這個問題,設想一下如下場景:

釋放鎖過程分析

q1:為什麼釋放鎖時還需要判斷key是否過期呢,直接del不是效能更高嗎?

a1:考慮這樣一種場景:

為什麼會這樣呢?回想c1被喚醒以後的事情,居然敢直接del,c2活都沒幹完呢,鎖就被c1給釋放了,這時c3來直接就加鎖成功,所以為了安全起見c3釋放鎖時得分成兩步:1.判斷value是否已經過期 2.如果已過期直接忽略,如果沒過期就執行del。這樣就真的安全了嗎?安全了嗎?安全了嗎?假如第一步和第二步之間相隔了很久是不是也會出現鎖被其他人釋放的問題呢?是吧?是的!有沒有別的解決辦法呢?聽說借助lua就可以解決這個問題了,感興趣的直接給你傳送過去可好。

正視自己的缺點

q1:redis鎖的過期時間小於業務的執行時間該如何續期?

a1:這個暫時沒有實現,據說有乙個叫redisson的傢伙解決了這個問題,我們也有部分業務在使用,未來有可能會切換到redisson。

q2:怎麼實現的高可用?

a2:我們採用failover機制,初始化redis鎖的時候會維護乙個redis連線池,加鎖或者釋放鎖的時候採用多寫的方式來保障一致性,如果某個節點不可用的時候會自動切換到其他節點,但是這種機制可能會導致多個客戶端同時獲取到鎖的情況,考慮這種情況:

針對這種情況,目前有些業務方是通過資料庫唯一索引的方式來規避的,未來會修復這個bug,具體方案目前還沒有。

總結

五一假期抽一點時間來做乙個簡單的分享,希望對有些同學能起到幫助,不喜勿噴。

關於redis分布式鎖的使用

背景 比如我有100張點卡,有兩台伺服器同時進行賣這個點卡,但是今天我就想賣10張,超出10張我就不賣了,在多執行緒的額情況下很容易出現賣出了11張甚至更多,這也是超賣的問題,從實現來說也可能出現兩個人獲取到的是同一張點卡,那麼也是不可取的 出於乙個涉世未深,對那些高大上的東西充滿好奇的我希望可以使...

分布式鎖的不同使用方式

lock lock lockholder.createlock bizcode,lockkey,timeoutmsecs,expiremsecs 業務編號,鎖定key,獲取鎖超時,鎖超時 1 lock.lock 注意 此方法為在超時範圍內阻塞式獲取鎖,如果超時仍然沒有獲得鎖,將不中斷,繼續進行後續操...

Zookeeper分布式鎖的嘗試使用

通過嘗試使用資料庫和redis來實現分布式鎖的方式 還有另外一種就是基於資料庫本身提供的排他鎖來實現分布式的鎖,利用資料庫的innodb引擎可以借助它自身的事務功能給我們開啟的鎖,當乙個執行緒進來時,通過select from table where for update可以讓innodb引擎在查詢...