C# 實現(xiàn)Zookeeper分布式鎖的參考示例
分布式鎖
互聯(lián)網初期,我們系統(tǒng)一般都是單點部署,也就是在一臺服務器完成系統(tǒng)的部署,后期隨著用戶量的增加,服務器的壓力也越來越大,響應速度越來越慢,甚至出現(xiàn)服務器崩潰的情況。
為解決服務器壓力太大,響應慢的特點,分布式系統(tǒng)部署出現(xiàn)了。
簡單的說,就是我們將系統(tǒng)資源部署到多臺服務器中,然后使用一臺服務器做入口代理,根據(jù)一些決策將接收到的請求轉發(fā)到資源服務器,這也就是我們常說的 反向代理(一般就是使用nginx)

雖然分布式解決了服務器壓力的問題,但也帶來了新的問題。
比如,我們有一個下單統(tǒng)計的功能,當完成下單后,需要執(zhí)行統(tǒng)計功能,而在高訪問的情況下,可能有兩個下單請求(A和B)同時完成,然后一起執(zhí)行了統(tǒng)計功能,這樣可能導致的結果就是A請求未將B請求數(shù)據(jù)統(tǒng)計在內,而B請求可能也未將A請求數(shù)據(jù)統(tǒng)計在內,這樣就造成了數(shù)據(jù)的統(tǒng)計錯,這個問題的產生的根本原因就是統(tǒng)計功能的并發(fā)導致的,如果是單點部署的系統(tǒng),我們簡單的使用一個鎖操作就能完成了,但是在分布式環(huán)境下,A和B請求可能同時運行在兩個服務器中,普通的鎖就不能起到效果了,這個時候就要使用分布式鎖了。
Zookeeper分布式鎖原理
分布式鎖的實現(xiàn)發(fā)放有多種,簡單的,我們可以使用數(shù)據(jù)庫表去實現(xiàn)它,也可以使用redis去實現(xiàn)它,這里要使用的Zookeeper去實現(xiàn)分布式鎖
Zookeeper分布式鎖的原理是巧妙的是使用了znode臨時節(jié)點的特點和監(jiān)聽(watcher)機制,監(jiān)聽機制很簡單,就是我們可以給znode添加一個監(jiān)聽器,當znode節(jié)點狀態(tài)發(fā)生改變時(如:數(shù)據(jù)內容改變,節(jié)點被刪除),會通知到監(jiān)聽器。
前面幾節(jié)介紹過znode有三種類型
PERSISTENT:持久節(jié)點,即使在創(chuàng)建該特定znode的客戶端斷開連接后,持久節(jié)點仍然存在。默認情況下,除非另有說明,否則所有znode都是持久的。
EPHEMERAL:臨時節(jié)點,客戶端是連接狀態(tài)時,臨時節(jié)點就是有效的。當客戶端與ZooKeeper集合斷開連接時,臨時節(jié)點會自動刪除。臨時節(jié)點不允許有子節(jié)點。臨時節(jié)點在leader選舉中起著重要作用。
SEQUENTIAL:順序節(jié)點,可以是持久的或臨時的。當一個新的znode被創(chuàng)建為一個順序節(jié)點時,ZooKeeper通過將10位的序列號附加到原始名稱來設置znode的路徑,順序節(jié)點在鎖定和同步中起重要作用。
其中,順序節(jié)點,可以是持久的或臨時的,而臨時節(jié)點有個特點,就是它屬于創(chuàng)建它的那個會話,當會話斷開,臨時節(jié)點就會自動刪除,如果在臨時節(jié)點上注冊了監(jiān)聽器,那么監(jiān)聽器就會收到通知,如果臨時節(jié)點有了時間順序,那我們?yōu)閷崿F(xiàn)分布式鎖就又有一個想法:
假如在Zookeeper中有一個znode節(jié)點/Locker
1、當client1連接Zookeeper時,先判斷/Locker節(jié)點是否存在子節(jié)點,如果沒有子節(jié)點,那么會在/Locker節(jié)點下創(chuàng)建一個臨時順序的znode節(jié)點,假如是/client1,表示client1獲取了鎖狀態(tài),client1可以繼續(xù)執(zhí)行。
2、當client2連接Zookeeper時,先判斷/Locker節(jié)點是否存在子節(jié)點,發(fā)現(xiàn)已經存在子節(jié)點了,然后獲取/Locker下的所有子節(jié)點,同時按時間順序排序,在最后一個節(jié)點,也就是/client1節(jié)點上注冊一個監(jiān)聽器(watcher1),同時在/Locker節(jié)點下創(chuàng)建一個臨時順序的znode節(jié)點,假如是/client2。同時client2將被阻塞,而阻塞狀態(tài)的釋放是在監(jiān)聽器(watcher1)中的。
3、當client3連接Zookeeper時,先判斷/Locker節(jié)點是否存在子節(jié)點,發(fā)現(xiàn)已經存在子節(jié)點了,然后獲取/Locker下的所有子節(jié)點,同時按時間順序排序,在最后一個節(jié)點,也就是/client2節(jié)點上注冊一個監(jiān)聽器(watcher2),同時在/Locker節(jié)點下創(chuàng)建一個臨時順序的znode節(jié)點,假如是/client3。同時client2將被阻塞,而阻塞狀態(tài)的釋放是在監(jiān)聽器(watcher2)中的。
以此類推。
4、當client1執(zhí)行完操作了,斷開Zookeeper的連接,因為/client1是臨時順序節(jié)點,于是將會自動刪除,而client2已經往/client1節(jié)點中注冊了一個監(jiān)聽器(watcher1),于是watcher1將會受到通知,而watcher1又會釋放client2的阻塞狀態(tài)。于是client2獲取鎖狀態(tài),繼續(xù)執(zhí)行。
5、當client2執(zhí)行完操作了,斷開Zookeeper的連接,因為/client2是臨時順序節(jié)點,于是將會自動刪除,而client3已經往/client2節(jié)點中注冊了一個監(jiān)聽器(watcher2),于是watcher2將會受到通知,而watcher2又會釋放client3的阻塞狀態(tài)。于是client3獲取鎖狀態(tài),繼續(xù)執(zhí)行。
以此類推。
這樣,不管分布式環(huán)境中有幾臺服務器,都可以保證程序的排隊似的執(zhí)行了。
C#實現(xiàn)Zookeeper分布式鎖
上一節(jié)有封裝過一個ZookeeperHelper的輔助類(Zookeeper基礎教程(四):C#連接使用Zookeeper),使用這個輔助類實現(xiàn)了一個ZookeeperLocker類:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AspNetCore.ZookeeperConsole
{
/// <summary>
/// 基于Zookeeper的分布式鎖
/// </summary>
public class ZookeeperLocker : IDisposable
{
/// <summary>
/// 單點鎖
/// </summary>
static object locker = new object();
/// <summary>
/// Zookeeper集群地址
/// </summary>
string[] address;
/// <summary>
/// Zookeeper操作輔助類
/// </summary>
ZookeeperHelper zookeeperHelper;
/// <summary>
/// 構造函數(shù)
/// </summary>
/// <param name="lockerPath">分布式鎖的根路徑</param>
/// <param name="address">集群地址</param>
public ZookeeperLocker(string lockerPath, params string[] address) : this(lockerPath, 0, address)
{
}
/// <summary>
/// 構造函數(shù)
/// </summary>
/// <param name="lockerPath">分布式鎖的根路徑</param>
/// <param name="sessionTimeout">回話過期時間</param>
/// <param name="address">集群地址</param>
public ZookeeperLocker(string lockerPath, int sessionTimeout, params string[] address)
{
this.address = address.ToArray();
zookeeperHelper = new ZookeeperHelper(address, lockerPath);
if (sessionTimeout > 0)
{
zookeeperHelper.SessionTimeout = sessionTimeout;
}
if (!zookeeperHelper.Connect())
{
throw new Exception("connect failed:" + string.Join(",", address));
}
lock (locker)
{
if (!zookeeperHelper.Exists())//根節(jié)點不存在則創(chuàng)建
{
zookeeperHelper.SetData("", "", true);
}
}
}
/// <summary>
/// 生成一個鎖
/// </summary>
/// <returns>返回鎖名</returns>
public string CreateLock()
{
var path = Guid.NewGuid().ToString().Replace("-", "");
while (zookeeperHelper.Exists(path))
{
path = Guid.NewGuid().ToString().Replace("-", "");
}
return CreateLock(path);
}
/// <summary>
/// 使用指定的路徑名稱設置鎖
/// </summary>
/// <param name="path">鎖名,不能包含路徑分隔符(/)</param>
/// <returns>返回鎖名</returns>
public string CreateLock(string path)
{
if (path.Contains("/"))
{
throw new ArgumentException("invalid path");
}
return zookeeperHelper.SetData(path, "", false, true);
}
/// <summary>
/// 獲取鎖
/// </summary>
/// <param name="path">鎖名</param>
/// <returns>如果獲得鎖返回true,否則一直等待</returns>
public bool Lock(string path)
{
return LockAsync(path).GetAwaiter().GetResult();
}
/// <summary>
/// 獲取鎖
/// </summary>
/// <param name="path">鎖名</param>
/// <param name="millisecondsTimeout">超時時間,單位:毫秒</param>
/// <returns>如果獲得鎖返回true,否則等待指定時間后返回false</returns>
public bool Lock(string path, int millisecondsTimeout)
{
return LockAsync(path, millisecondsTimeout).GetAwaiter().GetResult();
}
/// <summary>
/// 異步獲取鎖等等
/// </summary>
/// <param name="path">鎖名</param>
/// <returns>如果獲得鎖返回true,否則一直等待</returns>
public async Task<bool> LockAsync(string path)
{
return await LockAsync(path, System.Threading.Timeout.Infinite);
}
/// <summary>
/// 異步獲取鎖等等
/// </summary>
/// <param name="path">鎖名</param>
/// <param name="millisecondsTimeout">超時時間,單位:毫秒</param>
/// <returns>如果獲得鎖返回true,否則等待指定時間后返回false</returns>
public async Task<bool> LockAsync(string path, int millisecondsTimeout)
{
var array = await zookeeperHelper.GetChildrenAsync("", true);
if (array != null && array.Length > 0)
{
var first = array.FirstOrDefault();
if (first == path)//正好是優(yōu)先級最高的,則獲得鎖
{
return true;
}
var index = array.ToList().IndexOf(path);
if (index > 0)
{
//否則添加監(jiān)聽
var are = new AutoResetEvent(false);
var watcher = new NodeWatcher();
watcher.NodeDeleted += (ze) =>
{
are.Set();
};
if (await zookeeperHelper.WatchAsync(array[index - 1], watcher))//監(jiān)聽順序節(jié)點中的前一個節(jié)點
{
if (!are.WaitOne(millisecondsTimeout))
{
return false;
}
}
are.Dispose();
}
else
{
throw new InvalidOperationException($"no locker found in path:{zookeeperHelper.CurrentPath}");
}
}
return true;
}
/// <summary>
/// 釋放資源
/// </summary>
public void Dispose()
{
zookeeperHelper.Dispose();
}
}
}
現(xiàn)在寫個程序可以模擬一下
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace AspNetCore.ZookeeperConsole
{
class Program
{
static void Main(string[] args)
{
//Zookeeper連接字符串,采用host:port格式,多個地址之間使用逗號(,)隔開
string[] address = new string[] { "192.168.209.133:2181", "192.168.209.133:2181", "192.168.209.133:2181" };
//會話超時時間,單位毫秒
int sessionTimeOut = 10000;
//鎖節(jié)點根路徑
string lockerPath = "/Locker";
for (var i = 0; i < 10; i++)
{
string client = "client" + i;
//多線程模擬并發(fā)
new Thread(() =>
{
using (ZookeeperLocker zookeeperLocker = new ZookeeperLocker(lockerPath, sessionTimeOut, address))
{
string path = zookeeperLocker.CreateLock();
if (zookeeperLocker.Lock(path))
{
//模擬處理過程
Console.WriteLine($"【{client}】獲得鎖:{DateTime.Now}");
Thread.Sleep(3000);
Console.WriteLine($"【{client}】處理完成:{DateTime.Now}");
}
else
{
Console.WriteLine($"【{client}】獲得鎖失敗:{DateTime.Now}");
}
}
}).Start();
}
Console.ReadKey();
}
}
}
運行結果如下:
可以發(fā)現(xiàn),鎖功能是實現(xiàn)了的
如果程序運行中打印日志:Client session timed out, have not heard from server in 8853ms for sessionid 0x1000000ec5500b2
或者直接拋出異常:org.apache.zookeeper.KeeperException.ConnectionLossException:“Exception_WasThrown”
只需要適當調整sessionTimeOut時間即可
以上就是C# 實現(xiàn)Zookeeper分布式鎖的參考示例的詳細內容,更多關于C# 實現(xiàn)Zookeeper分布式鎖的資料請關注腳本之家其它相關文章!
相關文章
C# WinForm調用net core實現(xiàn)文件上傳接口
這篇文章主要為大家詳細介紹了C# WinForm如何調用net core實現(xiàn)文件上傳接口,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-04-04
silverlight實現(xiàn)圖片局部放大效果的方法
這篇文章主要介紹了silverlight實現(xiàn)圖片局部放大效果的方法,結合實例形式分析了silverlight針對圖片屬性的相關操作技巧,需要的朋友可以參考下2017-03-03

