新聞中心
ZooKeeper實現(xiàn)分布式鎖的原理
作者: 劉進(jìn)坤 2021-02-28 07:49:28
大數(shù)據(jù)
分布式 相信大部分面試都是說用 Redis 去實現(xiàn)分布式鎖,用 Zookeeper 實現(xiàn)分布式鎖相對而言遇到的較少,最近在整理之前的面經(jīng)答案,因此特意寫篇博客解釋一下。

本文轉(zhuǎn)載自微信公眾號「菜鳥飛呀飛」,作者劉進(jìn)坤。轉(zhuǎn)載本文請聯(lián)系菜鳥飛呀飛公眾號。
前言
在面字節(jié)跳動時,遇到了這道面試題:如何用 Zookeeper 實現(xiàn)分布式鎖?
相信大部分面試都是說用 Redis 去實現(xiàn)分布式鎖,用 Zookeeper 實現(xiàn)分布式鎖相對而言遇到的較少,最近在整理之前的面經(jīng)答案,因此特意寫篇博客解釋一下。
實現(xiàn)一把分布式鎖通常有很多方法,比較常見的有 redis 和 Zookeeper。相信大家對 redis 實現(xiàn)分布式鎖已經(jīng)非常了解,今天介紹的是如何通過 Zookeeper 去實現(xiàn)一把分布式鎖。
首先 Zookeeper 為什么能實現(xiàn)一把分布式鎖呢?這是因為它有一個特性,就是多個線程去 Zookeeper 里面去創(chuàng)建同一個節(jié)點的時候,只會有一個線程去執(zhí)行成功。
Zookeeper 的 ZNode 節(jié)點
在了解 Zookeeper 實現(xiàn)分布式鎖之前,首先,我們需要了解 Zookeeper 里面節(jié)點相關(guān)的知識。
Zookeeper 里面的節(jié)點可以分為兩大類,一種是臨時節(jié)點,一種是持久化節(jié)點。
臨時節(jié)點,指的是節(jié)點創(chuàng)建后,如果創(chuàng)建節(jié)點的客戶端和 Zookeeper 服務(wù)端的會話失效(例如斷開連接),那么節(jié)點就會被刪除。
持久化節(jié)點指的是節(jié)點創(chuàng)建后,即使創(chuàng)建節(jié)點的客戶端和 Zookeeper 服務(wù)端的會話失效(例如斷開連接),節(jié)點也不會被刪除,只有客戶端主動發(fā)起刪除節(jié)點的請求,節(jié)點才會被刪除。
另外還有一種節(jié)點叫做有序節(jié)點,這種節(jié)點在創(chuàng)建時會有一個序號,這個序號是自增的。有序節(jié)點既可以是有序臨時節(jié)點,也可以是有序持久化節(jié)點。
Zookeeper 中所有的數(shù)據(jù)都是通過節(jié)點來存儲的,它的目錄結(jié)構(gòu)就像一個文件樹,如下圖。
Zookeeper結(jié)構(gòu)
圖中的 locks、register、data 這幾個目錄自定義創(chuàng)建的,分別用來存儲不同業(yè)務(wù)的數(shù)據(jù),例如 locks 用來存放分布式鎖相關(guān)的信息,register 用來存放注冊中心相關(guān)的數(shù)據(jù)。
現(xiàn)在我們要獲取一個分布式的所,那么假設(shè)這個鎖的 K 叫做 K1,那么現(xiàn)在有一個客戶端 a,然后去 JK 里面去創(chuàng)建一個分布式的,所創(chuàng)建 K1 這個分布式鎖,那么他就會在 nex 這個目錄下面創(chuàng)建一個叫做 K1 的文件夾,叫做 K1 的文件?,F(xiàn)在我們要獲取一個分布式的所,那么假設(shè)這個鎖的 K 叫做 K1,那么現(xiàn)在有一個客戶端 a,然后去 JK 里面去創(chuàng)建一個分布式的,所創(chuàng)建 K1 這個分布式鎖,那么他就會在 nex 這個目錄下面創(chuàng)建一個叫做 K1 的文件夾,叫做 K1 的文件。
如何實現(xiàn)
采用 Zookeeper 實現(xiàn)分布式鎖,有兩種方案:1. 基于臨時節(jié)點實現(xiàn);2. 基于臨時順序節(jié)點實現(xiàn)。下面以及介紹這種方案的實現(xiàn)原理。
首先,假設(shè)所有的分布式鎖都存儲在 locks 這個目錄中。
方案一:基于臨時節(jié)點實現(xiàn)(不推薦)
假設(shè)現(xiàn)在有客戶端 A、B、C 均來獲取同一把分布式鎖:Key1。
首先,客戶端 A 來獲取分布式鎖 Key1,那么它就會嘗試在 locks 這個目錄下去創(chuàng)建一個叫做 Key1 的 ZNode 節(jié)點。如果這個時候 locks 目錄里面沒有 Key1 這個 ZNode 節(jié)點,那么客戶端 A 就能成功創(chuàng)建 Key1 節(jié)點,這就表示客戶端 A 成功獲取到了 Key1 這把鎖鎖。
圖1
同時,客戶端 B 也來獲取 Key1 這把鎖。客戶端 B 也需要去 locks 這個目錄里面去創(chuàng)建 Key1 ZNode 節(jié)點,這個時候,由于 Key1 這個 ZNode 節(jié)點已經(jīng)存在,所以客戶端 B 就會創(chuàng)建失敗。而創(chuàng)建失敗就表示客戶端 B 獲取鎖失敗,所以這個時候客戶端 B 就會向 Zookeeper 注冊自己的監(jiān)聽器(Watcher),監(jiān)聽 Key1 這個 ZNode 節(jié)點的變化(當(dāng) Key1 節(jié)點發(fā)生變化時,Zookeeper 會通知到客戶端 B)。
如果客戶端 A 和客戶端 B,是同時請求到 Zookeeper,那么 Zookeeper 它有一個機制,它會保證只會有其中一個客戶端能創(chuàng)建成功 Key1 這個 ZNode 節(jié)點。
圖2
同理,此時客戶端 C 來獲取 Key1 鎖時,也是無法獲取到鎖,也會把自己的 Watcher 注冊到 ZK 中,監(jiān)聽 Key1 這個 ZNode 節(jié)點的變化。
當(dāng)客戶端 A 處理完自己的業(yè)務(wù)邏輯之后,那么就會執(zhí)行釋放鎖的操作。釋放鎖時,客戶端刪除 Key1 節(jié)點,如果節(jié)點刪除成功就表示鎖釋放成功。當(dāng) Key1 這個節(jié)點被刪除后,Zookeeper 就會通知所有監(jiān)聽 Key1 這個節(jié)點的客戶端,也就是客戶端 B、C。
當(dāng)客戶端 B 和 C 接到通知以后,知道 Key1 節(jié)點發(fā)生了變化,這個時候它們就會重新去請求 Zookeeper,嘗試在 locks 目錄下面創(chuàng)建 Key1 節(jié)點,這個時候也只會有一個客戶端能成功創(chuàng)建 Key1 節(jié)點。假如說是客戶端 B 創(chuàng)建成功了,那么就表示客戶端 B 成功獲取到了鎖.客戶端 C 獲取鎖失敗,那么就繼續(xù)去監(jiān)聽 Key1 這個節(jié)點的變化。
圖3
為什么不推薦
以上就是基于臨時節(jié)點這個方案去實現(xiàn) Zookeeper 分布式鎖,但是這個方案通常是不被推薦的。為什么呢?這是因為使用這個方案會存在一個很大的問題:羊群效應(yīng)。
什么意思呢?
從上面的過程中我們可以看到,當(dāng)客戶端 A 釋放鎖成功以后,Zookeeper 需要去通知所有監(jiān)聽 Key1 這個節(jié)點的客戶端。上面我們的例子中只有客戶端 B 和客戶端 C,但是在實際應(yīng)用中可能有成百上千個客戶端,甚至更多。Zookeeper 在這一瞬間需要發(fā)送成百上千個請求,首先這個效率顯然是不高的,另外當(dāng)分布式鎖的競爭較為激烈時,極有可能在這一瞬間 Zookeeper 的網(wǎng)卡可能被撐爆。而且系統(tǒng)中可能并不僅僅存 Key1 這一把鎖,還會存在 Key2、Key3、Key4...,這些鎖也會存在競爭,Zookeeper 的壓力會更大。
在這個過程中,我們很明顯地能感覺到這是不合理的,因為獲取分布式鎖時肯定是只有其中一個客戶端能獲取到,那么當(dāng) Key1 這個節(jié)點被刪除以后,需要通知其他的客戶端來獲取鎖,這個時候我們有必要去通知所有的客戶端嗎?
顯然是沒有必要的,我們只需要通知其中一個客戶端就可以了。因此方案二出現(xiàn)了。
方案二:基于臨時順序節(jié)點實現(xiàn)(推薦)
基于臨時順序節(jié)點去實現(xiàn)分布式鎖時,就不是在 Linux 這個目錄下面創(chuàng)建 Key1 這個臨時節(jié)點了。而是先在 locks 這個目錄下面創(chuàng)建一個 Key1 目錄,然后在 Key1 目錄里面去創(chuàng)建臨時順序節(jié)點。
假設(shè)現(xiàn)在客戶端 a 來獲取分布式鎖 Key1,那么這個時候客戶端 A 就會在 Key1 這個目錄里面創(chuàng)建一個臨時順序節(jié)點,這個臨時順序節(jié)點的序號是 001。
然后客戶端 A 會判斷自己創(chuàng)建的這個臨時順序節(jié)點 001 在 Key1 這個目錄里面,它的序號是不是最小的?如果是最小的,那么就表示客戶端 A 獲取鎖成功。
接著客戶端 B 也來獲取 Key1 這個分布式鎖,它也會在 Key1 這個目錄下面去創(chuàng)建一個臨時順序節(jié)點,由于這個時候自增序號已經(jīng)變?yōu)?002 了,因為之前已經(jīng)創(chuàng)建過 001 了,所以客戶端 B 會創(chuàng)建 002 這個臨時順序節(jié)點。
圖4
同理,客戶端 B 也會判斷自己當(dāng)前創(chuàng)建的臨時順序節(jié)點 002,是不是當(dāng)前 Key1 目錄中序號最小的臨時節(jié)點,顯然不是,因為前面有一個 001 臨時順序節(jié)點,所以客戶端 B 這個時候是獲取鎖失敗。
當(dāng)客戶端 B 獲取鎖失敗之后,它會把自己的監(jiān)聽器注冊到 Zookeeper,它監(jiān)聽的是它前面一個臨時順序節(jié)點,也就是 001 這個順序節(jié)點。
圖5
此時如果客戶端 C 也來獲取分布式鎖 Key1,這個時候它就會在 Key 目錄中創(chuàng)建臨時順序節(jié)點 003,同樣 003 也不是序號最小的臨時順序節(jié)點,所以客戶端 C 也獲取鎖失敗,接著它會去監(jiān)聽 002 這個臨時順序節(jié)點。
當(dāng)客戶端 A 處理完業(yè)務(wù)邏輯之后,它就會去釋放鎖。釋放鎖的操作就是去刪除 Key1 這個目錄下面客戶端 A 所創(chuàng)建的臨時順序節(jié)點,也就是刪除 001 這個臨時順序節(jié)點。當(dāng) 001 這個順序節(jié)點被刪除以后,Zookeeper 就會去通知監(jiān)聽 001 這個順序節(jié)點的所有客戶端,也就是通知客戶端 B??蛻舳?B 接收到 Zookeeper 的通知之后,它就會去判斷我當(dāng)前創(chuàng)建的臨時順序節(jié)點 002 是不是當(dāng)前 Key1 這個目錄中序號最小的一個臨時順序節(jié)點。此時由于 001 這個順序節(jié)點已經(jīng)不存在了,顯然 002 是最小的了,因此客戶端 B 就獲取鎖成功。
圖6
同樣當(dāng)客戶端 B 釋放鎖之后,就會將 002 刪除,002 刪除以后,Zookeeper 會通知客戶端 C,客戶端 C 發(fā)現(xiàn)我當(dāng)前創(chuàng)建的臨時順序節(jié)點 003 是 Key1 這個目錄里面最小的序號,所以客戶端 C 獲取鎖成功。
思考
當(dāng)客戶端 A 獲取鎖成功以后,長時間不釋放鎖,或者說客戶端 A 所在的機器宕機,或者客戶端 A 所在的機器出現(xiàn)網(wǎng)絡(luò)故障,這個時候會出現(xiàn)什么狀況?
當(dāng)客戶端 A 所在的機器出現(xiàn)宕機,或者出現(xiàn)網(wǎng)絡(luò)故障后,長時間不和 Zookeeper 通信的時候,客戶端 A 和 Zookeeper 之間創(chuàng)建的 Session 就會失效,當(dāng)這個 Session 失效以后,Zookeeper 會將客戶端 A 所創(chuàng)建的臨時順序節(jié)點給直接刪除,這個時候其他的客戶端就能正常獲取鎖了。
當(dāng)前文章:Zookeeper實現(xiàn)分布式鎖的原理
轉(zhuǎn)載源于:http://www.dlmjj.cn/article/cdgiepo.html


咨詢
建站咨詢
