.NET Redis分布式锁 简单实现锁续约

接口代码

        protected readonly StackExchange.Redis.IDatabase _redisDb;
        protected readonly IAbpRedisCacheDatabaseProvider _abpRedisCacheDatabaseProvider;

        _redisDb = _abpRedisCacheDatabaseProvider.GetDatabase();

        public async Task Update(EntityDto<long> input)
        {
            //创建锁信息,如果需要提升性能,可以把这个Id分成多个key(分段锁)
            var lockInfo = new LockInfo(ToDoItemConsts.LockKey + input.Id.ToString());

            while (!await _redisDb.LockTakeAsync(lockInfo.Key, lockInfo.Value, TimeSpan.FromSeconds(lockInfo.Expiry)))
            {
                await Task.Delay(TimeSpan.FromMilliseconds(RandomHelper.GetRandom(10, 300)));

                // TODO: 超过等待时间,则不再等待
                // if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break;
            }

            //锁续约(1/3过期时间为周期,执行锁续约代码)
            var timer = new Timer(new TimerCallback(DoExtend), lockInfo, TimeSpan.Zero, TimeSpan.FromSeconds(lockInfo.Expiry / 3));

            //try 防止执行异常,没有释放锁
            try
            {
                //锁续约测试
                await Task.Delay(TimeSpan.FromSeconds(lockInfo.Expiry + 1));

                var stock = 0;
                var stockKey = ToDoItemConsts.Stock + input.Id.ToString();
                var redisStock = await _redisDb.StringGetAsync(stockKey);
                if (redisStock.IsNull)//redis不存在,就到数据库找
                {
                    var toDoItem = await _dbContext.Set<ToDoItem>().FirstOrDefaultAsync(x => x.Id == input.Id);
                    stock = toDoItem.Count;
                }
                else
                {
                    stock = (int)redisStock;
                }

                if (stock > 0)
                {
                    stock--;
                    await _redisDb.StringSetAsync(stockKey, stock);
                }
                else
                {
                    throw new UserFriendlyException("库存不足!");
                }
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                //只能释放自己加的锁,因为锁有过期时间,有可能在自己释放前,锁过期了,导致别人加了锁。(可以用锁续命防止锁过期 LockExtendAsync)
                //确保锁释放是原子操作
                await _redisDb.LockReleaseAsync(lockInfo.Key, lockInfo.Value);
                timer.Dispose();
            }
        }

        private void DoExtend(object stateInfo)
        {
            if (!(stateInfo is LockInfo))
                throw new UserFriendlyException($"锁续约出错:{stateInfo}");

            //TODO 如果大于循环时间
            var arg = (LockInfo)stateInfo;
            //将过期时间重置为arg.Expiry
            _redisDb.LockExtend(arg.Key, arg.Value, TimeSpan.FromSeconds(arg.Expiry));
            Console.WriteLine(DateTime.Now + arg.ToString());
        }

外部类代码

    public class LockInfo
    {
        public string Key { get; set; }

        /// <summary>
        /// 生成一个随机值guid用于释放锁的判断
        /// </summary>
        public string Value { get; set; } = Guid.NewGuid().ToString();

        /// <summary>
        /// second
        /// </summary>
        public int Expiry { get; set; } = 9;

        public LockInfo(string key)
        {
            Key = key ?? throw new ArgumentNullException(nameof(key));
        }

        public override string ToString() => $" Key:{Key},Value:{Value},Expiry:{Expiry}";
    }
posted @ 2023-02-13 22:53  cnblogsName  阅读(254)  评论(0编辑  收藏  举报