1. 程式人生 > 實用技巧 >JAVA 高併發下單解決方案-分散式鎖

JAVA 高併發下單解決方案-分散式鎖

背景:高併發情況下,商品出現超賣的情況。

最終目標:保證資料的最終一致性。

Contrrler 層框架 :Spring MVC

第一次嘗試:最初的時候,發現Spring MVC是一個單例多執行緒的Controller框架。它在多執行緒同時訪問的時候會出現執行緒不安全的情況。經過分析,發現如果不建立 成員變數 的話,執行緒不安全的情況是不會出現的。如果需要建立成員變數,解決這個問題可以通過ThreadLocal來解決這個問題。ThreadLocal可以儲存 獨屬於 執行緒的變數。(PS:說了這麼多還是沒解決這個問題)

第二次嘗試:發現不是Spring MVC的問題後,開始使用 Java1.5新特性中的Lock鎖來解決這個問題。保證同一時期,只有一個執行緒可以進行寫的操作。(暫時解決了問題)

第二次嘗試失敗原因:後續訂單量又一步的增長,發現又出現了超賣的情況。細查之後,發現是因為有多個 tomcat 容器,多個tomcat之間的鎖不同步導致。

第三次嘗試:分散式同步鎖。在經過大量的資料查詢後,分散式鎖無法滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性,最多隻能滿足其中兩項。所以,建議各位在程式設計之初就要做出取捨。在網際網路這個行業中,我所接觸的專案中大多都犧牲了系統的強一致性,從而來換取系統的可用性(但是系統一定要確保 資料的最終一致性)。為了確保 資料的最終一致性,分散式同步鎖就這樣誕生了。

目前有一下幾種方案:

1,基於快取實現分散式鎖

基於快取實現的分散式鎖,在效能上是非要好的,並且叢集部署

使用setNX 來儲存 鎖的超時時間戳

語法:

SETNX key value

setNX 解析:

  將key的值設為value,當且僅當key不存在。

  若給定的key已經存在,則SETNX不做任何動作。

  SETNX是『SET if Not eXists』(如果不存在,則 SET)的簡寫

返回值:

  設定成功,返回1。

  設定失敗,返回0。

問題一-死鎖:如果一個持有鎖的客戶端失敗或崩潰了不能釋放鎖

問題一-解決方案:可以根據鎖的超時時間戳 來判斷是否發生了,如果當前時間大於 lock的值,說明該鎖已經失效,可以重新使用。但是要注意,發生這種情況不能 只del 然後 setNX。因為這樣的話,當多個執行緒檢測到超時後,就會去 del 該鎖,然後持有他。

問題二-多執行緒持有鎖-解決方案:getSET

語法:

GETSET key value

將指定的key設定指定的value值,並且返回之前儲存的value值。當沒有返回值時,返回 null

假設現在 A1 伺服器已經發生了死鎖問題。這時 A2 伺服器 setNX操作返回0, A2伺服器 get操作檢查是否超時。如果沒超時就繼續等待重試。

如果已超時,就通過 getSET 重新設定超時時間,如果 A2伺服器拿到的是未超時的值。說明在此之前 A3伺服器 先一步進行了 getSET 操作,那麼 A2伺服器繼續等待重試。

注意:當持有鎖的 A1 伺服器準備 del 的時候,一定要再次檢查一下 鎖是否超時。如果已超時就不必要解鎖了。

2,基於Zookeeper實現分散式鎖

基於zookeeper臨時有序節點可以實現的分散式鎖。

注意:基於Zookeeper 實現的 分散式鎖 有效的解決了 死鎖這個問題。但是在效能上是不如 快取鎖的,而且需要對Zookeeper的原理有所瞭解。其次,使用Zookeeper也有可能出現問題。由於網路抖動,客戶端與Zookeeper的連線斷了,這時Zookeeper就會以為客戶端掛了,就會刪除鎖節點。這時就會產生併發問題。

3,基於 資料庫實現分散式鎖

要實現分散式鎖,最簡單的方式可能就是直接建立一張鎖表,然後通過操作該表中的資料來實現了。

當我們要鎖住某個方法或資源時,我們就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。

總結:以上幾種方案其實都無法做到完美。

從 實現複雜度來說:Zookeeper與Redis差不多、最為複雜的是資料庫

從 效能角度來說:Redis最好、其次Zookeeper、最差為資料庫

從 可靠性來說:Zookeeper最好、其次Redis、最差為資料庫

個人建議,在使用分散式鎖中 不要去使用 資料庫的方式來進行操作。操作資料庫需要一定的開銷,並且行級鎖不一定靠譜。

關於Zookeeper實現的分散式鎖,由於本人不是很瞭解Zookeeper,所以介紹的不是很詳細。如果有興趣的同學可以自己研究一下。(我會告訴你們,我用的就是 redis分散式鎖嗎 ^_^)