C#實現(xiàn)Redis的分布式鎖
Redis實現(xiàn)分布式鎖(悲觀鎖/樂觀鎖)
對鎖的概念和應用場景在此就不闡述了,網(wǎng)上搜索有很多解釋,只是我搜索到的使用C#利用Redis的SetNX命令實現(xiàn)的鎖雖然能用,但是都不太適合我需要的場景。
Redis有三個最基本屬性來保證分布式鎖的有效實現(xiàn):
- 安全性: 互斥,在任何時候,只有一個客戶端能持有鎖。
- 活躍性A:沒有死鎖,即使客戶端在持有鎖的時候崩潰,最后也會有其他客戶端能獲得鎖,超時機制。
- 活躍性B:故障容忍,只有大多數(shù)Redis節(jié)點時存活的,客戶端仍可以獲得鎖和釋放鎖。
基于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命令實現(xiàn)加鎖,
/// <summary> /// 加鎖 /// </summary> /// <param name="key">鎖key</param> /// <param name="selfMark">自己標記</param> /// <param name="lockExpirySeconds">鎖自動過期時間[默認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");//自己標記,釋放鎖時會用到,自己加的鎖除非過期否則只能自己打開 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; } }
因為ServiceStack.Redis提供的SetNX方法,并沒有提供設置過期時間的方法,對于加鎖業(yè)務又不能分開執(zhí)行(如果加鎖成功設置過期時間失敗導致的永久死鎖問題),所以就使用腳本實現(xiàn),解決了異常情況死鎖問題.
- 參數(shù)key:鎖的key
- 參數(shù)selfMark:在設置鎖的時候會產(chǎn)生一個自己的標識,在釋放鎖的時候會用到,所謂解鈴還須系鈴人。防止鎖被誤釋放,導致鎖無效.
- 參數(shù)lockExpirySeconds:鎖的默認過期時間,防止被永久死鎖.
- 參數(shù)waitLockMilliseconds:循環(huán)獲取鎖的等待時間.
如果設置為0,為樂觀鎖機制,獲取不到鎖,直接返回未獲取到鎖.
默認值為long最大值,為悲觀鎖機制,約等于很多很多天,可以理解為一直等待.
釋放鎖
/// <summary> /// 釋放鎖 /// </summary> /// <param name="key">鎖key</param> /// <param name="selfMark">自己標記</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:在設置鎖的時候返回的自己標識,用來解鎖自己加的鎖(此值不能隨意傳,必須是加鎖時返回的值)
調用方式
悲觀鎖方式
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īng)可以滿足大多數(shù)場景了,若有不妥,還請多多指出,以免誤別人!
(次方案不支持Redis集群,Redis集群不能調用腳本執(zhí)行)
到此這篇關于C#實現(xiàn)Redis的分布式鎖的文章就介紹到這了,更多相關C# Redis分布式鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C#語法糖(Csharp Syntactic sugar)大匯總
首先需要聲明的是“語法糖”這個詞絕非貶義詞,它可以給我?guī)矸奖?,是一種便捷的寫法,編譯器會幫我們做轉換;而且可以提高開發(fā)編碼的效率,在性能上也不會帶來損失。這讓java開發(fā)人員羨慕不已,呵呵。2010-06-06