前面的文章詳細介紹了「分布式互斥」,解釋了同一臨界資源(共享資源)同一時刻只能被乙個程式訪問的問題,也就是說只有獲得訪問許可權的程序才可以訪問共享資源,而此時其他程序必須等待擁有該許可權的程序釋放許可權。那麼在訪問共享資源時,這個許可權是如何設定或產生的呢?以及設定或產生這個許可權的工作原理是什麼?本文就來介紹分布式鎖是如何解決這個問題的。
通常在單機多執行緒環境中,會有多個執行緒訪問同乙個共享資源(臨界資源)的情況,此時,為了保證資料的一致性,會有某種機制來控制讓滿足某條件的執行緒訪問資源,不滿足的等待,直到下一輪競爭中滿足條件後才可訪問資源。
而這個機制就是:為了實現分布式互斥,需要在某處設定乙個所有執行緒都能看到的標記,當標記被設定後,其他執行緒只能等待擁有該標記的執行緒執行完成,並釋放該標記後,才能去設定該標記和訪問共享資源。這個標記,就是我們常說的鎖。
總結:在單機環境中鎖是實現多執行緒訪問同一共享資源時,保證同一時刻只有乙個執行緒可訪問該資源而設定的一種標記。
分布式鎖為了實現多個程序併發訪問同乙個臨界資源,同一時刻只有乙個程序可訪問共享資源,確保資料的一致性。而與單機環境下鎖的主要區別是:
執行在分布式環境:分布式鎖是在分布式環境下,系統部署在多個機器中,實現多程序分布式互斥的一種鎖;
鎖被公共儲存:為了保證多個程序能看到鎖,鎖被存在公共儲存(比如 redis、memcache、資料庫等三方儲存中);
我們先看乙個實際場景的例子:某電商要售賣某大牌手機,庫存只有 2 個,但有多個來自不同地區的使用者幾乎同時下單,如果規定按照下單時間作為購買成功的判斷依據,最終需要保證手機售出時,資料庫中更新的庫存是正確的。
**解決上述問題單機環境鎖的方案是:**給手機的庫存數加乙個鎖。當有乙個使用者提交訂單後,後台伺服器給庫存數加乙個鎖,根據該使用者的訂單修改庫存。而其他使用者必須等到鎖釋放以後,才能重新獲取庫存數,繼續購買。在這裡,手機的庫存就是共享資源,不同的購買者對應著多個程序,後台伺服器對共享資源加的鎖就是告訴其他程序「非請勿入」。
但是上述方法能夠解決問題嗎?當然沒這麼簡單。想象一下,使用者 a 想買 1 個手機,使用者 b 想買 2 個手機。在理想狀態下,使用者 a 網速好先買走了 1 個,庫存還剩下 1 個,此時應該提示使用者 b 庫存不足,使用者 b 購買失敗。但實際情況是,使用者 a 和使用者 b 同時獲取到商品庫存還剩 2 個,使用者 a 買走 1 個,在使用者 a 更新庫存之前,使用者 b 又買走了 2 個,此時使用者 b 更新庫存,商品還剩 0 個。這時,電商就頭大了,總共 2 個吹風機,卻賣出去了 3 個。
所以,如果只使用單機鎖將會出現不可預知的後果。因此,在高併發場景下,為了保證臨界資源同一時間只能被乙個程序使用,從而確保資料的一致性,我們就需要引入分布式鎖了。此外,在大規模分布式系統中,單個機器的執行緒鎖無法管控多個機器對同一資源的訪問,這時使用分布式鎖,就可以把整個集群當作乙個應用一樣去處理,實用性和擴充套件性更好。
實現分布式鎖的 3 種主流方法:
1.4.1 基於資料庫實現分布式鎖
基於資料庫實現分布式鎖的方式就是建立一張鎖表,然後通過操作該表中的資料來實現。當我們要鎖住某個資源時,就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。資料庫對共享資源做了唯一性約束,如果有多個請求被同時提交到資料庫的話,資料庫會保證只有乙個操作可以成功,操作成功的那個執行緒就獲得了訪問共享資源的鎖,可以進行操作。
實現過程舉例:
下面,我們還是以上面電商售賣手機的場景為例。手機庫存是 2 個,有 3 個來自不同地區的使用者想要購買,其中使用者 a 想買 1 個,使用者 b 想買 2 個,使用者 c 想買 1 個。具體實現過程如下:
1.4.2 基於快取實現分布式鎖
基於資料庫實現的分布式鎖,是最容易理解的。但是,因為資料庫需要落到硬碟上,頻繁讀取資料庫會導致 io 開銷大,因此這種分布式鎖適用於併發量低,對效能要求低的場景。
對於雙 11、雙 12 等需求量激增的場景,資料庫鎖是無法滿足其效能要求的,而基於快取實現分布式鎖的方式,非常適合解決這種場景下的問題。所謂基於快取,也就是說把資料存放在計算機記憶體中,不需要寫入磁碟,減少了 io 讀寫。
redis-基於快取實現分布式鎖的方式:
redis 通常可以使用setnx(key, value) 函式來實現分布式鎖。key和value就是基於快取的分布式鎖的兩個屬性,其中 key 表示鎖 id,value = currenttime + timeout,表示當前時間 + 超時時間。也就是說,某個程序獲得 key 這把鎖後,如果在 value 的時間內未釋放鎖,系統就會主動釋放鎖
。setnx 函式的返回值有 0 和 1:
1.4.3 基於 zookeeper 實現分布式鎖
zookeeper 基於樹形資料儲存結構實現分布式鎖,來解決多個程序同時訪問同一臨界資源時,資料的一致性問題。zookeeper 的樹形資料儲存結構主要由 4 種節點構成:
在與該方法對應的持久節點shared_lock的目錄下,為每個程序建立乙個臨時順序節點。如下圖所示,手機就是乙個擁有 shared_lock 的目錄,當有人買手機時,會為他建立乙個臨時順序節點。
每個程序獲取 shared_lock 目錄下的所有臨時節點列表,註冊子節點變更的 watcher,並監聽節點。
每個節點確定自己的編號是否是 shared_lock 下所有子節點中最小的,若最小,則獲得鎖。例如,使用者 a 的訂單最先到伺服器,因此建立了編號為 1 的臨時順序節點 locknode1。該節點的編號是持久節點目錄下最小的,因此獲取到分布式鎖,可以訪問臨界資源,從而可以購買手機。
若本程序對應的臨時節點編號不是最小的,則分為兩種情況:a. 本程序為讀請求,如果比自己序號小的節點中有寫請求,則等待;b. 本程序為寫請求,如果比自己序號小的節點中有讀請求,則等待。
例如,使用者 b 也想要買手機,但在他之前,使用者 c 想看看手機的庫存量。因此,使用者 b 只能等使用者 a 買完手機、使用者 c 查詢完庫存量後,才能購買手機。
小結:
使用 zookeeper 可以完美解決設計分布式鎖時遇到的各種問題,比如單點故障、不可重入、死鎖等問題。雖然 zookeeper 實現的分布式鎖,幾乎能涵蓋所有分布式鎖的特性,且易於實現,但需要頻繁地新增和刪除節點,所以效能不如基於快取實現的分布式鎖。
1.4.4 三種分布式鎖實現方式對比
總結來說,zookeeper 分布式鎖的可靠性最高,有封裝好的框架,很容易實現分布式鎖的功能,並且幾乎解決了資料庫鎖和快取式鎖的不足,因此是實現分布式鎖的首選方法。
分布式技術架構原理解析之協調與同步(三)分布式共識
之前提到的分布式選舉問題,是通過分布式選舉演算法從多個節點中選出乙個主節點。不管是哪種選舉演算法,幾乎都有乙個共同特點 每個節點都有選舉權和被選舉權,當某個節點得到了大部分節點的同意或認可後成為主節點,然後主節點向其他節點宣告主權。從本質上看,分布式選舉問題,其實就是傳統的分布式共識方法,主要是基於...
memcache分布式原理解析
所謂的分布式就是將不同的資料放在不同的伺服器上,獲取資料時需要根據路由從不同的伺服器上獲取。在memcache中,伺服器端並不支援分布式,而只是在客戶端程式中設定分布式。如果有多個memcache伺服器,那麼他們之間並不互相通訊,資料也無法同步。所以在很多語言對memcache操作的類庫中都可以配置...
分布式事務原理解析
了解過tcc分布式事務的都知道它有三個階段 try,confirm,cancel,但很多文章就只有原理圖,和對原理圖的解釋,看一遍也留不下印象,這裡用實際場景舉個例子,說明tcc分布式事務原理 tcc分布式框架推薦 bytetcc,tcc transaction,himly 最終一致性方案一般都是有...