SoFunction
Updated on 2025-03-07

C# Understand the detailed process of implementing distributed locks in the csredis library

statement:

The first thing to use here is csredis, the address is /2881099/csredis

The library itself is perfect enough, so I will add some things here to facilitate my use.

The csredis library itself has implemented the complete locking and de-locking logic. The implementation here is different from the implementation of the library itself (the code location of the csredis implementation is: /2881099/csredis/blob/bb6d947695770333027f3936f80052041db41b64/src/CSRedisCore/#L4344. If you are interested, you can learn about it and take a look)

1. Removed the function of the lock-up and renewal part of csredis to simplify it as much as possible.

2. Leave the settings of the locked token to the outside, whether it is guid or id. Through known tokens, you can release the lock as an observer anywhere.

3. Try not to modify the original value of its key and do not add prefixes to prevent unnecessary trouble during observation.

logic:

Locking means setting a key, and if the key exists, it will return a failure. Then a typical command is setnx.

A lock obviously requires an expiration time, so we may need to use the expire command.

Release the lock is a del command

You need to check the value of the lock.

The most common locking method uses setnx, but because redis supports SETkeytokenNXEX/PXmax-lock-time(sec/millsec) (set whether the key token does not exist, set seconds mode/msec mode, seconds or milliseconds) This parameter transfer mode, therefore, it is more recommended to use the set command here.

If you execute del on our code side, the following is low probability:

A Apply to lock set x, the expiration time is t.

After time t, A happened to be finished. A used the get command to see if the token is consistent, and found that the result was consistent.

A decided to send del to the redis server, and A happened to be in a network congestion.

The redis server releases the lock x because lock x timed out.

At this time, B happened to apply for lock x, without expiration time.

A network recovery, the del command is sent successfully.

As a result, B's lock was released by A.

Fortunately, redis supports lua scripts. This allows us to simply implement the expiration, locking and unlocking functions without having to manually expire.

Here you need to use the eval command to execute the script.

Code

using CSRedis;
using System;
using ;
using ;

namespace 
{
    /// <summary>
    /// Simple encapsulation based on csredis    /// </summary>
    public class CsRedisManager
    {
       
        private ConcurrentDictionary<string, CSRedisClient> _serviceNameWithClient;
      
        /// <summary>
        /// Initialization        /// </summary>
        public void Init()
        {
            _serviceNameWithClient = new ConcurrentDictionary<string, CSRedisClient>();
        }
        /// Get business redis service        /// <param name="serviceName"></param>
        /// <returns></returns>
        public CSRedisClient GetRedisClient(string serviceName)
            CSRedisClient result = null;
            _serviceNameWithClient.TryGetValue(serviceName,out result);
            return result;
        /// Add redis service        /// <param name="connectStr"></param>
        public bool AddRedisClient(string serviceName,string connectStr)
            CSRedisClient cSRedisClient = new CSRedisClient(connectStr);
            return _serviceNameWithClient.TryAdd(serviceName, cSRedisClient);
        /// Set string type kv        /// <param name="key">key</param>
        /// <param name="value">value</param>
        /// <param name="expireSecond">Expiration time (seconds)</param>        /// <returns> Whether it was successful</returns>        public bool Set(string serviceName,string key,string value,int expireSecond=-1)
            var redisClient = GetRedisClient(serviceName);
            GetExceptionOfClient(redisClient);
            
            return (key, value, expireSecond);
           
        /// Get the value of the corresponding key        /// &lt;param name="key"&gt;&lt;/param&gt;
        public string Get(string serviceName, string key)
            return (key);
        /// If it does not exist, it will be ignored        /// &lt;param name="value"&gt;&lt;/param&gt;
        public bool SetNx(string serviceName, string key, string value)
            var res = (key, value);
            return res;
        /// setNx with expiration time        /// &lt;param name="seconds"&gt;&lt;/param&gt;
        public bool SetNx(string serviceName, string key, string value, int millSeconds = -1)
            var res = Set(serviceName, key, value, , millSeconds);
        /// SetXx with expiration time        public bool SetXx(string serviceName, string key, string value, int millSeconds = -1)
            var res = Set(serviceName, key, value, , millSeconds);
        /// With parameter set        /// &lt;param name="existence"&gt;&lt;/param&gt;
        public bool Set(string serviceName, string key, string value, RedisExistence existence, int millSeconds = -1)
            var res = (key, value, millSeconds, existence);
        /// Set survival time        public bool Expire(string serviceName, string key, int seconds)
            return (key, seconds);
        /// Get the remaining survival time (seconds)        public long Ttl(string serviceName, string key)
            return (key);
        /// Delete del        public long Del(string serviceName,params string[] keys)
            return (keys);
        /// Execute the script        /// &lt;param name="script"&gt;&lt;/param&gt;
        /// &lt;param name="args"&gt;&lt;/param&gt;
        public object Eval(string serviceName, string script,string key,params object[] args)
            var res = (script, key,args);
        /// Add a shared lock        public bool AddLock(string serviceName, string key,string token, int millSeconds = -1)
            var valRes = SetNx(serviceName, key, token, millSeconds);
            return valRes;
        /// Delete the shared lock        public bool ReleaseLock(string serviceName, string key,string token)
            var script = GetReleaseLockScript();
            var res = (script, key, token);
            if (0== (long)res)
            {
                return false;
            }
            return true;
        /// Get key value        /// &lt;param name="pattern"&gt;&lt;/param&gt;
        public string[] Keys(string serviceName, string pattern)
            var res = (pattern);
        /// An exception occurred when obtaining client        /// &lt;param name="client"&gt;&lt;/param&gt;
        private void GetExceptionOfClient(CSRedisClient client)
            if (client == null)
                throw new Exception("No valid redis service");
        /// Lua script deletes the shared lock        /// Solve the moment when A applies for lock xxkey expires, B applies for lock xxkey,        /// At this time, A happens to be executing to release xxkey, which causes abnormal release caused by releasing xxkey.        private static  string GetReleaseLockScript()
            return "if (\"get\",KEYS[1]) == ARGV[1] \nthen\nreturn (\"del\", KEYS[1])\nelse\nreturn 0\nend";
        
    }
    
}

Here I propose the lua script to be executed separately

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

This script corresponds to the text in the GetReleaseLockScript() method in c#.

I personally have a lazy look here. According to the principle, there should be a LoadScriptPath here. The location of the loading script is located. When calling, check whether the script is in memory. If it is not there, go to LoadScriptPath to find the corresponding script to facilitate different people to cooperate. But that is the script manager, and you still have to design the interface, which is a bit off the topic.

Below is the test code

using ;
using ;

namespace TestProject
{
    public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }
        [Test]
        public void Test1()
            CsRedisManager csRedisManager = new CsRedisManager();
            ();
            ("TEST", "127.0.0.1:6379,password=123456, connectTimeout =1000,connectRetry=1,syncTimeout=10000,defaultDatabase=0");
            //("PRODUCT", "127.0.0.1:6379,password=123456, connectTimeout =1000,connectRetry=1,syncTimeout=10000,defaultDatabase=1");
            
            var token = "123";
            var lockKey = "LOCKKEY1";
            ("TEST", lockKey,token,20 * 1000);
            ("TEST", lockKey, token);
    }
}

Here is a simple implementation of shared locks. There are a lot of codes that are not related to this command. Haihan Haihan

This is the end of this article about C# understanding the implementation of distributed locks by CREDI. For more related content of C# distributed locks, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!