引言:最近回头看了看开发的.Net Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死锁的问题,下面我只展示部分代码:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

问题:

(1)这里setnx设置的值“1”,我想问,你最后del的这个值一定是你自己创建的吗?

(2)图中标注的步骤1和步骤2不是原子操作,会有死锁的概率吗?

大家可以思考一下先,下面让我们带着这两个问题往下看,下面介绍一下使用Redis实现分布式锁常用的几个命令。

一、使用Redis实现分布式锁常见的几个命令

"color: #ff0000">二、使用Redis实现分布式锁版本一:与时间戳的结合

对于上面的setnx设置的默认值1,我们采用时间戳来防止问题一,下面先让我们来看下想当然写法流程图。

流程图:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

C#代码实现:

static void Main(string[] args)
    {
      var lockTimeout = 5000;//单位是毫秒
      var currentTime = DateTime.Now.ToUnixTime(true);
      if (SetNx("lockkey", currentTime+ lockTimeout,lockTimeout))
      {
        //TODO:一些业务逻辑代码
        //.....
        //.....
        //最后释放锁
        Remove("lockkey");
      }
      else
      {
        Console.WriteLine("没有获得分布式锁");
      }
      Console.ReadKey();
    }

    public static bool SetNx(string key,long time ,double expireMS)
    {
      if (redisClient.SetNx(key, time))
      {
        if (expireMS > 0)
          redisClient.Expire(key, TimeSpan.FromMilliseconds(expireMS));
        return true;
      }
      return false;
    }

    public static bool Remove(string key)
    {
      return redisClient.Del(key) > 0;
    }

上面的代码中value的值我们使用时间戳,不是一个固定的值了,至少能保证你删除的key确实是你自己的,所以,建议大家在设value的值时,不要设置一个固定的值,最好是随机的。但是这样写虽然解决了问题一,但是这种写法还是存在一定的风险,虽然Redis是单线程的并且setnx、expire是原子操作,但是先setnx再expire就不是原子操作了!!!我们要考虑多线程环境和容器部署时多实例环境等等,那这样的写法就会出现问题。

比如:现在有A、B两台服务器在跑这个应用,当A台应用跑到:setnx成功但是还没有设置过期时间的时候,突然重启服务,这个时候在分布式环境中就会发生死锁的问题,因为你没有设置过期时间。

下面我们通过调试来展示死锁的场景:

A应用:在执行到setnx成功但是在执行expire之前宕机了,此时的Redis已经有数据了,但是没有过期时间

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

B应用:运行正常

但是B应用就会一直获取不到锁,导致死锁。

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

所以上面在获取锁的逻辑还是有问题的,为了解决这个问题,我们采用下面的方式来处理。

三、使用Redis实现分布式锁版本二:双重防死锁

流程图:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

C#代码实现:

public static void RedisLockV2()
    {
      var lockTimeout = 5000;//单位是毫秒
      var currentTime = DateTime.Now.ToUnixTime(true);

      if (SetNxV2("lockkey",DateTime.Now.ToUnixTime(true)+lockTimeout))
      {
        //设置过期时间
        redisClient.Expire("lockkey", TimeSpan.FromMilliseconds(5000));
        //TODO:一些业务逻辑代码
        
        Console.WriteLine("处理业务ing");
        Thread.Sleep(100000);

        Console.WriteLine("处理业务ed");
        //最后释放锁
        Remove("lockkey");
      }
      else
      {
        //未获取到锁,继续判断,判断时间戳看看是否可以重置并获取锁
        var lockValue = redisClient.Get("lockkey");
        var time = DateTime.Now.ToUnixTime(true);

        if (!string.IsNullOrEmpty(lockValue) && time> lockValue.ToInt64())
        {
          //再次用当前时间戳getset
          //返回固定key的旧值,旧值判断是否可以获取锁
          var getsetResult = redisClient.GetSet("lockkey", time);
          if (getsetResult == null || (getsetResult != null && getsetResult == lockValue))
          {
            Console.WriteLine("获取到Redis锁了");
            //真正获取到锁
            redisClient.Expire("lockkey", TimeSpan.FromMilliseconds(5000));
            //TODO:一些业务逻辑代码
            //.....
            //.....
            Console.WriteLine("处理业务");
            //最后释放锁
            Remove("lockkey");
          }
          else
          {
            Console.WriteLine("没有获取到锁");
          }

        }
        else
        {
          Console.WriteLine("没有获取到锁");
        }
      }
      
    }

现在,Redis中的情况如下:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

我们运行上面的代码,结果如下:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

副本.exe中添加一行代码。来模拟这种场景:有A、B两台服务器在跑这个应用,当A台应用跑到:setnx成功但是还没有设置过期时间的时候,突然重启服务,这个时候在分布式环境中就会发生死锁的问题,因为你没有设置过期时间

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

我们先执行Lottery.ThriftRpc - 副本.exe,等Redis里面有值了,并且这个key是没有过期时间,再关闭掉该程序:

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

然后,再执行Lottery.ThriftRpc.exe

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

详解ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁

看,我们是不是解决了该问题,至于过期时间设置为多少要结合你的具体业务处理时间来计算出一个合理的值,好了,聊到这里关于Redis的分布式锁就讲完了,希望对你有帮助,谢谢。

四、总结:

上面的示例中Redis的组件用的是CSRedisCore,这里只是自己的一点体会,如果你有更好的办法,可以在评论区讨论,关于Redis的理论讲解有太多的文章了,大家可以参考,关于Redis的文章我只总结工作中遇到的一些问题,关于文章中的源码,我就不提供了,太简单了。后面我会不定期分享一些Redis的问题,希望大家多多支持。

以上所述是小编给大家介绍的ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

标签:
ASP.Net,Core借助CSRedis实现分布式锁,CSRedis实现分布式锁

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
狼山资源网 Copyright www.pvsay.com

《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线

暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。

艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。

《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。