ZooKeeper的應用場景(分布式鎖、分布式隊列)

7 分布式鎖

分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要通過一些互斥手段來防止彼此之間的干擾,以保證一致性,在這種情況下,就需要使用分布式鎖了。

在平時的實際項目開發中,我們往往很少會去在意分布式鎖,而是依賴于關系型數據庫固有的排他性來實現不同進程之間的互斥。這確實是一種非常簡便且被廣泛使用的分布式鎖實現方式。然而有一個不爭的事實是,目前絕大多數大型分布式系統的性能瓶頸都集中在數據庫操作上。因此,如果上層業務再給數據庫添加一些額外的鎖,例如行鎖、表鎖甚至是繁重的事務處理,那么是不是會讓數據庫更加不堪重負呢?下面我們來看看使用ZooKeeper如何實現分布式鎖,這里主要講解排他鎖和共享鎖兩類分布式鎖。

排他鎖

排他鎖(Exclusive Locks,簡稱X鎖),又稱為寫鎖或獨占鎖,是一種基本的鎖類型。

如果事務T1對數據對象O1加上了排他鎖,那么在整個加鎖期間,只允許事務T1對O1進行讀取和更新操作,其他任何事務都不能再對這個數據對象進行任何類型的操作——直到T1釋放了排他鎖。

從上面講解的排他鎖的基本概念中,我們可以看到,排他鎖的核心是如何保證當前有且僅有一個事務獲得鎖,并且鎖被釋放后,所有正在等待獲取鎖的事務都能夠被通知到。

下面我們就來看看如何借助ZooKeeper實現排他鎖。

定義鎖

在通常的Java開發編程中,有兩種常見的方式可以用來定義鎖,分別是synchronized機制和JDK5提供的ReentrantLock。然而,在ZooKeeper中,沒有類似于這樣的API可以直接使用,而是通過ZooKeeper上的數據節點來表示一個鎖,例如/exclusive_lock/lock節點就可以被定義為一個鎖,如下圖所示。

獲取鎖

在需要獲取排他鎖時,所有的客戶端都會試圖通過調用create()接口,在/exclusive_lock 節點下創建臨時子節點/exclusive_lock/lock。在前面幾節中我們也介紹了,ZooKeeper會保證在所有的客戶端中,最終只有一個客戶端能夠創建成功,那么就可以認為該客戶端獲取了鎖。同時,所有沒有獲取到鎖的客戶端就需要到/exclusive_lock節點上注冊一個子節點變更的Watcher監聽,以便實時監聽到lock節點的變更情況。

釋放鎖

在“定義鎖”部分,我們已經提到,/exclusive_lock/lock是一個臨時節點,因此在以下兩種情況下,都有可能釋放鎖。

(1)當前獲取鎖的客戶端機器發生宕機,那么ZooKeeper上的這個臨時節點就會被移除。

(2)正常執行完業務邏輯后,客戶端就會主動將自己創建的臨時節點刪除。

無論在什么情況下移除了lock 節點,ZooKeeper都會通知所有在/exclusive_lock節點上注冊了子節點變更Watcher監聽的客戶端。這些客戶端在接收到通知后,再次重新發起分布式鎖獲取,即重復“獲取鎖”過程。整個排他鎖的獲取和釋放流程,可以用下圖來表示。

共享鎖

共享鎖(Shared Locks,簡稱S鎖),又稱為讀鎖,同樣是一種基本的鎖類型。如果事務T1對數據對象O1,加上了共享鎖,那么當前事務只能對O1進行讀取操作,其他事務也只能對這個數據對象加共享鎖一直到該數據對象上的所有共享鎖都被釋放。

共享鎖和排他鎖最根本的區別在于,加上排他鎖后,數據對象只對一個事務可見,而加上共享鎖后,數據對所有事務都可見。下面我們就來看看如何借助ZooKeeper來實現共享鎖。

定義鎖

和排他鎖一樣,同樣是通過ZooKeeper上的數據節點來表示一個鎖,是一個類似于“/shared_lock/[Hostname]-請求類型-序號”的臨時順序節點,例如/shared_lock/192.168.0.1-0000000001,那么,這個節點就代表了一個共享鎖。

獲取鎖

在需要獲取共享鎖時,所有客戶端都會到/shared_lock這個節點下面創建一個臨時順序節點,如果當前是讀請求,那么就創建例如/shared_lock/192. 168.0.1-0000000001的節點;如果是寫請求,那么就創建例如/shared_lock/192.168.0. I-W-000000001的節點。

判斷讀寫順序

根據共享鎖的定義,不同的事務都可以同時對同一個數據對象進行讀取操作,而更新操作必須在當前沒有任何事務進行讀寫操作的情況下進行。基于這個原則,我們來看看如何通過ZooKeeper的節點來確定分布式讀寫順序,大致可以分為如下4個步驟。

(1)創建完節點后,獲取/shared_lock節點下的所有子節點,并對該節點注冊子節點變更的Watcher監聽。

(2)確定自己的節點序號在所有子節點中的順序。

(3)對于讀請求:

如果沒有比自己序號小的子節點,或是所有比自己序號小的子節點都是讀請求,那么表明自己已經成功獲取到了共享鎖,同時開始執行讀取邏輯。

如果比自己序號小的子節點中有寫請求,那么就需要進入等待。

對于寫請求:

如果自己不是序號最小的子節點,那么就需要進入等待。

(4)接收到Watcher通知后,重復步驟1。

釋放鎖

釋放鎖的邏輯和排他鎖是一致的,這里不再贅述。整個共享鎖的獲取和釋放流程,可以用下圖來表示。

羊群效應

上面講解的這個共享鎖實現,大體上能夠滿足一般的分布式集群競爭鎖的需求,并且性能都還可以一這里說的一般場景是指集群規模不是特別大,一般是在10臺機器以內。

但是如果機器規模擴大之后,會有什么問題呢?我們著重來看上面“判斷讀寫順序”程的步驟3,結合下圖給出的實例,看看實際運行中的情況。

針對上圖中的實際情況,我們看看會發生什么事情。

(1)192.168.0.1這臺機器首先進行讀操作,完成讀操作后將節點/192.168.0.1-R-0000000001刪除。

(2)余下的4臺機器均收到了這個節點被移除的通知,然后重新從/shared_lock節點上獲取一份新的子節點列表。

(3)每個機器判斷自己的讀寫順序。其中192.168.0.2這臺機器檢測到自己已經是序號最小的機器了,于是開始進行寫操作,而余下的其他機器發現沒有輪到自己進行讀取或更新操作,于是繼續等待。

(4)繼續....

上面這個過程就是共享鎖在實際運行中最主要的步驟了,我們著重看下上面步驟3中提

到的:“而余下的其他機器發現沒有輪到自己進行讀取或更新操作,于是繼續等待。”很

明顯,我們看到,192.168.0.1 這個客戶端在移除自己的共享鎖后,ZooKeeper 發送了子

節點變更Watcher通知給所有機器,然而這個通知除了給192.168.0.2這臺機器產生實際

影響外,對于余下的其他所有機器都沒有任何作用。

相信讀者也已經意識到了,在這整個分布式鎖的競爭過程中,大量的“Watcher通知”和“子節點列表獲取”兩個操作重復運行,并且絕大多數的運行結果都是判斷出自己并非是序號最小的節點,從而繼續等待下一次通知——這個看起來顯然不怎么科學。客戶端無端地接收到過多和自己并不相關的事件通知,如果在集群規模比較大的情況下,不僅會對ZooKeeper服務器造成巨大的性能影響和網絡沖擊,更為嚴重的是,如果同一時間有多個節點對應的客戶端完成事務或是事務中斷引起節點消失,ZooKeeper服務器就會在短時間內向其余客戶端發送大量的事件通知一這就是所謂的羊群效應。

上面這個ZooKeeper分布式共享鎖實現中出現羊群效應的根源在于,沒有找準客戶端真正的關注點。我們再來回顧一下上面的分布式鎖競爭過程,它的核心邏輯在于:判斷自己是否是所有子節點中序號最小的。于是,很容易可以聯想到,每個節點對應的客戶端只需要關注比自己序號小的那個相關節點的變更情況就可以了一而不需要關注全局的子列表變更情況。

改進后的分布式鎖實現

現在我們來看看如何改進上面的分布式鎖實現。首先,我們需要肯定的一點是,上面提到的共享鎖實現,從整體思路上來說完全正確。這里主要的改動在于:每個鎖競爭者,只需要關注/shared.lock節點下序號比自己小的那個節點是否存在即可,具體實現如下。

(1)客戶端調用create()方法創建一個類似于“/shared_ lock/[Hostname]請求類型-序號”的臨時順序節點。

(2)客戶端調用getChildren()接口來獲取所有已經創建的子節點列表,注意,這里不注冊任何Watcher。

(3)如果無法獲取共享鎖,那么就調用exist()來對比自己小的那個節點注冊Watcher。

注意,這里“比自己小的節點”只是一個籠統的說法,具體對于讀請求和寫請求不一樣。

讀請求:向比自己序號小的最后一個寫請求節點注冊Watcher監聽。

寫請求:向比自己序號小的最后一個節點注冊Watcher 監聽。

(4)等待Watcher通知,繼續進入步驟2。

改進后的分布式鎖流程如下圖所示。

8 分布式隊列

業界有不少分布式隊列產品,不過絕大多數都是類似于ActiveMQ、Metamorphosis、Kafka和HornetQ等的消息中間件(或稱為消息隊列)。在本文中,我們主要介紹基于ZooKeeper實現的分布式隊列。分布式隊列,簡單地講分為兩大類,一種是常規的先入先出隊列,另一種則是要等到隊列元素集聚之后才統一安排執行的Barrier模型。

FIFO:先入先出

FIFO (First Input First Output,先入先出)的算法思想,以其簡單明了的特點,廣泛應用于計算機科學的各個方面。而FIFO隊列也是一種非常典型且應用廣泛的按序執行的隊列模型:先進入隊列的請求操作先完成后,才會開始處理后面的請求。

使用ZooKeeper實現FIFO隊列,和前面共享鎖的實現非常類似。FIFO 隊列就類似于一個全寫的共享鎖模型,大體的設計思路其實非常簡單:所有客戶端都會到/queue_fifo這個節點下面創建一個臨時順序節點,例如/queue. fifo/192. 168.0.1-0000000001。

創建完節點之后,根據如下4個步驟來確定執行順序。

(1)通過調用getChildren()接口來獲取/queue_fifo節點下的所有子節點,即獲取隊列中所有的元素。

(2)確定自己的節點序號在所有子節點中的順序。

(3)如果自己不是序號最小的子節點,那么就需要進入等待,同時向比自己序號小的最后一個節點注冊Watcher監聽。

(4)接收到Watcher通知后,重復步驟1。

整個FIFO隊列的工作流程,可以用下圖來表示。

Barrier:分布式屏障

Barrier原意是指障礙物、屏障,而在分布式系統中,特指系統之間的一個協調條件,規定了一個隊列的元素必須都集聚后才能統一進行安排, 否則一直等待。這往往出現在那些大規模分布式并行計算的應用場景上:最終的合并計算需要基于很多并行計算的子結果來進行。這些隊列其實是在FIFO 隊列的基礎上進行了增強,大致的設計思想如下:開始時,/queue_barrier節點是一個已經存在的默認節點,并且將其節點的數據內容賦值為一個數字n來代表Barrier 值,例如n=10表示只有當/queue_barrier節點下的子節點個數達到10后,才會打開Barrier。之后,所有的客戶端都會到/queue_barrier節點下創建一個臨時節點,例如/queue_barrier/192.168.0.1。

創建完節點之后,根據如下5個步驟來確定執行順序。

(1)通過調用getData()接口獲取/queue_barrier節點的數據內容:10。

(2)通過調用getChildren()接口獲取/queue_barrier節點下的所有子節點,即獲取隊列中的所有元素,同時注冊對子節點列表變更的Watcher監聽。

(3)統計子節點的個數。

(4)如果子節點個數還不足10個,那么就需要進入等待。

(5)接收到Watcher通知后,重復步驟2。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41642.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41642.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41642.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

島嶼的最大面積(力扣)遞歸 JAVA

給你一個大小為 m x n 的二進制矩陣 grid 。 島嶼 是由一些相鄰的 1 (代表土地) 構成的組合,這里的「相鄰」要求兩個 1 必須在 水平或者豎直的四個方向上 相鄰。你可以假設 grid 的四個邊緣都被 0(代表水)包圍著。 島嶼的面積是島上值為 1 的…

error_Network Error

此頁面為訂單列表,是混合開發(頁面嵌入在客戶端中) 此頁面為訂單列表,此需求在開發時后端先將代碼發布在測試環境,我在本地調試時調用的后端接口進行聯調沒有任何問題。 此后我將代碼發布在測試環境,在app中打開頁面&#xff0c…

vue echarts中按鈕點擊后修改值 watch數據變化后刷新圖表

1 點擊按鈕 {feature: {myBtn1: {show: true,title: 反轉Y軸,showTitle: true,icon: path://M512 0A512 512 0 1 0 512 1024A512 512 0 0 0 512 0M320 320V192h384v128zM128 416V288h256v128zM320 704V576h384v128zM128 800V672h256v128z,onclick: () > {dataSetting.rever…

nginx服務器報錯502 Bad Gateway的原因以及解決辦法

服務器報錯nginx 502 Bad Gateway的原因以及解決辦法_502 bad gateway nginx_主題模板站的博客-CSDN博客

C++學習筆記總結練習:effective 學習日志

準則 1.少使用define define所定義的常量會在預處理的時候被替代,出錯編譯器不容易找到錯誤。而且還沒有作用范圍限制,推薦使用constdefine宏定義的函數,容易出錯,而且參數需要加上小括號,推薦使用inline有的類中例如…

已經開源的中文大模型對比,支持更新

大模型下載:互鏈高科 ClueAI/PromptCLUE-base-v1-5 at main (huggingface.co) 支持多任務生成,支持中文,不支持多輪對話,體驗:ClueAI (cluebenchmarks.com) 基于promptclue-base進一步訓練的模型:ClueAI/Ch…

【C與C++的相互調用方法】

C與C的相互調用方法 C與C為什么相互調用的方式不同C中調用CC中調用C致謝 C與C為什么相互調用的方式不同 C 和 C 之間的相互調用方式存在區別,主要是由于 C 和 C 語言本身的設計和特性不同。 函數調用和參數傳遞方式不同:C 和 C 在函數調用和參數傳遞方面…

docker oracle linux命令執行sql

docker 安裝參照 https://blog.csdn.net/arcsin_/article/details/123707618 docker container ls -a命令查看容器名 打開容器 docker exec -it orcl19c_03 /bin/bashsys 用戶登錄容器 sqlplus / as sysdbashow pdbs;什么是pdb數據庫?什么是CDB? 參…

游戲如何防御DDOS流量攻擊呢,用游戲盾真的有用么?

針對在線游戲行業來說,DDoS(分布式拒絕服務)攻擊是一種極具破壞性的威脅。DDoS攻擊可能導致游戲服務器不可用,嚴重影響游戲體驗和運營。為了解決這一問題,游戲盾作為一種專門為游戲行業設計的安全解決方案,…

微信小程序 藍牙設備連接,控制開關燈

1.前言 微信小程序中連接藍牙設備,信息寫入流程 1、檢測當前使用設備(如自己的手機)是否支持藍牙/藍牙開啟狀態 wx:openBluetoothAdapter({}) 2、如藍牙已開啟狀態,檢查藍牙適配器的狀態 wx.getBluetoothAdapterState({}) 3、添加…

第十三章 SpringBoot項目(總)

1.創建SpringBoot項目 1.1.設置編碼 1.4.導入已有的spring boot項目 2.快速搭建Restfull風格的項目 2.1.返回字符串 RestController public class IndexController {RequestMapping("/demo1")public Object demo1() {System.out.println("demo1 ran...."…

kafka的位移

文章目錄 概要消費位移__consumer_offsets主題位移提交 概要 本文主要總結kafka的位移是如何管理的,在broker端如何通過命令行查看到位移信息,并從代碼層面總結了位移的提交方式。 消費位移 對于 Kafka 中的分區而言,它的每條消息都有唯一…

0基礎學習VR全景平臺篇 第86篇:智慧眼-為什么要設置分組選擇?

一、功能說明 分組選擇,也就是給全景的每個分組去設置其所屬的行政區劃,設置后只有屬于同行政區劃的成員才可進入其場景進行相關操作,更便于實現城市的精細化管理。 二、后臺編輯界面 分組名稱:場景的分組名稱。 對應分類&…

網絡安全--linux下Nginx安裝以及docker驗證標簽漏洞

目錄 一、Nginx安裝 二、docker驗證標簽漏洞 一、Nginx安裝 1.首先創建Nginx的目錄并進入: mkdir /soft && mkdir /soft/nginx/cd /soft/nginx/ 2.下載Nginx的安裝包,可以通過FTP工具上傳離線環境包,也可通過wget命令在線獲取安裝包…

【數據結構與算法】隊列

文章目錄 一:隊列1.1 隊列的概念1.2 隊列的介紹1.3 隊列示意圖 二:數組模擬隊列2.1 介紹2.2 思路2.3 代碼實現2.3.1 定義隊列基本信息2.3.2 初始化隊列2.3.3 判斷隊列是否滿,是否為空2.3.4 添加數據到隊列2.3.5 獲取隊列數據,出隊…

垃圾回收機制

什么是內存泄漏? 內存泄漏是指程序中已經不再使用的內存卻沒有被正確釋放或回收的情況。在編程中,當對象或數據不再被程序使用,但其所占用的內存空間沒有被垃圾回收機制回收,就會導致內存泄漏。 內存泄漏可能會導致程序的內存消…

圖數據庫_Neo4j和SpringBoot整合使用_創建節點_刪除節點_創建關系_使用CQL操作圖譜---Neo4j圖數據庫工作筆記0009

首先需要引入依賴 springboot提供了一個spring data neo4j來操作 neo4j 可以看到它的架構 這個是下載下來的jar包來看看 有很多cypher對吧 可以看到就是通過封裝的驅動來操作graph database 然后開始弄一下 首先添加依賴

【實用黑科技】如何 把b站的緩存視頻弄到本地——數據恢復軟件WinHex 和 音視頻轉碼程序FFmpeg

👨?💻個人主頁:元宇宙-秩沅 👨?💻 hallo 歡迎 點贊👍 收藏? 留言📝 加關注?! 👨?💻 本文由 秩沅 原創 👨?💻 收錄于專欄:效率…

onnxruntime 支持的所有后端

1 代碼導出 import onnxruntime as ort aaa ort.get_all_providers() print(aaa)1. 1 下面是ort支持的所有后端 TensorrtExecutionProvider, CUDAExecutionProvider, MIGraphXExecutionProvider, ROCMExecutionProvider, OpenVINOExecutionProvider, DnnlExecutionProvider…

Baumer工業相機堡盟工業相機如何通過BGAPISDK設置相機的固定幀率(C#)

Baumer工業相機堡盟工業相機如何通過BGAPI SDK設置相機的固定幀率(C#) Baumer工業相機Baumer工業相機的固定幀率功能的技術背景CameraExplorer如何查看相機固定幀率功能在BGAPI SDK里通過函數設置相機固定幀率 Baumer工業相機通過BGAPI SDK設置相機固定幀…