ZooKeeper 實(shí)現(xiàn)分布式鎖的方法示例
ZooKeeper 是一個(gè)典型的分布式數(shù)據(jù)一致性解決方案,分布式應(yīng)用程序可以基于 ZooKeeper 實(shí)現(xiàn)諸如數(shù)據(jù)發(fā)布/訂閱、負(fù)載均衡、分布式協(xié)調(diào)/通知、集群管理、Master 選舉、分布式鎖等功能。
節(jié)點(diǎn)
在介紹 ZooKeeper 分布式鎖前需要先了解一下 ZooKeeper 中節(jié)點(diǎn)(Znode),ZooKeeper 的數(shù)據(jù)存儲(chǔ)數(shù)據(jù)模型是一棵樹(shù)(Znode Tree),由斜杠(/)的進(jìn)行分割的路徑,就是一個(gè) Znode(如 /locks/my_lock)。每個(gè) Znode 上都會(huì)保存自己的數(shù)據(jù)內(nèi)容,同時(shí)還會(huì)保存一系列屬性信息。
Znode 又分為以下四種類型:
類型 | 描述 |
---|---|
持久節(jié)點(diǎn) | 節(jié)點(diǎn)創(chuàng)建后,會(huì)一直存在,不會(huì)因客戶端會(huì)話失效而刪除 |
持久順序節(jié)點(diǎn) | 基本特性與持久節(jié)點(diǎn)一致,創(chuàng)建節(jié)點(diǎn)的過(guò)程中,ZooKeeper 會(huì)在其名字后自動(dòng)追加一個(gè)單調(diào)增長(zhǎng)的數(shù)字后綴,作為新的節(jié)點(diǎn)名 |
臨時(shí)節(jié)點(diǎn) | 客戶端會(huì)話失效或連接關(guān)閉后,該節(jié)點(diǎn)會(huì)被自動(dòng)刪除 |
臨時(shí)順序節(jié)點(diǎn) | 基本特性與臨時(shí)節(jié)點(diǎn)一致,創(chuàng)建節(jié)點(diǎn)的過(guò)程中,ZooKeeper 會(huì)在其名字后自動(dòng)追加一個(gè)單調(diào)增長(zhǎng)的數(shù)字后綴,作為新的節(jié)點(diǎn)名 |
鎖原理
ZooKeeper 分布式鎖是基于 臨時(shí)順序節(jié)點(diǎn) 來(lái)實(shí)現(xiàn)的,鎖可理解為 ZooKeeper 上的一個(gè)節(jié)點(diǎn),當(dāng)需要獲取鎖時(shí),就在這個(gè)鎖節(jié)點(diǎn)下創(chuàng)建一個(gè)臨時(shí)順序節(jié)點(diǎn)。當(dāng)存在多個(gè)客戶端同時(shí)來(lái)獲取鎖,就按順序依次創(chuàng)建多個(gè)臨時(shí)順序節(jié)點(diǎn),但只有排列序號(hào)是第一的那個(gè)節(jié)點(diǎn)能獲取鎖成功,其他節(jié)點(diǎn)則按順序分別監(jiān)聽(tīng)前一個(gè)節(jié)點(diǎn)的變化,當(dāng)被監(jiān)聽(tīng)者釋放鎖時(shí),監(jiān)聽(tīng)者就可以馬上獲得鎖。
而且用臨時(shí)順序節(jié)點(diǎn)的另外一個(gè)用意是如果某個(gè)客戶端創(chuàng)建臨時(shí)順序節(jié)點(diǎn)后,自己意外宕機(jī)了也沒(méi)關(guān)系,ZooKeeper 感知到某個(gè)客戶端宕機(jī)后會(huì)自動(dòng)刪除對(duì)應(yīng)的臨時(shí)順序節(jié)點(diǎn),相當(dāng)于自動(dòng)釋放鎖。
如上圖:ClientA 和 ClientB 同時(shí)想獲取鎖,所以都在 locks 節(jié)點(diǎn)下創(chuàng)建了一個(gè)臨時(shí)節(jié)點(diǎn) 1 和 2,而 1 是當(dāng)前 locks 節(jié)點(diǎn)下排列序號(hào)第一的節(jié)點(diǎn),所以 ClientA 獲取鎖成功,而 ClientB 處于等待狀態(tài),這時(shí) ZooKeeper 中的 2 節(jié)點(diǎn)會(huì)監(jiān)聽(tīng) 1 節(jié)點(diǎn),當(dāng) 1節(jié)點(diǎn)鎖釋放(節(jié)點(diǎn)被刪除)時(shí),2 就變成了 locks 節(jié)點(diǎn)下排列序號(hào)第一的節(jié)點(diǎn),這樣 ClientB 就獲取鎖成功了。
代碼測(cè)試
請(qǐng)確保 ZooKeeper 服務(wù)已啟動(dòng),ZooKeeper 的搭建可參考Kafka 集群 中的 ZooKeeper 集群部分
以下是基于 C# 的測(cè)試,Java 可使用 Curator 框架,實(shí)現(xiàn)原理和上面描述是一致的,有興趣可以看看源碼,應(yīng)該也不難理解。
創(chuàng)建 .NET Core 控制臺(tái)程序 Nuget
創(chuàng)建 ZooKeeper Client
private const int CONNECTION_TIMEOUT = 50000; private const string CONNECTION_STRING = "127.0.0.1:2181"; private ZooKeeper CreateClient() { var zooKeeper = new ZooKeeper(CONNECTION_STRING, CONNECTION_TIMEOUT, NullWatcher.Instance); Stopwatch sw = new Stopwatch(); sw.Start(); while (sw.ElapsedMilliseconds < CONNECTION_TIMEOUT) { var state = zooKeeper.getState(); if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTING) { break; } } sw.Stop(); return zooKeeper; } class NullWatcher : Watcher { public static readonly NullWatcher Instance = new NullWatcher(); private NullWatcher() { } public override Task process(WatchedEvent @event) { return Task.CompletedTask; } }
添加 Lock 方法
/// <summary> /// 加鎖 /// </summary> /// <param name="key">加鎖的節(jié)點(diǎn)名</param> /// <param name="lockAcquiredAction">加鎖成功后需要執(zhí)行的邏輯</param> /// <param name="lockReleasedAction">鎖釋放后需要執(zhí)行的邏輯,可為空</param> /// <returns></returns> public async Task Lock(string key, Action lockAcquiredAction, Action lockReleasedAction = null) { // 獲取 ZooKeeper Client ZooKeeper keeper = CreateClient(); // 指定鎖節(jié)點(diǎn) WriteLock writeLock = new WriteLock(keeper, $"/{key}", null); var lockCallback = new LockCallback(() => { lockAcquiredAction.Invoke(); writeLock.unlock(); }, lockReleasedAction); // 綁定鎖獲取和釋放的監(jiān)聽(tīng)對(duì)象 writeLock.setLockListener(lockCallback); // 獲取鎖(獲取失敗時(shí)會(huì)監(jiān)聽(tīng)上一個(gè)臨時(shí)節(jié)點(diǎn)) await writeLock.Lock(); } class LockCallback : LockListener { private readonly Action _lockAcquiredAction; private readonly Action _lockReleasedAction; public LockCallback(Action lockAcquiredAction, Action lockReleasedAction) { _lockAcquiredAction = lockAcquiredAction; _lockReleasedAction = lockReleasedAction; } /// <summary> /// 獲取鎖成功回調(diào) /// </summary> /// <returns></returns> public Task lockAcquired() { _lockAcquiredAction?.Invoke(); return Task.FromResult(0); } /// <summary> /// 釋放鎖成功回調(diào) /// </summary> /// <returns></returns> public Task lockReleased() { _lockReleasedAction?.Invoke(); return Task.FromResult(0); } }
多線程模擬測(cè)試
static async Task RunAsync() { Parallel.For(1, 10, async (i) => { await new ZooKeeprDistributedLock().Lock("locks", () => { Console.WriteLine($"第{i}個(gè)請(qǐng)求,獲取鎖成功:{DateTime.Now},線程Id:{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); // 業(yè)務(wù)邏輯... }, () => { Console.WriteLine($"第{i}個(gè)請(qǐng)求,釋放鎖成功:{DateTime.Now},線程Id:{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("-------------------------------"); }); }); await Task.CompletedTask; }
雖然模擬的是多線程并行執(zhí)行,但最終都會(huì)依賴鎖的獲取和釋放而串行執(zhí)行實(shí)際業(yè)務(wù)邏輯。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
c# 重載WndProc,實(shí)現(xiàn)重寫(xiě)“最小化”的實(shí)現(xiàn)方法
在做“亦歌桌面版”的時(shí)候,發(fā)現(xiàn)當(dāng)打開(kāi)歌詞狀態(tài)下,用最小化隱藏窗體到托盤(pán)的話(如下code #1),在調(diào)出發(fā)現(xiàn)歌詞縮小了(雖然顯現(xiàn)的窗體大小跟剛才一樣),從這點(diǎn)看調(diào)用該方法其實(shí)窗體大小是改變了的(這個(gè)過(guò)程只是不可視而已)。2009-02-02C#+RedisSearch實(shí)現(xiàn)高性能全文搜索
Redis?Search是一個(gè)Redis模塊,它使用壓縮的倒排索引來(lái)實(shí)現(xiàn)快速的索引和低內(nèi)存占用,本文主要介紹了C#如何使用RedisSearch實(shí)現(xiàn)高性能全文搜索,希望對(duì)大家有所幫助2023-07-07C# Winform實(shí)現(xiàn)自定義漂亮的通知效果
這篇文章主要介紹了C# Winform實(shí)現(xiàn)自定義漂亮的通知效果,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08C# Xamarin利用ZXing.Net.Mobile進(jìn)行掃碼的方法
這篇文章主要介紹了C# Xamarin利用ZXing.Net.Mobile進(jìn)行掃碼的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06C#在驗(yàn)證文件共享模式下實(shí)現(xiàn)多線程文件寫(xiě)入
這篇文章主要為大家詳細(xì)介紹了C#在驗(yàn)證文件共享模式下實(shí)現(xiàn)多線程文件寫(xiě)入的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-01-01總結(jié)C#刪除字符串?dāng)?shù)組中空字符串的幾種方法
C#中要如何才能刪除一個(gè)字符串?dāng)?shù)組中的空字符串呢?下面的文章會(huì)介紹多種方式來(lái)實(shí)現(xiàn)清除數(shù)組中的空字符串,以及在.net中將字符串?dāng)?shù)組中字符串為空的元素去除。2016-08-08C#操作Windows服務(wù)類System.ServiceProcess.ServiceBase
這篇文章介紹了C#操作Windows服務(wù)類System.ServiceProcess.ServiceBase,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05