.net core StackExchange.Redis 分布式锁 自动续期

 

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(":", "-")}";
        }

    }
}

  

posted on 2024-07-20 12:33  是水饺不是水饺  阅读(15)  评论(0)    收藏  举报

导航