引言:最近回头看了看开发的.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 = ;//单位是毫秒
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 > )
redisClient.Expire(key, TimeSpan.FromMilliseconds(expireMS));
return true;
}
return false;
} public static bool Remove(string key)
{
return redisClient.Del(key) > ;
}

上面的代码中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 = ;//单位是毫秒
var currentTime = DateTime.Now.ToUnixTime(true); if (SetNxV2("lockkey",DateTime.Now.ToUnixTime(true)+lockTimeout))
{
//设置过期时间
redisClient.Expire("lockkey", TimeSpan.FromMilliseconds());
//TODO:一些业务逻辑代码 Console.WriteLine("处理业务ing");
Thread.Sleep(); 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());
//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的文章我只总结工作中遇到的一些问题,关于文章中的源码,我就不提供了,太简单了。后面我会不定期分享一些Redis的问题,希望大家多多支持。

作者:郭峥

出处:http://www.cnblogs.com/runningsmallguo/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

Redis系列文章总结:ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁的更多相关文章

  1. 【Blazor】在ASP.NET Core中使用Blazor组件 - 创建一个音乐播放器

    前言 Blazor正式版的发布已经有一段时间了,.NET社区的各路高手也创建了一个又一个的Blazor组件库,其中就包括了我和其他小伙伴一起参与的AntDesign组件库,于上周终于发布了第一个版本0 ...

  2. ASP.NET Core中的依赖注入(3): 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

  3. ASP.NET Core中使用漏桶算法限流

    漏桶算法是限流的四大主流算法之一,其应用场景各种资料中介绍的不多,一般都是说应用在网络流量控制中.这里举两个例子: 1.目前家庭上网都会限制一个固定的带宽,比如100M.200M等,一栋楼有很多的用户 ...

  4. ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解 (转载)

    “传导体” HttpContext 要理解 HttpContext 是干嘛的,首先,看图 图一 内网访问程序 图二 反向代理访问程序 ASP.NET Core 程序中,Kestrel 是一个基于 li ...

  5. ASP.NET Core 中 HttpContext 详解与使用 | Microsoft.AspNetCore.Http 详解

    笔者没有学 ASP.NET,直接学 ASP.NET Core ,学完 ASP.NET Core MVC 基础后,开始学习 ASP.NET Core 的运行原理.发现应用程序有一个非常主要的 “传导体” ...

  6. ASP.NET Core 中的SEO优化(4):自定义视图路径及主题切换

    系列回顾 <ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存> <ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图> < ...

  7. ASP.NET Core中使用GraphQL - 第三章 依赖注入

    ASP.NET Core中使用GraphQL ASP.NET Core中使用GraphQL - 第一章 Hello World ASP.NET Core中使用GraphQL - 第二章 中间件 SOL ...

  8. 探索 ASP.Net Core 3.0系列三:ASP.Net Core 3.0中的Service provider validation

    前言:在本文中,我将描述ASP.NET Core 3.0中新的“validate on build”功能. 这可以用来检测您的DI service provider是否配置错误. 具体而言,该功能可检 ...

  9. 探索ASP.Net Core 3.0系列六:ASP.NET Core 3.0新特性启动信息中的结构化日志

    前言:在本文中,我将聊聊在ASP.NET Core 3.0中细小的变化——启动时记录消息的方式进行小的更改. 现在,ASP.NET Core不再将消息直接记录到控制台,而是正确使用了logging 基 ...

随机推荐

  1. session,cookie,sessionStorage,localStorage的区别

    浏览器的缓存机制提供了可以将用户数据存储在客户端上的方式,可以利用cookie,session等跟服务端进行数据交互. 一.cookie和session cookie和session都是用来跟踪浏览器 ...

  2. Percona XtraBackup 备份还原篇

    Percona XtraBackup 安装介绍篇已经对Percona XtraBackup的功能和安装做了比较详细的介绍,那么本篇我们直接进入主题,如何使用XtraBackup做备份.还原,下面主要介 ...

  3. 转自:stuff字符串拼接方法

    下文讲述数据表中将多列合并到一列的方法分享 转自:http://www.maomao365.com/?p=6796

  4. mysql表分区简述

    一. 简介 数据库分区 数据库分区是一种物理数据库设计技术.虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减sql语句的响应时间,同时对于应用来说分区完全是 ...

  5. 安装Jenkins getting started卡住

    前言 jenkins版本:2.32.3 操作系统:windows 卡住信息 如果在安装jenkins时卡在getting startted的界面,如下所示 解决方法 1.打开 运行  输入 servi ...

  6. C#字节数组与字符串转换

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  7. spring cloud(Greenwich.M2) hystrix dashboard 报/actuator/hystrix.stream 404 Not Found的问题

    consumer端不引用spring-boot-starter-actuator的情况 Consumer端会报Unable to connect to Command Metric Stream.新建 ...

  8. 移动端rem flexible方案

    一.px 自动转换为rem sublim Text3 下载本项目,比如:git clone https://github.com/flashlizi/cssrem 进入packages目录(在Subl ...

  9. 面试----你可以手写一个promise吗

    参考:https://www.jianshu.com/p/473cd754311f <!DOCTYPE html> <html> <head> <meta c ...

  10. UVA12545-Bits Equalizer(思维)

    Problem UVA12545-Bits Equalizer Accept: 821  Submit: 4548Time Limit: 3000 mSec Problem Description I ...