C#實(shí)現(xiàn)Redis的分布式鎖
Redis實(shí)現(xiàn)分布式鎖(悲觀鎖/樂觀鎖)
對鎖的概念和應(yīng)用場景在此就不闡述了,網(wǎng)上搜索有很多解釋,只是我搜索到的使用C#利用Redis的SetNX命令實(shí)現(xiàn)的鎖雖然能用,但是都不太適合我需要的場景。
Redis有三個最基本屬性來保證分布式鎖的有效實(shí)現(xiàn):
- 安全性: 互斥,在任何時候,只有一個客戶端能持有鎖。
- 活躍性A:沒有死鎖,即使客戶端在持有鎖的時候崩潰,最后也會有其他客戶端能獲得鎖,超時機(jī)制。
- 活躍性B:故障容忍,只有大多數(shù)Redis節(jié)點(diǎn)時存活的,客戶端仍可以獲得鎖和釋放鎖。
基于ServiceStack.Redis寫了一個幫助類
Redis連接池
public static PooledRedisClientManager RedisClientPool = CreateManager();
private static PooledRedisClientManager CreateManager()
{
var redisHosts = System.Configuration.ConfigurationManager.AppSettings["redisHosts"];
if (string.IsNullOrEmpty(redisHosts))
{
throw new Exception("AppSetting redisHosts no found");
}
string[] redisHostarr = redisHosts.Split(new string[] { ",", "," }, StringSplitOptions.RemoveEmptyEntries);
return new PooledRedisClientManager(redisHostarr, redisHostarr, new RedisClientManagerConfig
{
MaxWritePoolSize = 1000,
MaxReadPoolSize = 1000,
AutoStart = true,
DefaultDb = 0
});
}
使用Redis的SetNX命令實(shí)現(xiàn)加鎖,
/// <summary>
/// 加鎖
/// </summary>
/// <param name="key">鎖key</param>
/// <param name="selfMark">自己標(biāo)記</param>
/// <param name="lockExpirySeconds">鎖自動過期時間[默認(rèn)10](s)</param>
/// <param name="waitLockMilliseconds">等待鎖時間(ms)</param>
/// <returns></returns>
public static bool Lock(string key, out string selfMark, int lockExpirySeconds = 10, long waitLockMilliseconds = long.MaxValue)
{
DateTime begin = DateTime.Now;
selfMark = Guid.NewGuid().ToString("N");//自己標(biāo)記,釋放鎖時會用到,自己加的鎖除非過期否則只能自己打開
using (RedisClient redisClient = (RedisClient)RedisClientPool.GetClient())
{
string lockKey = "Lock:" + key;
while (true)
{
string script = string.Format("if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then redis.call('PEXPIRE',KEYS[1],{0}) return 1 else return 0 end", lockExpirySeconds * 1000);
//循環(huán)獲取取鎖
if (redisClient.ExecLuaAsInt(script, new[] { lockKey }, new[] { selfMark }) == 1)
{
return true;
}
//不等待鎖則返回
if (waitLockMilliseconds == 0)
{
break;
}
//超過等待時間,則不再等待
if ((DateTime.Now - begin).TotalMilliseconds >= waitLockMilliseconds)
{
break;
}
Thread.Sleep(100);
}
return false;
}
}
因?yàn)镾erviceStack.Redis提供的SetNX方法,并沒有提供設(shè)置過期時間的方法,對于加鎖業(yè)務(wù)又不能分開執(zhí)行(如果加鎖成功設(shè)置過期時間失敗導(dǎo)致的永久死鎖問題),所以就使用腳本實(shí)現(xiàn),解決了異常情況死鎖問題.
- 參數(shù)key:鎖的key
- 參數(shù)selfMark:在設(shè)置鎖的時候會產(chǎn)生一個自己的標(biāo)識,在釋放鎖的時候會用到,所謂解鈴還須系鈴人。防止鎖被誤釋放,導(dǎo)致鎖無效.
- 參數(shù)lockExpirySeconds:鎖的默認(rèn)過期時間,防止被永久死鎖.
- 參數(shù)waitLockMilliseconds:循環(huán)獲取鎖的等待時間.
如果設(shè)置為0,為樂觀鎖機(jī)制,獲取不到鎖,直接返回未獲取到鎖.
默認(rèn)值為long最大值,為悲觀鎖機(jī)制,約等于很多很多天,可以理解為一直等待.
釋放鎖
/// <summary>
/// 釋放鎖
/// </summary>
/// <param name="key">鎖key</param>
/// <param name="selfMark">自己標(biāo)記</param>
public void UnLock(string key, string selfMark)
{
using (RedisClient redisClient = (RedisClient)RedisClientPool.GetClient())
{
string lockKey = "Lock:" + key;
var script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisClient.ExecLuaAsString(script, new[] { lockKey }, new[] { selfMark });
}
}
參數(shù)key:鎖的key
參數(shù)selfMark:在設(shè)置鎖的時候返回的自己標(biāo)識,用來解鎖自己加的鎖(此值不能隨意傳,必須是加鎖時返回的值)
調(diào)用方式
悲觀鎖方式
int num = 10;
string lockkey = "xianseng";
//悲觀鎖開啟20個人同時拿寶貝
for (int i = 0; i < 20; i++)
{
Task.Run(() =>
{
string selfmark = "";
try
{
if (PublicLockHelper.Lock(lockkey, out selfmark))
{
if (num > 0)
{
num--;
Console.WriteLine($"我拿到了寶貝:寶貝剩余{num}個\t\t{selfmark}");
}
else
{
Console.WriteLine("寶貝已經(jīng)沒有了");
}
Thread.Sleep(100);
}
}
finally
{
PublicLockHelper.UnLock(lockkey, selfmark);
}
});
}
樂觀鎖方式
int num = 10;
string lockkey = "xianseng";
//樂觀鎖開啟10個線程,每個線程拿5次
for (int i = 0; i < 10; i++)
{
Task.Run(() =>
{
for (int j = 0; j < 5; j++)
{
string selfmark = "";
try
{
if (PublicLockHelper.Lock(lockkey, out selfmark, 10, 0))
{
if (num > 0)
{
num--;
Console.WriteLine($"我拿到了寶貝:寶貝剩余{num}個\t\t{selfmark}");
}
else
{
Console.WriteLine("寶貝已經(jīng)沒有了");
}
Thread.Sleep(1000);
}
else
{
Console.WriteLine("沒有拿到,不想等了");
}
}
finally
{
PublicLockHelper.UnLock(lockkey, selfmark);
}
}
});
}
單機(jī)只能用多線模擬使用分布式鎖了
此鎖已經(jīng)可以滿足大多數(shù)場景了,若有不妥,還請多多指出,以免誤別人!
(次方案不支持Redis集群,Redis集群不能調(diào)用腳本執(zhí)行)
到此這篇關(guān)于C#實(shí)現(xiàn)Redis的分布式鎖的文章就介紹到這了,更多相關(guān)C# Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于C#實(shí)現(xiàn)Json轉(zhuǎn)Lua的操作指南
JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式,它的語法基于 JavaScript 對象表示法,簡單、易讀,同時被許多編程語言支持,本文給大家介紹了如何基于C#實(shí)現(xiàn)Json轉(zhuǎn)Lua的操作指南,需要的朋友可以參考下2024-12-12
C# XML與Json之間相互轉(zhuǎn)換實(shí)例詳解
這篇文章主要介紹了C# XML與Json之間相互轉(zhuǎn)換實(shí)例詳解,大家參考使用吧2013-11-11
利用C#實(shí)現(xiàn)批量圖片格式轉(zhuǎn)換功能
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)批量圖片格式轉(zhuǎn)換功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12
WPF實(shí)現(xiàn)曲線數(shù)據(jù)展示
這篇文章將以動數(shù)據(jù)分析為例為大家詳細(xì)介紹wpf實(shí)現(xiàn)曲線數(shù)據(jù)展示與函數(shù)曲線展示的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下2024-12-12
C#獲取ListView鼠標(biāo)下的Item實(shí)例
下面小編就為大家?guī)硪黄狢#獲取ListView鼠標(biāo)下的Item實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01
C#數(shù)據(jù)結(jié)構(gòu)與算法揭秘二
上文對數(shù)據(jù)結(jié)構(gòu)與算法,有了一個簡單的概述與介紹,這篇文章,我們介紹一中典型數(shù)據(jù)結(jié)構(gòu)——線性結(jié)構(gòu)2012-10-10
Windows下C#的GUI窗口程序中實(shí)現(xiàn)調(diào)用Google Map的實(shí)例
這篇文章主要介紹了Windows下C#的GUI窗口程序中實(shí)現(xiàn)調(diào)用Google Map的實(shí)例,如果只想調(diào)用瀏覽器打開網(wǎng)頁的話可以看文章最后的方法,需要的朋友可以參考下2016-04-04

