分布式redislock使用注意事项

采用技术框架:csredis 

业务逻辑:单个数据做判重,不重复增加,后续update

实现:使用redislock +分布式redis key的方式双重机制

问题:一个过程耗时72s

代码:

public async Task<long> AddBasicCustomerLog(BaseEmpInfo empInfo, long buid, DateTime dataTime, IDbConnection connection, IDbTransaction transaction = null)
        {
             
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            StringBuilder stringBuilder = new StringBuilder("【AddBasicCustomerLog】buid=" + buid);
            long res = 0;
            string token = DateTime.Now.ToLongTimeString() + Guid.NewGuid().ToString();
            string lockKey = LockKeyCustomerLog + buid ;
            CSRedisClientLock lockRedis = null;
            //这块还是要释放,不然还是有问题,会导致程序占用的太多了 todo
            int i = 0;
            while (lockRedis == null)
            {
                lockRedis = RedisHelper.Lock(lockKey, 15);

                //lockRedis = await _database.LockTakeAsync(lockKey, token, TimeSpan.FromSeconds(15));
                if (lockRedis == null)
                {
                    //if (connection.State != ConnectionState.Closed)
                    //{
                    //    connection.Close();
                    //}
                    Thread.Sleep(3000);//等待3s
                }
                i++;
                if (i > 3)
                {
                    _logger.LogCritical(i + "次获取锁,依然失败,本次放弃对buid:" + buid + "的新增CustomerLog事件");
                    return res;
                }
            }
            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
            }
            stringBuilder.AppendLine("2拿锁" + stopWatch.Elapsed.TotalSeconds);
            //获取锁后再次查看是否已有  如果没有就新增
            var statisDefeat = await _statisCustomerLogRepository.GetStatisCustomerIdByEmpId(buid, dataTime, connection, transaction);
            stringBuilder.AppendLine("3GetStatisCustomerIdByEmpId-" + stopWatch.Elapsed.TotalSeconds);
            if (statisDefeat <= 0)
            {
                res = await _statisCustomerLogRepository.SavBasicRecord(empInfo, buid, dataTime, connection, transaction);
                stringBuilder.AppendLine("4SavBasicRecord-" + stopWatch.Elapsed.TotalSeconds);
            }
            else
            {
                return statisDefeat;
            }

            //await _database.LockReleaseAsync(lockKey, token);
            lockRedis?.Unlock();

            stopWatch.Stop();
            stringBuilder.AppendLine("3结束" + stopWatch.Elapsed.TotalSeconds);
            if (stopWatch.Elapsed.TotalSeconds > 2)
            {
                _logger.LogInformation(stringBuilder.ToString());
            }
            return res;
        }

  问题1:如果是一个已存在的数据 那么 可能存在没有释放lock , return statisDefeat;这一步

       问题2:lock 没有释放,自动延期

       问题3:lockrediskey 不唯一

 /// <summary>开启分布式锁,若超时返回null</summary>
  /// <param name="name">锁名称</param>
  /// <param name="timeoutSeconds">超时(秒)</param>
  /// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
  /// <returns></returns>
  public static CSRedisClientLock Lock(
    string name,
    int timeoutSeconds,
    bool autoDelay = true)
  {
    return RedisHelper<TMark>.Instance.Lock(name, timeoutSeconds, true);
  }
/// <summary>开启分布式锁,若超时返回null</summary>
    /// <param name="name">锁名称</param>
    /// <param name="timeoutSeconds">超时(秒)</param>
    /// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
    /// <returns></returns>
    public CSRedisClientLock Lock(string name, int timeoutSeconds, bool autoDelay = true)
    {
      name = "CSRedisClientLock:" + name;
      DateTime now = DateTime.Now;
      while (DateTime.Now.Subtract(now).TotalSeconds < (double) timeoutSeconds)
      {
        string str = Guid.NewGuid().ToString();
        if (this.Set(name, (object) str, timeoutSeconds, new RedisExistence?(RedisExistence.Nx)))
          return new CSRedisClientLock(this, name, str, timeoutSeconds, autoDelay);
        Thread.CurrentThread.Join(3);
      }
      return (CSRedisClientLock) null;
    }

  解决方案:1:return 前一定释放lock 2:redislock设置为可过期的 3:设置rediskey的时候设置成业务唯一的

新代码

public async Task<long> AddBasicCustomerLog(BaseEmpInfo empInfo, long buid, long compId, DateTime dataTime, IDbConnection connection, IDbTransaction transaction = null)
        {
             
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            StringBuilder stringBuilder = new StringBuilder("【AddBasicCustomerLog】buid=" + buid);
            long res = 0;
            string token = DateTime.Now.ToLongTimeString() + Guid.NewGuid().ToString();
            string lockKey = LockKeyCustomerLog + buid + "_" + compId;
            CSRedisClientLock lockRedis = null;
            //这块还是要释放,不然还是有问题,会导致程序占用的太多了 todo
            int i = 0;
            while (lockRedis == null)
            {
                lockRedis = RedisHelper.Lock(lockKey, 15, false);

                //lockRedis = await _database.LockTakeAsync(lockKey, token, TimeSpan.FromSeconds(15));
                if (lockRedis == null)
                {
                    //if (connection.State != ConnectionState.Closed)
                    //{
                    //    connection.Close();
                    //}
                    Thread.Sleep(3000);//等待3s
                }
                i++;
                if (i > 3)
                {
                    _logger.LogCritical(i + "次获取锁,依然失败,本次放弃对buid:" + buid + "的新增CustomerLog事件");
                    return res;
                }
            }
            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
            }
            stringBuilder.AppendLine("2拿锁" + stopWatch.Elapsed.TotalSeconds);
            //获取锁后再次查看是否已有  如果没有就新增
            var statisDefeat = await _statisCustomerLogRepository.GetStatisCustomerIdByEmpId(buid, dataTime, connection, transaction);
            stringBuilder.AppendLine("3GetStatisCustomerIdByEmpId-" + stopWatch.Elapsed.TotalSeconds);
            if (statisDefeat <= 0)
            {
                res = await _statisCustomerLogRepository.SavBasicRecord(empInfo, buid, dataTime, connection, transaction);
                stringBuilder.AppendLine("4SavBasicRecord-" + stopWatch.Elapsed.TotalSeconds);
            }
            else
            {
                res = statisDefeat;
            }

            //await _database.LockReleaseAsync(lockKey, token);
            lockRedis?.Unlock();

            stopWatch.Stop();
            stringBuilder.AppendLine("3结束" + stopWatch.Elapsed.TotalSeconds);
            if (stopWatch.Elapsed.TotalSeconds > 2)
            {
                _logger.LogInformation(stringBuilder.ToString());
            }
            return res;
        }

  

posted @ 2020-12-18 15:32  赵坤坤  阅读(1381)  评论(0编辑  收藏  举报