分布式鎖技術實現原理_第1頁
分布式鎖技術實現原理_第2頁
分布式鎖技術實現原理_第3頁
分布式鎖技術實現原理_第4頁
分布式鎖技術實現原理_第5頁
已閱讀5頁,還剩12頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、 分布式鎖技術實現原理目 錄 TOC o 1-3 h z u HYPERLINK l _Toc526848738 1.分布式鎖概念 PAGEREF _Toc526848738 h 3 HYPERLINK l _Toc526848739 2.基于 Redis 實現的鎖服務 PAGEREF _Toc526848739 h 3 HYPERLINK l _Toc526848740 3.基于ZooKeeper 實現的鎖服務 PAGEREF _Toc526848740 h 5分布式鎖概念分布式鎖,是用來控制分布式系統中互斥訪問共享資源的一種手段,從而避免并行導致的結果不可控。基本的實現原理和單進程鎖是一致

2、的,通過一個共享標識來確定唯一性,對共享標識進行修改時能夠保證原子性和和對鎖服務調用方的可見性。由于分布式環境需要考慮各種異常因素,為實現一個靠譜的分布式鎖服務引入了一定的復雜度。分布式鎖服務一般需要能夠保證以下幾點。同一時刻只能有一個線程持有鎖鎖能夠可重入不會發生死鎖具備阻塞鎖特性,且能夠及時從阻塞狀態被喚醒鎖服務保證高性能和高可用當前使用較多的分布式鎖方案主要基于 Redis、ZooKeeper 提供的功能特性加以封裝來實現的,下面我們會簡要分析下這兩種鎖方案的處理流程以及它們各自的問題。基于 Redis 實現的鎖服務 加鎖流程SET resource_name my_random_val

3、ue NX PX max-lock-time注:資源不存在時才能夠成功執行 set 操作,用于保證鎖持有者的唯一性;同時設置過期時間用于防止死鎖;記錄鎖的持有者,用于防止解鎖時解掉了不符合預期的鎖。解鎖流程if redis.get(resource_name) = my_random_valuereturn redis.del(resource_name)else return 0注:使用 Lua 腳本保證獲取鎖的所有者、對比解鎖者是否所有者、解鎖是一個原子操作。該方案的問題在于:通過過期時間來避免死鎖,過期時間設置多長對業務來說往往比較頭疼,時間短了可能會造成:持有鎖的線程 A 任務還未處理

4、完成,鎖過期了,線程 B 獲得了鎖,導致同一個資源被 A、B 兩個線程并發訪問;時間長了會造成:持有鎖的進程宕機,造成其他等待獲取鎖的進程長時間的無效等待。Redis 的主從異步復制機制可能丟失數據,會出現如下場景:A 線程獲得了鎖,但鎖數據還未同步到 slave 上,master 掛了,slave 頂成主,線程 B 嘗試加鎖,仍然能夠成功,造成 A、B 兩個線程并發訪問同一個資源。基于ZooKeeper 實現的鎖服務 加鎖流程首先在/resource_name節點下創建臨時有序節點 。獲取當前線程創建的節點及 /resource_name 目錄下的所有子節點,確定當前節點序號是否最小,是則加

5、鎖成功。否則監聽序號較小的前一個節點。注:ZAB 一致性協議保證了鎖數據的安全性,不會因為數據丟失造成多個鎖持有者;心跳保活機制解決死鎖問題,防止由于進程掛掉或者僵死導致的鎖長時間被無效占用。具備阻塞鎖特性,并通過 Watch 機制能夠及時從阻塞狀態被喚醒。解鎖流程是刪除當前線程創建的臨時接點。該方案的問題在于通過心跳保活機制解決死鎖會造成鎖的不安全性,可能會出現如下場景:持有鎖的線程 A 僵死或網絡故障,導致服務端長時間收不到來自客戶端的保活心跳,服務端認為客戶端進程不存活主動釋放鎖,線程 B 搶到鎖,線程 A 恢復,同時有兩個線程訪問共享資源。基于上訴對現有鎖方案的討論,我們能看到,一個理

6、想的鎖設計目標主要應該解決如下問題:鎖數據本身的安全性。不發生死鎖。不會有多個線程同時持有相同的鎖。而為了實現不發生死鎖的目標,又需要引入一種機制,當持有鎖的進程因為宕機、GC 活者網絡故障等各種原因無法主動過釋放鎖時,能夠有其他手段釋放掉鎖,主流的做法有兩種:鎖設置過期時間,過期之后 Server 端自動釋放鎖。對鎖的持有進程進行探活,發現持鎖進程不存活時 Server 端自動釋放。實際上不管采用哪種方式,都可能造成鎖的安全性被破壞,導致多個線程同時持有同一把鎖的情況出現。因此我們認為鎖設計方案應在預防死鎖和鎖的安全性上取得平衡,沒有一種方案能夠絕對意義上保證不發生死鎖并且是安全的。而鎖一般

7、的用途又可以分為兩種,實際應用場景下,需要根據具體需求出發,權衡各種因素,選擇合適的鎖服務實現模型。無論選擇哪一種模型,需要我們清楚地知道它在安全性上有哪些不足,以及它會帶來什么后果。為了效率,主要是避免一件事被重復的做多次,用于節省 IT 成本,即使鎖偶然失效,也不會造成數據錯誤,該種情況首要考慮的是如何防止死鎖。為了正確性,在任何情況下都要保證共享資源的互斥訪問,一旦發生就意味著數據可能不一致,造成嚴重的后果,該種情況首要考慮的是如何保證鎖的安全。下面主要介紹一下 SharkLock 的一些設計選擇。鎖信息設計如下lockBy:Client 唯一標識。condition:Client 在加

8、鎖時傳給 Server,用于定義 Client 期望 Server 的行為方式。lockTime:加鎖時間。txID:全局自增 ID。lease:租約。如何保證鎖數據的可靠性 SharkLock 底層存儲使用的是 SharkStore,SharkStore 是一個分布式的持久化 Key-Value 存儲系統。采用多副本來保證數據安全,同時使用 raft 來保證各個副本之間的數據一致性。如何預防死鎖 Client 定時向 Server 發送心跳包,Server 收到心跳包之后,維護 Server 端 Session 并立即回復,Client 收到心跳包響應后,維護 Client 端 Sessio

9、n。心跳包同時承擔了延長 Session 租約的功能。當鎖持有方發生故障時,Server 會在 Session 租約到期后,自動刪除該 Client 持有的鎖,以避免鎖長時間無法釋放而導致死鎖。Client 會在 Session 租約到期后,進行回調,可選擇性的決策是否要結束對當前持有資源的訪問。對于未設置過期的鎖,也就意味著無法通過租約自動釋放故障 Client 持有的鎖。因此額外提供了一種協商機制,在加鎖的時候傳遞一些 condition 到服務端,用于約定 Client 端期望 Server 端對異常情況的處理,包括什么情況下能夠釋放鎖。譬如可以通過這種機制實現 Server 端在未收到

10、十個心跳請求后自動釋放鎖,Client 端在未收到五個心跳響應后主動結束對共享資源的訪問。盡最大程度保證鎖被加鎖進程主動釋放。進程正常關閉時調用鉤子來嘗試釋放鎖未釋放的鎖信息寫文件,進程重啟后讀取鎖信息,并嘗試釋放鎖。如何確保鎖的安全性 1. 盡量不打破誰加鎖誰解鎖的約束,盡最大程度保證鎖被加鎖進程主動釋放。a)進程正常關閉時調用鉤子來嘗試釋放鎖。b)未釋放的鎖信息寫文件,進程重啟后讀取鎖信息,并嘗試釋放鎖。2. 依靠自動續約來維持鎖的持有狀態,在正常情況下,客戶端可以持有鎖任意長的時間,這可以確保它做完所有需要的資源訪問操作之后再釋放鎖。一定程度上防止如下情況發生。a)線程 A 獲取鎖,進行

11、資源訪問。b)鎖已經過期,但 A 線程未執行完成。c)線程 B 獲得了鎖,導致同時有兩個線程在訪問共享資源。3. 提供一種安全檢測機制,用于對安全性要求極高的業務場景。a)對于同一把鎖,每一次獲取鎖操作,都會得到一個全局增長的版本號。b)對外暴露檢測 API checkVersion(lock_name,version),用于檢測持鎖進程的鎖是不是已經被其他進程搶占(鎖已經有了更新的版本號)。c)加鎖成功的客戶端與后端資源服務器通信的時候可帶上版本號,后端資源服務器處理請求前,調用 checkVersion 去檢查鎖是否依然有效。有效則認為此客戶端依舊是鎖的持有者,可以為其提供服務。d)該機制

12、能在一定程度上解決持鎖 A 線程發生故障,Server 主動釋放鎖,線程 B 獲取鎖成功,A 恢復了認為自己仍舊持有鎖而發起修改資源的請求,會因為鎖的版本號已經過期而失敗,從而保障了鎖的安全性。下面對 SharkLock 依賴的 SharkStore 做一個簡單的介紹。SharkStore 基本模塊 Master Server 集群分片路由等元數據管理、擴容和 Failover 調度等。Data Server 數據存儲節點,提供 RPC 服務訪問其上的 KV 數據。Gateway Server 網關節點,負責用戶接入。Sharding SharkStore 采用多副本的形式來保證數據的可靠性和

13、高可用。同一份數據會存儲多份,少于一半的副本宕機仍然可以正常服務。 SharkStore 的數據分布如下圖所示。擴容方案 當某個分片的大小到達一定閾值,就會觸發分裂操作,由一個分片變成兩個,以達到擴容的目的。Dataserver 上 range 的 leader 自己觸發。 leader 維持寫入操作字節計數,每到達 check size 大小,就異步遍歷其負責范圍內的數據,計算大小并同時找出分裂時的中間 key 如果大小到達 split size,向 master 發起 AskSplit 請求,同意后提交一個分裂命令。分裂命令也會通過 raft 復制到其他副本。本地分裂。分裂是一個本地操作,

14、在本地新建一個 range,把原始 range 的部分數據劃撥給新 range,原始 range 仍然保留,只是負責的范圍減半。分裂是一個輕量級的操作。Failover 方案 failover 以 range 的級別進行。range 的 leader 定時向其他副本發送心跳,一段時間內收不到副本的心跳回應,就判斷副本宕機,通過 range 心跳上報給 master。由 master 發起 failover 調度。 Master 會先刪除宕機的副本然后選擇一個合適的新節點,添加到 range 組內之后通過 raft 復制協議來完成新節點的數據同步。Balance 方案 dataserver 上的

15、 range leader 會通過 range 心跳上報一些信息,每個 dataserver 還會有一個節點級別的 Node 心跳。 Master 收集這些信息來執行 balance 操作。Balance 通過在流量低的節點上增加副本,流量高的節點上減少副本促使整個集群比較均衡,維護集群的穩定和性能。Raft實踐: MultiRaft 1. 心跳合并以目標 dataserver 為維度,合并 dataserver 上所有 Raft 心跳 心跳只攜帶 range ids,心跳只用來維護 leader 的權威和副本健康檢測 range ids 的壓縮,比如差量 + 整型變長 Leader 類似跟蹤

16、復制進度,跟蹤 follower commit 位置。2. 快照管理控制建立 ACK 機制,在對端處理能力之內發送快照 ; 控制發送和應用快照的并發度,以及限速 ; 減少對正常業務的沖擊。Raft 實踐 -PreVote。Raft 算法中,leader 收到來自其他成員 term 比較高的投票請求會退位變成 follower因此,在節點分區后重加入、網絡閃斷等異常情況下,日志進度落后的副本發起選舉,但其本身并無法被選舉為 leader,導致集群在若干個心跳內丟失 leader,造成性能波動 ;針對這種情況,在 raft 作者的博士論文中,提出了 prevote 算法: 在發起選舉前,先進行一次

17、預選舉 Pre-Candidate, 如果預選舉時能得到大多數的投票,再增加 term,進行正常的選舉。prevote 會導致選舉時間變長 (多了一輪 RPC),然而這個影響在實踐中是非常小的, 可以有利于集群的穩定,是非常值得的實踐。Raft 實踐: NonVoter 一個新的 raft 成員加入后,其日志進度為空 ; 新成員的加入可能會導致 quorum 增加,并且同時引入了一個進度異常的副本 ; 新成員在跟上 leader 日志進度之前,新寫入的日志都無法復制給它 ; 如果此時再有原集群內一個成員宕機, 很有可能導致集群內可寫副本數到不到 quorum,使得集群變得不可寫。 很多 raft 的實現中,都會引入了一種特殊身份的 raft 成員 (non-voting 或者 lea

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論