You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

83 lines
3.2 KiB

using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Easy.Realization;
internal static class IDatabaseExtension
{
#region Distributed Locker
internal static async Task<(bool Success, string LockValue)> LockAsync(this IDatabase redisDb, string cacheKey, int timeoutSeconds = 5, bool autoDelay = true)
{
var lockKey = GetLockKey(cacheKey);
var lockValue = Guid.NewGuid().ToString();
var timeoutMilliseconds = timeoutSeconds * 1000;
var expiration = TimeSpan.FromMilliseconds(timeoutMilliseconds);
bool flag = await redisDb.StringSetAsync(lockKey, lockValue, expiration, When.NotExists, CommandFlags.None);
if (flag && autoDelay)
{
var refreshMilliseconds = (int)(timeoutMilliseconds / 2.0);
var autoDelayTimer = new Timer(timerState => Delay(redisDb, lockKey, lockValue, timeoutMilliseconds), null, refreshMilliseconds, refreshMilliseconds);
var addResult = AutoDelayTimers.TryAdd(lockKey, autoDelayTimer);
if (!addResult)
{
autoDelayTimer?.Dispose();
await redisDb.SafedUnLockAsync(cacheKey, lockValue);
return (false, null);
}
}
return (flag, flag ? lockValue : null);
}
internal static async Task<bool> SafedUnLockAsync(this IDatabase redisDb, string cacheKey, string lockValue)
{
var lockKey = GetLockKey(cacheKey);
AutoDelayTimers.CloseTimer(lockKey);
var script = @"local invalue = @value
local currvalue = redis.call('get',@key)
if(invalue==currvalue) then redis.call('del',@key)
return 1
else
return 0
end";
var parameters = new { key = lockKey, value = lockValue };
var prepared = LuaScript.Prepare(script);
var result = (int)await redisDb.ScriptEvaluateAsync(prepared, parameters);
return result == 1;
}
internal static void Delay(IDatabase redisDb, string key, string value, int milliseconds)
{
if (!AutoDelayTimers.ContainsKey(key))
return;
// local ttltime = redis.call('PTTL', @key)
var script = @"local val = redis.call('GET', @key)
if val==@value then
redis.call('PEXPIRE', @key, @milliseconds)
return 1
end
return 0";
object parameters = new { key, value, milliseconds };
var prepared = LuaScript.Prepare(script);
var result = redisDb.ScriptEvaluateAsync(prepared, parameters, CommandFlags.None).GetAwaiter().GetResult();
if ((int)result == 0)
{
AutoDelayTimers.CloseTimer(key);
}
return;
}
internal static string GetLockKey(string cacheKey)
{
return $"adnc:locker:{cacheKey.Replace(":", "-")}";
}
#endregion Distributed Locker
}