• 分布式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;
            }
    

      

  • 相关阅读:
    Mac OS X 下安装Raspbian系统
    Tiny4412 uboot Makefile 分析
    Tiny4412增强版底板串口电路与设置
    为Debian搞定Mercury MW150US无线网卡驱动
    树莓派的启动过程
    xml格式转换为Bean
    j2ee之hibernate工具类
    验证码代码
    j2ee之AJAX的二级联动
    j2ee之原生AJAX
  • 原文地址:https://www.cnblogs.com/zhaokunbokeyuan256/p/14155200.html
Copyright © 2020-2023  润新知