.net core RedLockNet 分布式锁

github源码地址

samcook/RedLock.net: An implementation of the Redlock algorithm in C# (github.com)

源码结构

 

主要代码在 RedLockFactory ,RedLock 两个类中

RedLock 创建 lock 和 释放 lock

主要看 RedLock 代码 

下面 是主要只读字段

		private readonly ICollection<RedisConnection> redisCaches;  //这是redis节点集合
		private readonly int quorum;//法定数量
		private readonly int quorumRetryCount;
		private readonly int quorumRetryDelayMs;
    
                    

private readonly TimeSpan expiryTime; //过期时间

private readonly TimeSpan? waitTime;// 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间

private readonly TimeSpan? retryTime;//  retryTime 等待时间内,多久尝试获取一次

 


  

            quorum = redisCaches.Count / 2 + 1; // 这个算了节点数除2加1
            quorumRetryCount = retryConfiguration?.RetryCount ?? DefaultQuorumRetryCount;
            quorumRetryDelayMs = retryConfiguration?.RetryDelayMs ?? DefaultQuorumRetryDelayMs;

创建锁 主要 是实例化   RedLock 和 调用   redisLock.Start();

 

internal static RedLock Create(
            ILogger<RedLock> logger,
            ICollection<RedisConnection> redisCaches,
            string resource,
            TimeSpan expiryTime,
            TimeSpan? waitTime = null,
            TimeSpan? retryTime = null,
            RedLockRetryConfiguration retryConfiguration = null,
            CancellationToken? cancellationToken = null)
        {
            var redisLock = new RedLock(
                logger,
                redisCaches,
                resource,
                expiryTime,
                waitTime,
                retryTime,
                retryConfiguration,
                cancellationToken);

            redisLock.Start();
            
            return redisLock;
        }

下面 主要是给参数赋值

private RedLock(
            ILogger<RedLock> logger,
            ICollection<RedisConnection> redisCaches,
            string resource,
            TimeSpan expiryTime,
            TimeSpan? waitTime = null,
            TimeSpan? retryTime = null,
            RedLockRetryConfiguration retryConfiguration = null,
            CancellationToken? cancellationToken = null)
        {
            this.logger = logger;

            if (expiryTime < MinimumExpiryTime)
            {
                logger.LogWarning($"Expiry time {expiryTime.TotalMilliseconds}ms too low, setting to {MinimumExpiryTime.TotalMilliseconds}ms");
                expiryTime = MinimumExpiryTime;
            }

            if (retryTime != null && retryTime.Value < MinimumRetryTime)
            {
                logger.LogWarning($"Retry time {retryTime.Value.TotalMilliseconds}ms too low, setting to {MinimumRetryTime.TotalMilliseconds}ms");
                retryTime = MinimumRetryTime;
            }

            this.redisCaches = redisCaches;

            quorum = redisCaches.Count / 2 + 1;
            quorumRetryCount = retryConfiguration?.RetryCount ?? DefaultQuorumRetryCount;
            quorumRetryDelayMs = retryConfiguration?.RetryDelayMs ?? DefaultQuorumRetryDelayMs;

            Resource = resource;
            LockId = Guid.NewGuid().ToString();
            this.expiryTime = expiryTime;
            this.waitTime = waitTime;
            this.retryTime = retryTime;
            this.cancellationToken = cancellationToken ?? CancellationToken.None;
        }

然后 是 主要代码  Start 主要逻辑就是 判断时间 然后调用  Acquire(); 方法

private void Start()
        {
            if (waitTime.HasValue && retryTime.HasValue && waitTime.Value.TotalMilliseconds > 0 && retryTime.Value.TotalMilliseconds > 0)
            {
                var stopwatch = Stopwatch.StartNew();

                // ReSharper disable PossibleInvalidOperationException
                while (!IsAcquired && stopwatch.Elapsed <= waitTime.Value)
                {
                    (Status, InstanceSummary) = Acquire();

                    if (!IsAcquired)
                    {
                        TaskUtils.Delay(retryTime.Value, cancellationToken).Wait(cancellationToken);
                    }
                }
                // ReSharper restore PossibleInvalidOperationException
            }
            else
            {
                (Status, InstanceSummary) = Acquire();
            }

            logger.LogInformation($"Lock status: {Status} ({InstanceSummary}), {Resource} ({LockId})");

            if (IsAcquired)
            {
                StartAutoExtendTimer();
            }
        }

 

 

private (RedLockStatus, RedLockInstanceSummary) Acquire()
        {
            var lockSummary = new RedLockInstanceSummary();

            for (var i = 0; i < quorumRetryCount; i++)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var iteration = i + 1;
                logger.LogDebug($"Lock attempt {iteration}/{quorumRetryCount}: {Resource} ({LockId}), expiry: {expiryTime}");

                var stopwatch = Stopwatch.StartNew();

                lockSummary = Lock();

                var validityTicks = GetRemainingValidityTicks(stopwatch);

                logger.LogDebug($"Acquired locks for {Resource} ({LockId}) in {lockSummary.Acquired}/{redisCaches.Count} instances, quorum: {quorum}, validityTicks: {validityTicks}");

                if (lockSummary.Acquired >= quorum && validityTicks > 0)
                {
                    return (RedLockStatus.Acquired, lockSummary);
                }
                
                // we failed to get enough locks for a quorum, unlock everything and try again
                Unlock();

                // only sleep if we have more retries left
                if (i < quorumRetryCount - 1)
                {
                    var sleepMs = ThreadSafeRandom.Next(quorumRetryDelayMs);

                    logger.LogDebug($"Sleeping {sleepMs}ms");

                    TaskUtils.Delay(sleepMs, cancellationToken).Wait(cancellationToken);
                }
            }

            var status = GetFailedRedLockStatus(lockSummary);

            // give up
            logger.LogDebug($"Could not acquire quorum after {quorumRetryCount} attempts, giving up: {Resource} ({LockId}). {lockSummary}.");

            return (status, lockSummary);
        }

 

 

加锁的方法 主要 是调用 LockInstance

private RedLockInstanceSummary Lock()
		{
			var lockResults = new ConcurrentBag<RedLockInstanceResult>();

			Parallel.ForEach(redisCaches, cache =>
			{
				lockResults.Add(LockInstance(cache));
			});

			return PopulateRedLockResult(lockResults);
		}

 LockInstance 方法怎么加锁的  注意 这个方法 是并行循环调用的 也就是每个redis节点 都加了 一个带过期时间的key

  主要代码 原来就是 加了 key 过期时间

cache.ConnectionMultiplexer
                    .GetDatabase(cache.RedisDatabase)
                    .StringSet(redisKey, LockId, expiryTime, When.NotExists, CommandFlags.DemandMaster);
然后 给状态 赋值
result = redisResult ? RedLockInstanceResult.Success : RedLockInstanceResult.Conflicted;
private RedLockInstanceResult LockInstance(RedisConnection cache)
        {
            var redisKey = GetRedisKey(cache.RedisKeyFormat, Resource);
            var host = GetHost(cache.ConnectionMultiplexer);
            RedLockInstanceResult result;
            try
            {
                logger.LogTrace($"LockInstance enter {host}: {redisKey}, {LockId}, {expiryTime}");
                var redisResult = cache.ConnectionMultiplexer
                    .GetDatabase(cache.RedisDatabase)
                    .StringSet(redisKey, LockId, expiryTime, When.NotExists, CommandFlags.DemandMaster);
                result = redisResult ? RedLockInstanceResult.Success : RedLockInstanceResult.Conflicted;
            }
            catch (Exception ex)
            {
                logger.LogDebug($"Error locking lock instance {host}: {ex.Message}");

                result = RedLockInstanceResult.Error;
            }

            logger.LogTrace($"LockInstance exit {host}: {redisKey}, {LockId}, {result}");

            return result;
        }

解锁 主要 调用  UnlockInstance

        private void Unlock()
        {
            // ReSharper disable once MethodSupportsCancellation
            extendUnlockSemaphore.Wait();
            try
            {
                Parallel.ForEach(redisCaches, UnlockInstance);
            }
            finally
            {
                extendUnlockSemaphore.Release();
            }
        }
UnlockInstance 这里主要代码 原来是 并行循环 调用了 lua 脚本
	private void UnlockInstance(RedisConnection cache)
		{
			var redisKey = GetRedisKey(cache.RedisKeyFormat, Resource);
			var host = GetHost(cache.ConnectionMultiplexer);

			var result = false;

			try
			{
				logger.LogTrace($"UnlockInstance enter {host}: {redisKey}, {LockId}");
				result = (bool) cache.ConnectionMultiplexer
					.GetDatabase(cache.RedisDatabase)
					.ScriptEvaluate(UnlockScript, new RedisKey[] {redisKey}, new RedisValue[] {LockId}, CommandFlags.DemandMaster);
			}
			catch (Exception ex)
			{
				logger.LogDebug($"Error unlocking lock instance {host}: {ex.Message}");
			}

			logger.LogTrace($"UnlockInstance exit {host}: {redisKey}, {LockId}, {result}");
		}

解锁脚本

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

 

 

 

public enum RedLockStatus
    {
        /// <summary>
        /// The lock has not yet been successfully acquired, or has been released.
        /// </summary>
        Unlocked,

        /// <summary>
        /// The lock was acquired successfully.
        /// </summary>
        Acquired,

        /// <summary>
        /// The lock was not acquired because there was no quorum available.
        /// </summary>
        NoQuorum,

        /// <summary>
        /// The lock was not acquired because it is currently locked with a different LockId.
        /// </summary>
        Conflicted,

        /// <summary>
        /// The lock expiry time passed before lock acquisition could be completed.
        /// </summary>
        Expired
    }

 

 

最后 总结 加锁

首先 实例化 RedLock 然后掉用 实例方法 Start ,Start 实例 方法 有判断 当前锁的 状态 RedLockStatus 为 Key 添加 过期时间 ,同时为每个redis节点 都添加 ,解锁是调用 解锁脚本 也是执行 每个节点解锁

原来原理这么简单

 

posted on 2023-07-11 15:56  是水饺不是水饺  阅读(457)  评论(0编辑  收藏  举报

导航