ASP.NET Core中借助CSRedis实现安全高效的分布式锁
引言
最近回头看了看开发的.NET Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死锁的问题,下面我只展示部分代码:
问题:
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,我们采用时间戳来防止问题一,下面先让我们来看下想当然写法流程图。
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已经有数据了,但是没有过期时间
B应用:运行正常
但是B应用就会一直获取不到锁,导致死锁。
所以上面在获取锁的逻辑还是有问题的,为了解决这个问题,我们采用下面的方式来处理。
三、使用Redis实现分布式锁版本二:双重防死锁
流程图:
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中的情况如下:
我们运行上面的代码,结果如下:
副本.exe中添加一行代码。来模拟这种场景:有A、B两台服务器在跑这个应用,当A台应用跑到:setnx成功但是还没有设置过期时间的时候,突然重启服务,这个时候在分布式环境中就会发生死锁的问题,因为你没有设置过期时间
我们先执行Lottery.ThriftRpc - 副本.exe,等Redis里面有值了,并且这个key是没有过期时间,再关闭掉该程序:
然后,再执行Lottery.ThriftRpc.exe
我们是不是解决了该问题,至于过期时间设置为多少要结合你的具体业务处理时间来计算出一个合理的值,好了,聊到这里关于Redis的分布式锁就讲完了。
四、总结
上面的示例中Redis的组件用的是CSRedisCore,这里只是自己的一点体会,如果你有更好的办法,可以在评论区讨论,关于Redis的理论讲解有太多的文章了,大家可以参考,关于Redis的文章我只总结工作中遇到的一些问题,关于文章中的源码,我就不提供了,太简单了。
原文地址:http://cnblogs.com/runningsmallguo/p/10322315.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
ASP.NET Core中借助CSRedis实现安全高效的分布式锁相关推荐
- .ASP NET Core中缓存问题案例
本篇博客中,我将描述一个关于会话状态(Session State)的问题, 这个问题我已经被询问了好几次了. 问题的场景 创建一个新的ASP.NET Core应用程序 一个用户在会话状态中设置了一个字 ...
- 使用Redis Stream来做消息队列和在Asp.Net Core中的实现
Redis - Wikipedia 写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消 ...
- 【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务
在我的上一篇文章<在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度>,我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计 ...
- 为什么我的会话状态在ASP.NET Core中不工作了?
原文:Why isn't my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookie ...
- Asp.Net Core中利用Seq组件展示结构化日志功能
在一次.Net Core小项目的开发中,掌握的不够深入,对日志记录并没有好好利用,以至于一出现异常问题,都得跑动服务器上查看,那时一度怀疑自己肯定没学好,不然这一块日志不可能需要自己扒服务器日志来查看 ...
- 如何使用C#在ASP.NET Core中轻松实现QRCoder
by Yogi 由瑜伽士 如何使用C#在ASP.NET Core中轻松实现QRCoder (How to easily implement QRCoder in ASP.NET Core using ...
- asp.net core中IHttpContextAccessor和HttpContextAccessor的妙用
分享一篇文章,关于asp.net core中httpcontext的拓展. 现在,试图围绕HttpContext.Current构建你的代码真的不是一个好主意,但是我想如果你正在迁移一个企业类型的应用 ...
- 在asp.net core中使用托管服务实现后台任务
在业务场景中经常需要后台服务不停的或定时处理一些任务,这些任务是不需要及时响应请求的. 在 asp.net中会使用windows服务来处理. 在 asp.net core中,可以使用托管服务来实现,托 ...
- ASP.NET Core中显示自定义错误页面-增强版
之前的博文 ASP.NET Core中显示自定义错误页面 中的方法是在项目中硬编码实现的,当有多个项目时,就会造成不同项目之间的重复代码,不可取. 在这篇博文中改用middleware实现,并且放在独 ...
最新文章
- CouncurrentHashMap源码解析
- VSCode主题:Dracula Official
- 文件系统损坏导致虚拟机无法正常启动的问题及解决方法
- 使用JUnit 5 执行条件和并发测试
- RTMP在NGINX的启动
- 万能无线键盘对码软件_Ceke M87蓝牙机械键盘拆解评测 - Mac小伴侣
- java上传文件以流方式判断类型
- Explorer.exe程序在系统中的作用
- Leetcode-整数反转 C++
- 命令提示符下对用户的操作
- hdu 1078 FatMouse and Cheese(记忆化搜索)
- Python Django 个人博客源码(附个人源码和网站参考)
- java实现聊天室(GUI界面+私聊+群聊)
- 易语言对象--Word之按行定位并写入文本
- rt-thread驱动篇(02)---STM32F429板卡外设驱动添加
- IDCC2018|上海数据港股份有限公司副总裁、数据中心首席架构师王海峰:标杆管理驱动数据中心建设变革...
- Python3,区区几行代码,turtle替我实现了我多年的绘画梦。
- 裕太微递交招股书上会稿:拟募资13亿元,哈勃投资、小米等为股东
- Vera平台,为NFT赋予DeFi衍生价值
- butter滤波器是iir吗_MATLAB IIR滤波器设计函数buttord与butter