ASP.NET Core中借助CSRedis实现安全高效的分布式锁

引言


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


640?wx_fmt=png


问题:


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


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


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


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


► Setnx


命令:SETNX key value


说明:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。


时间复杂度:O(1)


返回值:设置成功,返回1 ; 设置失败,返回 0


► Getset


命令:GETSET key value


说明:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。当 key 存在但不是字符串类型时,返回一个错误。


时间复杂度:O(1)


返回值:返回给定 key 的旧值; 当 key 没有旧值时,也即是, key 不存在时,返回 nil 。


► Expire


命令:EXPIRE key seconds


说明:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。


时间复杂度:O(1)


返回值:设置成功返回 1 ;当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。


► Del


命令:DEL key [key ...]


说明:删除给定的一个或多个 key 。不存在的 key 会被忽略。


时间复杂度:O(N); N 为被删除的 key 的数量。


删除单个字符串类型的 key ,时间复杂度为O(1)。


删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。


返回值:被删除 key 的数量。


好了,命令熟悉之后,下面我们就开始一步一步实现分布式锁。


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


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


640?wx_fmt=png


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已经有数据了,但是没有过期时间


640?wx_fmt=png


B应用:运行正常


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


640?wx_fmt=png


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


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


流程图:


640?wx_fmt=png


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中的情况如下:


640?wx_fmt=png


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


640?wx_fmt=png


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


640?wx_fmt=png


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


640?wx_fmt=png


然后,再执行Lottery.ThriftRpc.exe


640?wx_fmt=png


640?wx_fmt=png


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


四、总结


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

原文地址:http://cnblogs.com/runningsmallguo/p/10322315.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

L Machining Disc Rotors

L Machining Disc Rotors 题意: 圆心为(0,0)半径为R的圆,现在被被n个互不相交的圆切割(圆心和半径会给出),保证这n个彼此之间不会交叉,保证n个圆中不会有某个包含整个大圆的情况。问切割后大圆剩余部分的直径(即两点…

线性代数二之矩阵加速DP——数学作业,Arc of Dream

矩阵加速数学作业descriptionsolutioncodeArc of Dreamdescriptionsolutioncode数学作业 description solution dpdpdp状态转移方程,dpidpi−1∗10lenii(modM)dp_{i}dp_{i-1}*10^{len_i}i\pmod Mdpi​dpi−1​∗10leni​i(modM) nnn巨大,分段矩阵加速 …

pjudge#21655-[PR #5]双向奔赴【状压dp】

正题 题目链接:http://pjudge.ac/contest/951/problem/21655 题目大意 给出一张nnn个点的简单无向图,每条边的两个方向具有不同权值。求一个权值和最小的定向方案使得整张图强连通。 1≤n≤18,−1≤ai,j≤1061\leq n\leq 18,-1\leq a_{i,j}\leq 10^61≤n≤18,−1≤…

YBTOJ洛谷P2387: 魔法森林(LCT)

解析 LCT从板子到算法的入门题吧 有一些不知道的很实用的技巧 把边按a排序从小到大加入边 那么我们只需要维护当前1-n路径上的b的最小值即可 如果这条边两端点本来不连通,就直接link 否则找到路径上b最大的一条边,断掉,再加入当前边&#x…

Shadow Properties之美(二)【Microsoft Entity Framework Core随笔】

接着上一篇Shadow Properties之美(一),我们来继续举一个有点啰嗦的栗子。先看简单需求:某HR系统,需要记录员工资料。需要记录的资料有:员工号(规则:分公司所在城市拼音首字母&#x…

K - Let the Flames Begin

K - Let the Flames Begin 题意: n个人围成一个环,编号分别是1~n,从第一个人开始报数,报道k的人被移除,然后下一个人从1重新报,一直这样进行。问第m给被移除的人报数是多少? 一共T组数据&…

CF1276F-Asterisk Substrings【SAM,线段树合并】

正题 题目链接:https://www.luogu.com.cn/problem/CF1276F 题目大意 给出一个长度为nnn的字符串SSS,现在依次进行如下操作 取出SSS的一个子串TTT。将TTT中的一个字符替换成∗*∗号(也可以不替换) 求最后有多少种不同的TTT。 解题思路 发…

线性代数三之状压DP的矩阵加速——Quad Tiling,Bus公交线路

状压与矩阵加速的藕断丝连Quad Tilingdescriptionsolutioncode[Hnoi2010]Bus 公交线路descriptionsolutioncodeQuad Tiling description solution 设dpi,S:dp_{i,S}:dpi,S​: iii列的状态为SSS的方案数&#xff0c;最后答案为dpn,(1<<4)−1dp_{n,(1<<4)-1}dpn,(…

codeforces:812(div2):总结

前言 比较水的一场比赛 E题几乎是一本通原题而我还是不会做qwq A - Sagheer and Crossroads 有一个十字路口&#xff0c;给出四个路口的车是否可以左转/右转/直行&#xff0c;并且给出每个路口的行人是否可以通过&#xff0c;求是否出现车和人冲突的情况 阅读理解题&#xff…

如何在ASP.NET Core程序启动时运行异步任务(2)

原文&#xff1a;Running async tasks on app startup in ASP.NET Core (Part 2)作者&#xff1a;Andrew Lock译者&#xff1a;Lamond Lu在我的上一篇博客中&#xff0c;我介绍了如何在ASP.NET Core应用程序启动时运行一些一次性异步任务。本篇博客将继续讨论上一篇的内容&…

Gym 102798A Golden Spirit

VJ链接 题意&#xff1a; 河的两岸各有n个人&#xff0c;中间有个桥&#xff0c;过桥时间为t&#xff0c;所有人过桥后要休息x时间&#xff0c;你每次可以带一个人过桥&#xff08;每次最多只能带一个人&#xff09;&#xff0c;问将所有人带到对岸并带回来&#xff0c;最短需…

P8347-「Wdoi-6」另一侧的月【博弈论,结论】

正题 题目链接:https://www.luogu.com.cn/problem/P8347 题目大意 给出一棵树&#xff0c;两个人轮流操作。 操作者可以选择一个点删除&#xff0c;然后选择一个剩下的连通块&#xff0c;删除其他连通块。 操作完成后只剩下一个点的人失败&#xff0c;求是否先手必败。 1≤…

线性代数四之动态DP(广义矩阵加速)——Can you answer these queries III,保卫王国

动态DP——广义矩阵加速SP1716 GSS3 - Can you answer these queries IIIdescriptionsolutioncode[NOIP2018 提高组] 保卫王国descriptionsolutioncode动态DP能矩阵加速要满足外层操作符对内层操作符具有分配率加法对于乘法就具有分配率(ab)*ca*cb*c SP1716 GSS3 - Can you a…

洛谷P4219 大融合(LCT、虚子树)

解析 本题需要用LCT维护子树大小 然后我就不会了 然后我就用树剖水过去了 又快又好写&#xff0c;真香 现在详细聊聊如何用LCT维护子树信息 每个结点再定义一个新的变量记录所有虚儿子的信息 然后…完了&#xff1f; 告别盲目pushup&#xff0c;我们来详细聊聊在哪里需要更新…

.Net Core跨平台应用研究-HelloArm(串口篇)

引言为了验证采用dotnet core技术开发的物联网设备数据采集接入服务应用是否能在高性价比的linux嵌入式平台运行&#xff0c;针对dotnet core应用程序进行嵌入式linux环境的发布部署运行验证研究。硬件环境硬件系统经过对比筛选&#xff0c;选用了友善之臂出品的NanoPC-T3 Plus…

H - Message Bomb Gym - 102798H

H - Message Bomb Gym - 102798H 题意&#xff1a; 有n个团队&#xff0c;m个人&#xff0c;s个操作 操作1&#xff1a;学生x加入y团队 操作2&#xff1a;学生x推出y团队 操作3&#xff1a;学生x在团队y发送一个信号&#xff0c;在团队y内的所有成员&#xff08;除了x&#x…

Loj#3005-「JOISC 2015 Day 4」Limited Memory【交互题】

正题 题目链接:https://loj.ac/p/3005 题目大意 有一个长度为nnn的括号串SSS&#xff0c;其中包括[]和<>两种括号类型&#xff0c;一个合法的括号串要求同类型的括号一一对应。 你每次可以询问SSS中的一个字符并且传递一个[0,222)[0,2^{22})[0,222)的数字到下一次。 …

[数论系列一]C Looooops,跳跳棋,The Luckiest number,CF906D Power Tower,Minimal Power of Prime,仪仗队,LCMSUM

文章目录C Looooopsdescriptionsolutioncode跳跳棋descriptionsolutioncodeThe Luckiest numberdescriptionsolutioncodeCF906D Power TowerdescriptionsolutioncodeMinimal Power of Primedescriptionsolutioncode[SDOI2008]仪仗队descriptionsolutioncodeLCMSUMdescriptionso…

P4299 首都(LCT、重心)

解析 动态维护树的重心 关键性质&#xff1a;两棵树合并时&#xff0c;新的重心一定在两个原重心之间的路径上 把两个重心之间的路径提出来&#xff0c;利用splay本身的二叉结构二分即可 注意虚子树信息的处理 不要忘记pushdown 代码 #include<bits/stdc.h> using nam…

.NET Core使用微软AI认知服务识别文字语言

点击上方蓝字关注“汪宇杰博客”识别一段文字的语言有多种途径&#xff0c;在这个以AI为热点的时代&#xff0c;我们也可以给自己的应用强行加上AI&#xff0c;然后就能加上“智慧”的名字“自主研发成功”后去吹牛逼。今天我带大家来看看如何使用微软智慧云Azure提供的AI认知服…