private static async Task <DistributedLockStatus> AcquireResourceAsync(RedisLock redisLock, ILogger logger) { if (redisLock.CancellationToken.HasValue && redisLock.CancellationToken.Value.IsCancellationRequested) { redisLock.Status = DistributedLockStatus.Cancelled; redisLock.CancellationToken.Value.ThrowIfCancellationRequested(); } List <RedisKey> redisKeys = new List <RedisKey>(); List <RedisValue> redisValues = new List <RedisValue>(); AddAcquireOrExtendRedisInfo(redisLock, redisKeys, redisValues, logger); IDatabase database = await GetDatabaseAsync(redisLock.Options.ConnectionSetting, logger).ConfigureAwait(false); try { RedisResult result = await database.ScriptEvaluateAsync( _loadedLuas.LoadedLockLua, redisKeys.ToArray(), redisValues.ToArray()).ConfigureAwait(false); int rt = (int)result; return(rt == 1 ? DistributedLockStatus.Acquired : DistributedLockStatus.Failed); } catch (RedisServerException ex) when(ex.Message.StartsWith("NOSCRIPT", StringComparison.InvariantCulture)) { logger.LogError(ex, "NOSCRIPT, will try again."); InitLoadedLuas(redisLock.Options.ConnectionSetting, logger); return(await AcquireResourceAsync(redisLock, logger).ConfigureAwait(false)); } }
private static void AddAcquireOrExtendRedisInfo(RedisLock redisLock, List <RedisKey> redisKeys, List <RedisValue> redisValues, ILogger logger) { /// keys: resource1, resource2, resource3 /// argv: 3(resource_count), expire_milliseconds, resource1_value, resource2_value, resource3_value //有可能到这里,dispose了,redisLock.Resources都为空 try { foreach (string item in redisLock.Resources) { redisKeys.Add(item); } redisValues.Add(redisKeys.Count); redisValues.Add((int)redisLock.ExpiryTime.TotalMilliseconds); foreach (string item in redisLock.ResourceValues) { redisValues.Add(item); } } catch (NullReferenceException ex) { logger.LogError(ex, $"在试图延长锁的时候,ResourceValues被清空. Resources:{redisLock.Resources.ToJoinedString(",")}, Status:{redisLock.Status}"); } }
/// <summary> /// /// </summary> /// <param name="resources"></param> /// <param name="expiryTime">对资源的最大占用时间,应该大于TimeSpan.Zero, null表示使用默认</param> /// <param name="waitTime">如果资源被占用,你愿意等多久,TimeSpan.Zero表明不愿意等。null表示使用默认等待时间</param> /// <param name="retryInterval">等待时不断尝试获取资源 的 等待间隔,应该大于TimeSpan.Zero, null 表示使用默认时间</param> /// <returns></returns> public async Task <IDistributedLock> LockAsync(IEnumerable <string> resources, TimeSpan expiryTime, TimeSpan?waitTime, TimeSpan?retryInterval, bool notUnlockWhenDispose = false, CancellationToken?cancellationToken = null) { if (expiryTime < _minimumExpiryTime) { _logger.LogWarning("Expiry {settingTime} ms too low, setting to {minimumExpiryTime} ms", expiryTime.TotalMilliseconds, _minimumExpiryTime.TotalMilliseconds); expiryTime = _minimumExpiryTime; } if (retryInterval != null && retryInterval.Value < _minimumRetryTime) { _logger.LogWarning("Retry {settingTime} ms too low, setting to {minimumRetryTime} ms", retryInterval.Value.TotalMilliseconds, _minimumRetryTime.TotalMilliseconds); retryInterval = _minimumRetryTime; } RedisLock redisLock = new RedisLock( _options, _logger, resources, expiryTime, waitTime ?? TimeSpan.FromMilliseconds(_options.DefaultWaitMilliseconds), retryInterval ?? TimeSpan.FromMilliseconds(_options.DefaultRetryIntervalMilliseconds), notUnlockWhenDispose, cancellationToken); await StartAsync(redisLock, _logger).ConfigureAwait(false); return(redisLock); }
private static void StartAutoExtendTimer(RedisLock redisLock, ILogger logger) { long interval = (long)redisLock.ExpiryTime.TotalMilliseconds / 2; redisLock.KeepAliveTimer = new Timer( state => { ExtendLockLifetime(redisLock, logger); }, null, interval, interval); }
private static async Task StartAsync(RedisLock redisLock, ILogger logger) { if (redisLock.WaitTime != TimeSpan.Zero) { redisLock.Status = DistributedLockStatus.Waiting; Stopwatch stopwatch = Stopwatch.StartNew(); while (!redisLock.IsAcquired && stopwatch.Elapsed <= redisLock.WaitTime) { logger.LogDebug($"锁在等待... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); redisLock.Status = await AcquireResourceAsync(redisLock, logger).ConfigureAwait(false); if (!redisLock.IsAcquired) { logger.LogDebug($"锁继续等待... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); if (redisLock.CancellationToken == null) { await Task.Delay((int)redisLock.RetryTime.TotalMilliseconds).ConfigureAwait(false); } else { await Task.Delay((int)redisLock.RetryTime.TotalMilliseconds, redisLock.CancellationToken.Value).ConfigureAwait(false); } } } if (!redisLock.IsAcquired) { logger.LogDebug($"锁等待超时... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); redisLock.Status = DistributedLockStatus.Expired; } stopwatch.Stop(); } else { //不等待 redisLock.Status = await AcquireResourceAsync(redisLock, logger).ConfigureAwait(false); } if (redisLock.IsAcquired) { logger.LogDebug($"锁获取成功... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); StartAutoExtendTimer(redisLock, logger); } else { logger.LogDebug($"锁获取失败... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); } }
/// <summary> /// ReleaseResourceAsync /// </summary> /// <param name="redisLock"></param> /// <param name="logger"></param> /// <returns></returns> /// <exception cref="LockException"></exception> internal static async Task ReleaseResourceAsync(RedisLock redisLock, ILogger logger) { StopKeepAliveTimer(redisLock, logger); if (redisLock.NotUnlockWhenDispose) { logger.LogDebug($"自动延期停止,但锁等他自己过期... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); return; } List <RedisKey> redisKeys = new List <RedisKey>(); List <RedisValue> redisValues = new List <RedisValue>(); AddReleaseResourceRedisInfo(redisLock, redisKeys, redisValues); IDatabase database = await GetDatabaseAsync(redisLock.Options.ConnectionSetting, logger).ConfigureAwait(false); try { RedisResult result = await database.ScriptEvaluateAsync( _loadedLuas.LoadedUnLockLua, redisKeys.ToArray(), redisValues.ToArray()).ConfigureAwait(false); int rt = (int)result; if (rt == 1) { logger.LogDebug($"锁已经解锁... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); } else { throw Exceptions.DistributedLockUnLockFailed(threadId: Thread.CurrentThread.ManagedThreadId, resources: redisLock.Resources); } } catch (RedisServerException ex) when(ex.Message.StartsWith("NOSCRIPT", StringComparison.InvariantCulture)) { InitLoadedLuas(redisLock.Options.ConnectionSetting, logger); await ReleaseResourceAsync(redisLock, logger).ConfigureAwait(false); } catch (Exception ex) when(ex is not LockException) { logger.LogDebug(ex, "锁解锁失败... {ThreadID}, {Resources}", Thread.CurrentThread.ManagedThreadId, redisLock.Resources); throw Exceptions.DistributedLockUnLockFailed(threadId: Thread.CurrentThread.ManagedThreadId, resources: redisLock.Resources, innerException: ex); } }
private static void AddReleaseResourceRedisInfo(RedisLock redisLock, List <RedisKey> redisKeys, List <RedisValue> redisValues) { /// keys: resource1,resource2,resource3 /// argv:3(resource_count), resource1_value, resource2_value, resource3_value foreach (string item in redisLock.Resources) { redisKeys.Add(item); } redisValues.Add(redisKeys.Count); foreach (string item in redisLock.ResourceValues) { redisValues.Add(item); } }
private static void StopKeepAliveTimer(RedisLock redisLock, ILogger logger) { if (redisLock.KeepAliveTimer != null) { lock (redisLock.StopKeepAliveTimerLockObj) { if (redisLock.KeepAliveTimer != null) { redisLock.KeepAliveTimer.Change(Timeout.Infinite, Timeout.Infinite); redisLock.KeepAliveTimer.Dispose(); redisLock.KeepAliveTimer = null; logger.LogDebug($"锁停止自动延期,Resources:{redisLock.Resources.ToJoinedString(",")}"); } } } }
private static void ExtendLockLifetime(RedisLock redisLock, ILogger logger) { if (redisLock.Status != DistributedLockStatus.Acquired) { logger.LogDebug($"锁已不是获取状态,停止自动延期... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}, Status:{redisLock.Status}"); return; } logger.LogDebug($"锁在自动延期... ThreadID: {Thread.CurrentThread.ManagedThreadId}, Resources:{redisLock.Resources.ToJoinedString(",")}"); List <RedisKey> redisKeys = new List <RedisKey>(); List <RedisValue> redisValues = new List <RedisValue>(); AddAcquireOrExtendRedisInfo(redisLock, redisKeys, redisValues, logger); IDatabase database = GetDatabase(redisLock.Options.ConnectionSetting, logger); try { RedisResult result = database.ScriptEvaluate( _loadedLuas.LoadedExtendLua, redisKeys.ToArray(), redisValues.ToArray()); int rt = (int)result; if (rt != 1) { logger.LogError($"RedisLock 延期 失败. Resources:{redisLock.Resources.ToJoinedString(",")}, Status:{redisLock.Status}"); return; } redisLock.ExtendCount++; } catch (RedisServerException ex) when(ex.Message.StartsWith("NOSCRIPT", StringComparison.InvariantCulture)) { logger.LogError(ex, "NOSCRIPT, will try again."); InitLoadedLuas(redisLock.Options.ConnectionSetting, logger); ExtendLockLifetime(redisLock, logger); } }