1. 程式人生 > 實用技巧 >es6轉碼和package.json中的配置

es6轉碼和package.json中的配置

提到資料一致性、操作原子性,諸如此類的一些與併發有關的詞彙時不知道你第一時間會聯想到什麼呢?我相信大多數人可能會想到“鎖”,為什麼是鎖呢,這個我不多說,大家心裡應該都明白。

在單體應用時代,我們使用jvm提供的鎖就可以很好的工作,但是到了分散式應用時代,jvm提供的鎖就行不通了,那麼勢必要藉助一些跨jvm的臨界資源來支援鎖的相關語義,比如redis,zookeeper等。

步入正題

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

加鎖過程分析

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

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然後判斷舊值是否過期就不會有這個問題,設想一下如下場景:

  • C1加鎖成功,不巧的是,這時C1意外的奔潰了,自然就不會釋放鎖;
  • C2,C3嘗試加鎖,這時key已存在,所以C2,C3去判斷key是否已過期,這裡假設key已經過期了,所以C2,C3使用set指令去設定值,那兩個都會加鎖成功,這就闖大禍了;如果使用getset指令,然後判斷下返回值是否過期就可以避免這種問題,假如C2跑的快,那C3判斷返回的時間戳已經過期,自然就加鎖失敗;

釋放鎖過程分析

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

A1:考慮這樣一種場景:

  • C1獲取鎖成功,開始執行自己的操作,不幸的是C1這時被阻塞了;
  • C2這時來獲取鎖,由於C1被阻塞了很長時間,所以key對應的value已經過期了,這時C2通過getset加鎖成功;
  • C1塵封了太久終於被再次喚醒,對於釋放鎖這件事它可是認真的,伴隨著一波del操作,悲劇即將發生;
  • C3來獲取鎖,好傢伙,居然一下就成功了,接著就是一波操作猛如虎,接著就是一堆的客訴過來了;

為什麼會這樣呢?回想C1被喚醒以後的事情,居然敢直接del,C2活都沒幹完呢,鎖就被C1給釋放了,這時C3來直接就加鎖成功,所以為了安全起見C3釋放鎖時得分成兩步:1.判斷value是否已經過期 2.如果已過期直接忽略,如果沒過期就執行del。

這樣就真的安全了嗎?安全了嗎?安全了嗎?假如第一步和第二步之間相隔了很久是不是也會出現鎖被其他人釋放的問題呢?是吧?是的!有沒有別的解決辦法呢?聽說藉助lua就可以解決這個問題了,感興趣的直接給你傳送過去可好。

正視自己的缺點

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

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

Q2:怎麼實現的高可用?

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

  • C1去redis1加鎖,加鎖成功後會寫到redis2,redis3;
  • C2也去redis1加鎖,但是此時C2到redis1的網路出現問題,這時C2切換到redis2去加鎖,由於第一步中的redis多寫並不是原子的,所有就有可能導致C2也獲取鎖成功;

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