C#實(shí)現(xiàn)Redis的分布式鎖
Redis實(shí)現(xiàn)分布式鎖(悲觀鎖/樂(lè)觀鎖)
對(duì)鎖的概念和應(yīng)用場(chǎng)景在此就不闡述了,網(wǎng)上搜索有很多解釋?zhuān)皇俏宜阉鞯降氖褂肅#利用Redis的SetNX命令實(shí)現(xiàn)的鎖雖然能用,但是都不太適合我需要的場(chǎng)景。
Redis有三個(gè)最基本屬性來(lái)保證分布式鎖的有效實(shí)現(xiàn):
- 安全性: 互斥,在任何時(shí)候,只有一個(gè)客戶(hù)端能持有鎖。
- 活躍性A:沒(méi)有死鎖,即使客戶(hù)端在持有鎖的時(shí)候崩潰,最后也會(huì)有其他客戶(hù)端能獲得鎖,超時(shí)機(jī)制。
- 活躍性B:故障容忍,只有大多數(shù)Redis節(jié)點(diǎn)時(shí)存活的,客戶(hù)端仍可以獲得鎖和釋放鎖。
基于ServiceStack.Redis寫(xiě)了一個(gè)幫助類(lèi)
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">鎖自動(dòng)過(guò)期時(shí)間[默認(rèn)10](s)</param> /// <param name="waitLockMilliseconds">等待鎖時(shí)間(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)記,釋放鎖時(shí)會(huì)用到,自己加的鎖除非過(guò)期否則只能自己打開(kāi) 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; } //超過(guò)等待時(shí)間,則不再等待 if ((DateTime.Now - begin).TotalMilliseconds >= waitLockMilliseconds) { break; } Thread.Sleep(100); } return false; } }
因?yàn)镾erviceStack.Redis提供的SetNX方法,并沒(méi)有提供設(shè)置過(guò)期時(shí)間的方法,對(duì)于加鎖業(yè)務(wù)又不能分開(kāi)執(zhí)行(如果加鎖成功設(shè)置過(guò)期時(shí)間失敗導(dǎo)致的永久死鎖問(wèn)題),所以就使用腳本實(shí)現(xiàn),解決了異常情況死鎖問(wèn)題.
- 參數(shù)key:鎖的key
- 參數(shù)selfMark:在設(shè)置鎖的時(shí)候會(huì)產(chǎn)生一個(gè)自己的標(biāo)識(shí),在釋放鎖的時(shí)候會(huì)用到,所謂解鈴還須系鈴人。防止鎖被誤釋放,導(dǎo)致鎖無(wú)效.
- 參數(shù)lockExpirySeconds:鎖的默認(rèn)過(guò)期時(shí)間,防止被永久死鎖.
- 參數(shù)waitLockMilliseconds:循環(huán)獲取鎖的等待時(shí)間.
如果設(shè)置為0,為樂(lè)觀鎖機(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è)置鎖的時(shí)候返回的自己標(biāo)識(shí),用來(lái)解鎖自己加的鎖(此值不能隨意傳,必須是加鎖時(shí)返回的值)
調(diào)用方式
悲觀鎖方式
int num = 10; string lockkey = "xianseng"; //悲觀鎖開(kāi)啟20個(gè)人同時(shí)拿寶貝 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}個(gè)\t\t{selfmark}"); } else { Console.WriteLine("寶貝已經(jīng)沒(méi)有了"); } Thread.Sleep(100); } } finally { PublicLockHelper.UnLock(lockkey, selfmark); } }); }
樂(lè)觀鎖方式
int num = 10; string lockkey = "xianseng"; //樂(lè)觀鎖開(kāi)啟10個(gè)線程,每個(gè)線程拿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}個(gè)\t\t{selfmark}"); } else { Console.WriteLine("寶貝已經(jīng)沒(méi)有了"); } Thread.Sleep(1000); } else { Console.WriteLine("沒(méi)有拿到,不想等了"); } } finally { PublicLockHelper.UnLock(lockkey, selfmark); } } }); }
單機(jī)只能用多線模擬使用分布式鎖了
此鎖已經(jīng)可以滿(mǎn)足大多數(shù)場(chǎng)景了,若有不妥,還請(qǐng)多多指出,以免誤別人!
(次方案不支持Redis集群,Redis集群不能調(diào)用腳本執(zhí)行)
到此這篇關(guān)于C#實(shí)現(xiàn)Redis的分布式鎖的文章就介紹到這了,更多相關(guān)C# Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#類(lèi)中的屬性使用總結(jié)(詳解類(lèi)的屬性)
屬性是一種類(lèi)的成員,它的實(shí)現(xiàn)類(lèi)似函數(shù),訪問(wèn)類(lèi)似字段。它的作用是提供一種靈活和安全的機(jī)制來(lái)訪問(wèn),修改私有字段。所以屬性必須依賴(lài)于字段2014-03-03C#語(yǔ)法糖(Csharp Syntactic sugar)大匯總
首先需要聲明的是“語(yǔ)法糖”這個(gè)詞絕非貶義詞,它可以給我?guī)?lái)方便,是一種便捷的寫(xiě)法,編譯器會(huì)幫我們做轉(zhuǎn)換;而且可以提高開(kāi)發(fā)編碼的效率,在性能上也不會(huì)帶來(lái)?yè)p失。這讓java開(kāi)發(fā)人員羨慕不已,呵呵。2010-06-06詳解C#的設(shè)計(jì)模式編程之抽象工廠模式的應(yīng)用
這篇文章主要介紹了C#的設(shè)計(jì)模式編程之抽象工廠模式的應(yīng)用,注意區(qū)分一下簡(jiǎn)單工廠模式、工廠方法模式和抽象工廠模式概念之間的區(qū)別,需要的朋友可以參考下2016-02-02C# 添加、修改和刪除PDF書(shū)簽的實(shí)例代碼
本篇文章主要介紹了C# 添加、修改和刪除PDF書(shū)簽的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07