日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

c#?理解csredis庫實現分布式鎖的詳細流程_C#教程

作者:SZMD.ls.nct ? 更新時間: 2022-04-11 編程語言

聲明:

這里首先使用的是csredis,地址是https://github.com/2881099/csredis

該庫本身已經足夠完善,這里我畫蛇添足一下,為了方便自己的使用。

本身csredis庫已經實現了完整的加鎖和去鎖的邏輯,這里實現的與庫本身所實現的有以下幾點區別(csredis實現代碼位置為:https://github.com/2881099/csredis/blob/bb6d947695770333027f3936f80052041db41b64/src/CSRedisCore/CSRedisClient.cs#L4344,有興趣可以去了解看下)

1. 去掉了csredis的鎖續租部分的功能,盡量簡化

2. 將鎖的token的設定交給外部,使用guid也罷,使用id也行。通過已知的token,保證了你可以在任意地方以觀察者的身份釋放鎖。

3. 盡量不修改其key的原本值,不添加前綴,防止在觀測時出現不必要的麻煩。

邏輯:

加鎖就是set 一個 key ,如果key 存在的情況下則返回失敗。那么典型的命令就是setnx.

一個鎖顯然是需要一個過期時間的,那么我們可能要用到 expire命令。

釋放鎖則是一個del命令

查看鎖的值是需要get命令

比較常見的加鎖使用的是setnx,不過由于redis支持了SETkeytokenNXEX/PXmax-lock-time(sec/millsec) (設置key token 是否不存在才set 秒數模式/毫秒數模式 秒數或毫秒數) 這種傳參模式,由此,這里更加推薦使用set 命令。

如果在我們的代碼端執行del 則小概率發生以下情況:

  A 申請鎖set x,過期時間為t。

  經過時間t后,A恰好忙完了,A通過get命令看看token是否一致,得到結果發現一致的。

A決定發送del到redis服務器,此時A恰好網絡擁堵。

redis服務器由于鎖x超時,進而釋放了鎖x。

  此時B恰好也申請了鎖x,無過期時間。

A網絡恢復,del命令發送成功。

結果 B的鎖被A釋放了。

幸好redis支持了lua腳本。讓我們得以簡單的實現過期,加鎖,去鎖功能,而不需要自己手動timer過期。

這里要使用到eval命令執行腳本。

代碼

using CSRedis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace CsRedis.Helper
{
    /// <summary>
    ///  基于csredis的簡單封裝
    /// </summary>
    public class CsRedisManager
    {
       
        private ConcurrentDictionary<string, CSRedisClient> _serviceNameWithClient;
      
        /// <summary>
        ///  初始化
        /// </summary>
        public void Init()
        {
            _serviceNameWithClient = new ConcurrentDictionary<string, CSRedisClient>();
        }
        ///  獲取業務redis服務
        /// <param name="serviceName"></param>
        /// <returns></returns>
        public CSRedisClient GetRedisClient(string serviceName)
            CSRedisClient result = null;
            _serviceNameWithClient.TryGetValue(serviceName,out result);
            return result;
        ///  添加redis服務
        /// <param name="connectStr"></param>
        public bool AddRedisClient(string serviceName,string connectStr)
            CSRedisClient cSRedisClient = new CSRedisClient(connectStr);
            return _serviceNameWithClient.TryAdd(serviceName, cSRedisClient);
        ///  設置字符串型kv 
        /// <param name="key">key</param>
        /// <param name="value">value</param>
        /// <param name="expireSecond">過期時間(秒)</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);
           
        ///  獲取相應key的值
        /// <param name="key"></param>
        public string Get(string serviceName, string key)
            return redisClient.Get(key);
        ///  如果不存在則執行,存在則忽略
        /// <param name="value"></param>
        public bool SetNx(string serviceName, string key, string value)
            var res = redisClient.SetNx(key, value);
            return res;
        ///  帶過期時間的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);
        ///  帶過期時間的SetXx
        public bool SetXx(string serviceName, string key, string value, int millSeconds = -1)
            var res = Set(serviceName, key, value, RedisExistence.Xx, millSeconds);
        ///  帶參數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);
        ///  設置生存時間
        public bool Expire(string serviceName, string key, int seconds)
            return redisClient.Expire(key, seconds);
        ///  獲取剩余的生存時間(秒)
        public long Ttl(string serviceName, string key)
            return redisClient.Ttl(key);
        ///  刪除del
        public long Del(string serviceName,params string[] keys)
            return redisClient.Del(keys);
        ///  執行腳本
        /// <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發生異常
        /// <param name="client"></param>
        private void GetExceptionOfClient(CSRedisClient client)
            if (client == null)
                throw new Exception("無有效的redis服務");
        ///  lua腳本刪除共享鎖
        ///  解決在A申請鎖 xxkey  過期的瞬間,B 申請鎖xxkey,
        ///  此時恰好A執行到釋放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";
        
    }
    
}

這里我把要單獨執行的lua腳本單獨提出來

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

這段腳本對應的是c# 中的GetReleaseLockScript()方法中的文字。

這里我個人偷了個懶,按照道理,這里應該有個LoadScriptPath,加載腳本所在位置,調用的時候先檢查腳本是否在內存中,不在則去LoadScriptPath找對應的腳本,方便不同的人協同合作。不過那個就是腳本管理器了,還要設計interface,有點偏離主題了。

下面是測試代碼

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);
    }
}

這里就是對于共享鎖的一點簡單實現,多了挺多與本次的命令無關的代碼,海涵海涵

原文鏈接:https://www.cnblogs.com/lsnct/p/15866860.html

欄目分類
最近更新