宝哥软件园

详细说明如何在ASP.Net内核中用CSRedis实现安全高效的分布式锁

编辑:宝哥软件园 来源:互联网 时间:2021-08-26

导读:最近我回头看了一下发达的复习总结。Net Core 2.1项目,其中很多地方使用了Redis实现的分布式锁。虽然死锁预防是在OnResultExecuting方法中完成的,但在某些场景中仍然会出现死锁。下面,我只展示一些代码:

问题:

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

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

你可以考虑一下。首先,让我们用这两个问题往下看。让我们介绍几种常用于使用Redis实现分布式锁的命令。

1.用Redis实现分布式锁的几个常见命令

Setnx

命令:SETNX键值描述:当且仅当键值不存在时,将键值设置为值。如果给定的密钥已经存在,SETNX什么也不做。SETNX是“如果不存在则设置”的缩写(如果不存在则设置)。时间复杂度:O(1)返回值:设置成功,返回1;设置失败并返回0

Getset

命令:GETSET键值描述:将给定键的值设置为值,并返回该键的旧值。当键存在但不是字符串类型时,将返回一个错误。时间复杂度:O(1)返回值:返回给定键的旧值;当键没有旧值时,即当键不存在时,返回nil。

过期

命令:EXPIRE密钥秒描述:设置给定密钥的生存期,当密钥过期(生存期为0)时,将自动删除。时间复杂度:O(1)返回值:设置成功,返回1;当密钥不存在或无法为密钥设置生存期时(例如,您试图在版本2.1.3之前的Redis中更新密钥的生存期),返回0。

德尔

命令:DEL键[键.]备注:删除一个或多个给定的键。不存在的键将被忽略。时间复杂度:O(N);n是删除的键的数量。删除单个字符串类型键的时间复杂度是O(1)。删除单个列表、集合、有序集合或哈希表类型的关键字,时间复杂度为O(M),M为上述数据结构中的元素个数。返回值:删除的键的数量。

好了,在熟悉了命令之后,让我们开始一步一步地实现分布式锁。

二、利用Redis实现分布式锁版本1:与时间戳结合

对于上面setnx设置的默认值1,我们使用时间戳来防止第一个问题。我们来看看想当然的流程图。

流程图:

C#代码实现:

静态void Main(字符串[]args){ var lock time out=5000;//单位为毫秒var current time=datetime。现在。到unixitime(true);If(设置NX('锁定键',当前时间锁定超时,锁定超时)){//todo:部分业务逻辑代码//.//.//最后松开锁Remove(' lock key ');} else {Console。WriteLine('未获得分布式锁');}控制台。ReadKey();} public static bool SetNx(string key,long time,double expireMS) { if (redisClient。SetNx(键,时间)){ if (expireMS 0) redisClient。过期(密钥,时间跨度。from毫秒(expireMS));返回真;}返回false} public static bool Remove(string key){ return redisClient。Del(键)0;}在上面的代码中,我们对value的值使用了timestamp,这个值不是固定值,至少可以保证你删除的密钥确实是你自己的。因此,建议您在设置值时不要设置固定值,最好随机设置。不过,这种写法解决了第一个问题,但这种写法还是有一定风险的。虽然Redis是单线程的,setnx和expire是原子操作,但是setnx先出后过期不是原子操作!部署容器时需要考虑多线程环境和多实例环境等。所以这样写会有问题。

例如,两台服务器A和B现在正在运行这个应用程序。当应用程序A到达:setnx成功,但尚未设置到期时间时,它会突然重新启动服务。此时,分布式环境中会出现死锁,因为您没有设置过期时间。

让我们通过调试展示死锁场景:

一个应用程序:在成功执行到setnx之后,但是在执行到期之前,它关闭了。此时,Redis已经有数据,但没有到期时间

应用:正常运行

但是,B应用程序永远得不到锁,导致死锁。

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

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

流程图:

C#代码实现:

public static void redislock v2(){ var lock time out=5000;//单位为毫秒var current time=datetime。现在。到unixitime(true);If (setnxv2 ('lockkey ',datetime。现在。to unixtime(true)lock time out(){//设置redisclient的到期时间。过期(' lockkey ',时间跨度。从毫秒(5000)开始);//TODO:部分业务逻辑代码Console。WriteLine('处理业务ing ');线程。睡眠(10万);控制台。WriteLine('处理业务ed ');//最后松开锁Remove(' lock key ');} else {//锁未被获取。继续判断和判断时间戳,看锁是否可以重置和获取。var lock value=redis client . get(' lock key ');var time=DateTime。现在. ToUnixTime(真);if(!字符串。is ullrempty(lock value)time lock value。toint 64()){//返回当前时间戳为getset //的固定密钥旧值,判断锁var是否为getsetresult=redisclient。getset ('lockkey ',time)可以用旧值获取;if(getsetResult==null | |(getsetResult!=null getsetresult==lock value)){控制台。writeline('获得Redis锁');//获取lock redis client . expire(' lock key ',timespan)。从毫秒(5000)开始);//TODO:部分业务逻辑代码//.//.console.writeline('处理业务');//最后松开锁Remove(' lock key ');} else {Console。WriteLine('未获得锁');}} else {Console。WriteLine('未获得锁');}}}现在,Redis的情况如下:

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

在副本中添加一行代码。模拟这个场景:有两个服务器,A和B,运行这个应用程序。当A应用程序运行:setnx成功但尚未设置到期时间时,服务会突然重新启动。此时,分布式环境中会出现死锁,因为您没有设置过期时间。

我们首先执行彩票。ThriftRpc-copy。然后在Redis中有一个值并且该密钥没有过期时间后关闭程序:

然后,进行抽奖。ThriftRpc.exe

看看我们是否解决了这个问题。至于设置多少到期时间,我们要根据你具体的业务处理时间来计算一个合理的数值。好了,这里我们已经讨论完了Redis分布式锁。希望对你有帮助。谢谢你。

四.总结:

在上面的例子中,Redis的组件使用了CSRedisCore。这只是我自己的经历。如果你有更好的方法,可以在评论区讨论。关于Redis理论的文章太多了,可以参考一下。我只总结工作中遇到的一些问题。我不会在文章中提供源代码。太简单了。后面我会不时分享一些Redis的问题,希望大家多多支持。

以上就是如何在边肖介绍的ASP.Net核心中实现与CSRedis安全高效的分布式锁集成。希望对大家有帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!

更多资讯
游戏推荐
更多+