namespace StackExchange.Redis
{
/// <summary>
/// 扩展 StackExchange.Redis
/// </summary>
public static class IDatabaseExtension
{
/// <summary>
/// 加分布式redis锁
/// </summary>
/// <param name="database"></param>
/// <param name="cachekey">业务key</param>
/// <param name="timeoutSeconds">过期时间默认5秒</param>
/// <param name="isDelay">是否自动延迟</param>
/// <returns></returns>
public static (bool IsSuccessLock, string value) Lock(this IDatabase database, string cachekey, int timeoutSeconds =5, bool isDelay=true)
{
string key = GetLockKey(cachekey);
string value = Guid.NewGuid().ToString();
//转换毫秒
int timeoutMilliseconds = timeoutSeconds * 1000;
TimeSpan expiry = TimeSpan.FromMilliseconds(timeoutMilliseconds);
//刷新时间间隔
int refreshMillisecond = (int)(timeoutMilliseconds / 2);
bool isSetOk = database.StringSet(key: key, value: value, expiry: expiry, when: When.NotExists);
if (isSetOk && isDelay)
{
//自动延迟 使用 Timer 自动回调实现
Timer timer = new Timer((state) => database.Delay(key, value, timeoutMilliseconds), null, refreshMillisecond, refreshMillisecond);
//如果 添加失败 到这步说明 有问题 所以需要 删除 redis 和 并发字典
bool isAddOK = AutoDelayTimers.Intrance.TryAdd(key, timer);
if (!isAddOK)
{
//解锁
//如果添加不成功说明 key还存活 就要删除key AutoDelayTimers中的删除 redis的也要删除
timer?.Dispose();
database.SafedUnLock(key, value);
return (false, string.Empty);
}
}
return (true, isSetOk ? value : string.Empty);
}
/// <summary>
/// 延迟 使用的是lua脚本 所以不用考虑多线程 并发
/// </summary>
/// <param name="database"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="milliseconds"></param>
public static void Delay(this IDatabase database, string key, string value, int milliseconds)
{
if (!AutoDelayTimers.Intrance.ContainsKey(key)) return;
string luaStript = @"local value = redis.call('Get',@key)
if value == @value then
redis.call('PEXPIRE',@key,@milliseconds)
return 1
end
return 0
";
var script = LuaScript.Prepare(luaStript);
RedisResult result = database.ScriptEvaluate(script, new { key = key, value = value, milliseconds = milliseconds });
if ((int)result == 0)
{
AutoDelayTimers.Intrance.CloseTimer(key);
}
return;
}
/// <summary>
/// 安全解锁 用的是lua 脚本 lua具有原子性 所以不用考虑多线程并发问题
/// </summary>
/// <param name="database"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void SafedUnLock(this IDatabase database, string key, string value)
{
AutoDelayTimers.Intrance.TryRemove(key);
string luaScript = @" local currentvalue = redis.call('Get',@key)
if (currentvalue==@value) then
redis.call('DEL',@key)
return 1
else
return 0
end
";
var script = LuaScript.Prepare(luaScript);
RedisResult result = database.ScriptEvaluate(script, new { key = key });
}
/// <summary>
/// 获取 key 的转换后的值
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
internal static string GetLockKey(string cacheKey)
{
return $"test:locker:{cacheKey.Replace(":", "-")}";
}
}
}