c#?理解csredis庫(kù)實(shí)現(xiàn)分布式鎖的詳細(xì)流程
聲明:
這里首先使用的是csredis,地址是https://github.com/2881099/csredis
該庫(kù)本身已經(jīng)足夠完善,這里我畫(huà)蛇添足一下,為了方便自己的使用。
本身csredis庫(kù)已經(jīng)實(shí)現(xiàn)了完整的加鎖和去鎖的邏輯,這里實(shí)現(xiàn)的與庫(kù)本身所實(shí)現(xiàn)的有以下幾點(diǎn)區(qū)別(csredis實(shí)現(xiàn)代碼位置為:https://github.com/2881099/csredis/blob/bb6d947695770333027f3936f80052041db41b64/src/CSRedisCore/CSRedisClient.cs#L4344,有興趣可以去了解看下)
1. 去掉了csredis的鎖續(xù)租部分的功能,盡量簡(jiǎn)化
2. 將鎖的token的設(shè)定交給外部,使用guid也罷,使用id也行。通過(guò)已知的token,保證了你可以在任意地方以觀察者的身份釋放鎖。
3. 盡量不修改其key的原本值,不添加前綴,防止在觀測(cè)時(shí)出現(xiàn)不必要的麻煩。
邏輯:
加鎖就是set 一個(gè) key ,如果key 存在的情況下則返回失敗。那么典型的命令就是setnx.
一個(gè)鎖顯然是需要一個(gè)過(guò)期時(shí)間的,那么我們可能要用到 expire命令。
釋放鎖則是一個(gè)del命令
查看鎖的值是需要get命令
比較常見(jiàn)的加鎖使用的是setnx,不過(guò)由于redis支持了SETkeytokenNXEX/PXmax-lock-time(sec/millsec) (設(shè)置key token 是否不存在才set 秒數(shù)模式/毫秒數(shù)模式 秒數(shù)或毫秒數(shù)) 這種傳參模式,由此,這里更加推薦使用set 命令。
如果在我們的代碼端執(zhí)行del 則小概率發(fā)生以下情況:
A 申請(qǐng)鎖set x,過(guò)期時(shí)間為t。
經(jīng)過(guò)時(shí)間t后,A恰好忙完了,A通過(guò)get命令看看token是否一致,得到結(jié)果發(fā)現(xiàn)一致的。
A決定發(fā)送del到redis服務(wù)器,此時(shí)A恰好網(wǎng)絡(luò)擁堵。
redis服務(wù)器由于鎖x超時(shí),進(jìn)而釋放了鎖x。
此時(shí)B恰好也申請(qǐng)了鎖x,無(wú)過(guò)期時(shí)間。
A網(wǎng)絡(luò)恢復(fù),del命令發(fā)送成功。
結(jié)果 B的鎖被A釋放了。
幸好redis支持了lua腳本。讓我們得以簡(jiǎn)單的實(shí)現(xiàn)過(guò)期,加鎖,去鎖功能,而不需要自己手動(dòng)timer過(guò)期。
這里要使用到eval命令執(zhí)行腳本。
代碼
using CSRedis; using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace CsRedis.Helper { /// <summary> /// 基于csredis的簡(jiǎn)單封裝 /// </summary> public class CsRedisManager { private ConcurrentDictionary<string, CSRedisClient> _serviceNameWithClient; /// <summary> /// 初始化 /// </summary> public void Init() { _serviceNameWithClient = new ConcurrentDictionary<string, CSRedisClient>(); } /// 獲取業(yè)務(wù)redis服務(wù) /// <param name="serviceName"></param> /// <returns></returns> public CSRedisClient GetRedisClient(string serviceName) CSRedisClient result = null; _serviceNameWithClient.TryGetValue(serviceName,out result); return result; /// 添加redis服務(wù) /// <param name="connectStr"></param> public bool AddRedisClient(string serviceName,string connectStr) CSRedisClient cSRedisClient = new CSRedisClient(connectStr); return _serviceNameWithClient.TryAdd(serviceName, cSRedisClient); /// 設(shè)置字符串型kv /// <param name="key">key</param> /// <param name="value">value</param> /// <param name="expireSecond">過(guò)期時(shí)間(秒)</param> /// <returns>是否成功</returns> public bool Set(string serviceName,string key,string value,int expireSecond=-1) var redisClient = GetRedisClient(serviceName); GetExceptionOfClient(redisClient); return redisClient.Set(key, value, expireSecond); /// 獲取相應(yīng)key的值 /// <param name="key"></param> public string Get(string serviceName, string key) return redisClient.Get(key); /// 如果不存在則執(zhí)行,存在則忽略 /// <param name="value"></param> public bool SetNx(string serviceName, string key, string value) var res = redisClient.SetNx(key, value); return res; /// 帶過(guò)期時(shí)間的setNx /// <param name="seconds"></param> public bool SetNx(string serviceName, string key, string value, int millSeconds = -1) var res = Set(serviceName, key, value, RedisExistence.Nx, millSeconds); /// 帶過(guò)期時(shí)間的SetXx public bool SetXx(string serviceName, string key, string value, int millSeconds = -1) var res = Set(serviceName, key, value, RedisExistence.Xx, millSeconds); /// 帶參數(shù)set /// <param name="existence"></param> public bool Set(string serviceName, string key, string value, RedisExistence existence, int millSeconds = -1) var res = redisClient.Set(key, value, millSeconds, existence); /// 設(shè)置生存時(shí)間 public bool Expire(string serviceName, string key, int seconds) return redisClient.Expire(key, seconds); /// 獲取剩余的生存時(shí)間(秒) public long Ttl(string serviceName, string key) return redisClient.Ttl(key); /// 刪除del public long Del(string serviceName,params string[] keys) return redisClient.Del(keys); /// 執(zhí)行腳本 /// <param name="script"></param> /// <param name="args"></param> public object Eval(string serviceName, string script,string key,params object[] args) var res = redisClient.Eval(script, key,args); /// 添加共享鎖 public bool AddLock(string serviceName, string key,string token, int millSeconds = -1) var valRes = SetNx(serviceName, key, token, millSeconds); return valRes; /// 刪除共享鎖 public bool ReleaseLock(string serviceName, string key,string token) var script = GetReleaseLockScript(); var res = redisClient.Eval(script, key, token); if (0== (long)res) { return false; } return true; /// 獲取鍵值 /// <param name="pattern"></param> public string[] Keys(string serviceName, string pattern) var res = redisClient.Keys(pattern); /// 獲取client發(fā)生異常 /// <param name="client"></param> private void GetExceptionOfClient(CSRedisClient client) if (client == null) throw new Exception("無(wú)有效的redis服務(wù)"); /// lua腳本刪除共享鎖 /// 解決在A申請(qǐng)鎖 xxkey 過(guò)期的瞬間,B 申請(qǐng)鎖xxkey, /// 此時(shí)恰好A執(zhí)行到釋放xxkey從而引起的異常釋放 private static string GetReleaseLockScript() return "if redis.call(\"get\",KEYS[1]) == ARGV[1] \nthen\nreturn redis.call(\"del\", KEYS[1])\nelse\nreturn 0\nend"; } }
這里我把要單獨(dú)執(zhí)行的lua腳本單獨(dú)提出來(lái)
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
這段腳本對(duì)應(yīng)的是c# 中的GetReleaseLockScript()方法中的文字。
這里我個(gè)人偷了個(gè)懶,按照道理,這里應(yīng)該有個(gè)LoadScriptPath,加載腳本所在位置,調(diào)用的時(shí)候先檢查腳本是否在內(nèi)存中,不在則去LoadScriptPath找對(duì)應(yīng)的腳本,方便不同的人協(xié)同合作。不過(guò)那個(gè)就是腳本管理器了,還要設(shè)計(jì)interface,有點(diǎn)偏離主題了。
下面是測(cè)試代碼
using CsRedis.Helper; using NUnit.Framework; namespace TestProject { public class Tests { [SetUp] public void Setup() { } [Test] public void Test1() CsRedisManager csRedisManager = new CsRedisManager(); csRedisManager.Init(); csRedisManager.AddRedisClient("TEST", "127.0.0.1:6379,password=123456, connectTimeout =1000,connectRetry=1,syncTimeout=10000,defaultDatabase=0"); //csRedisManager.AddRedisClient("PRODUCT", "127.0.0.1:6379,password=123456, connectTimeout =1000,connectRetry=1,syncTimeout=10000,defaultDatabase=1"); var token = "123"; var lockKey = "LOCKKEY1"; csRedisManager.AddLock("TEST", lockKey,token,20 * 1000); csRedisManager.ReleaseLock("TEST", lockKey, token); } }
這里就是對(duì)于共享鎖的一點(diǎn)簡(jiǎn)單實(shí)現(xiàn),多了挺多與本次的命令無(wú)關(guān)的代碼,海涵海涵
到此這篇關(guān)于c# 理解csredis實(shí)現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)c# 分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#調(diào)用C++DLL傳遞結(jié)構(gòu)體數(shù)組的終極解決方案
這篇文章主要介紹了C#調(diào)用C++DLL傳遞結(jié)構(gòu)體數(shù)組的終極解決方案的相關(guān)資料,需要的朋友可以參考下2017-01-01C# 編碼好習(xí)慣,獻(xiàn)給所有熱愛(ài)c#的同志
c#編寫(xiě)者,需要培養(yǎng)的一些好習(xí)慣2009-02-02C#創(chuàng)建WCF服務(wù)控制臺(tái)應(yīng)用程序詳解
這篇文章主要為大家詳細(xì)介紹了C#創(chuàng)建WCF服務(wù)控制臺(tái)應(yīng)用程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07C# List<T> Contains<T>()的用法小結(jié)
本篇文章主要是對(duì)C#中List<T> Contains<T>()的用法進(jìn)行了總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01C# WPF利用Clip屬性實(shí)現(xiàn)截屏框功能
這篇文章主要為大家詳細(xì)介紹了C# WPF如何利用Clip屬性實(shí)現(xiàn)截屏框功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01C#創(chuàng)建Windows服務(wù)與服務(wù)的安裝、卸載
這篇文章介紹了C#創(chuàng)建Windows服務(wù)與服務(wù)的安裝、卸載,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02