Redis之品鉴之旅(七)
分布式锁
1)阻塞锁:
尝试在redis中创建一个字符串结构缓存,方法传入的key,value为锁的过期时间timeout的时间戳。
若redis中没有这个key,则创建成功(即抢到锁),然后立即返回。
若已经有这个key,则先watch,然后校验value中的时间戳是否已经超过当前时间。
若已超过,则尝试使用提交事务的方式覆盖新的时间戳,事务提交成功(即抢到锁),然后立即返回;
若未超过当前时间或事务提交失败(即被别人抢到锁),
如果没有抢到锁,则进入 一个内部优化过的微循环,不断重试。
//这个是阻塞的锁
public static void Show(int i, string key, TimeSpan timeout)
{
using (var client = new RedisClient("127.0.0.1", 6379, "12345", 10))
{
//尝试在redis中创建一个字符串结构缓存,方法传入的key,value为锁的过期时间timeout的时间戳。
// 加了这句话,下面所有的代码都是单线程的执行
using (var datalock = client.AcquireLock("DataLock:" + key,timeout))
{
//库存数量
var inventory = client.Get<int>("inventoryNum");
if (inventory > 0)
{
client.Set<int>("inventoryNum", inventory - 1);
//订单数量
var orderNum = client.Incr("orderNum");
Console.WriteLine($"{i}抢购成功*****线程id:{ Thread.CurrentThread.ManagedThreadId.ToString("00")},库存:{inventory},订单数量:{orderNum}");
}
else
{
Console.WriteLine($"{i}抢购失败");
}
//client.Remove("DataLock:" + key);
Thread.Sleep(100);
}
}
}
using完成方法调用或者显式调用dispose,都会直接清除key。
AcquireLock这句话对应的redis内部源码:
public RedisLock(IRedisClient redisClient, string key, TimeSpan? timeOut)
{
this.redisClient = redisClient;
this.key = key;
//如果返回fasle,则进入一个内部优化过的微循环,不断重试
ExecUtils.RetryUntilTrue(delegate
{
TimeSpan value = timeOut ?? new TimeSpan(365, 0, 0, 0);
DateTime dateTime = DateTime.UtcNow.Add(value);
string lockString = (dateTime.ToUnixTimeMs() + 1).ToString();
//若redis中没有这个key,则创建成功(即抢到锁),然后立即返回。
if (redisClient.SetValueIfNotExists(key, lockString))
{
return true;
}
//若已经有这个key,则先watch,然后校验value中的时间戳是否已经超过当前时间
redisClient.Watch(key);
if (!long.TryParse(redisClient.Get<string>(key), out long result))
{
redisClient.UnWatch();
return false;
}
//通过检查value中时间戳来判断是否过期,并不是利用redis在key上设置expire time来通过key的过期实现,下面的代码是靠事务实现的,如果key不存在了,事务也就不能使用了,所以这个过期时间使用的是value,而不是set方法设置的过期时间。
//若未超过当前时间(即被别人抢到锁)
if (result > DateTime.UtcNow.ToUnixTimeMs())
{
redisClient.UnWatch();
return false;
}
//若已超过,则尝试使用提交事务的方式覆盖新的时间戳,事务提交成功(即抢到锁),然后立即返回;事务提交失败(即被别人抢到锁)
using (IRedisTransaction redisTransaction = redisClient.CreateTransaction())
{
redisTransaction.QueueCommand((Func<IRedisClient, bool>)((IRedisClient r) => r.Set(key, lockString)));
return redisTransaction.Commit();
}
}, timeOut);//传入的timeout还有一个作用,就是控制重试时间,重试超时后则抛异常。
}
//内部优化过的微循环,不断重试,直到
public static void RetryUntilTrue(Func<bool> action, TimeSpan? timeOut = null)
{
int num = 0;
DateTime utcNow = DateTime.UtcNow;
while (!timeOut.HasValue || DateTime.UtcNow - utcNow < timeOut.Value)
{
num++;
if (action())
{
return;
}
SleepBackOffMultiplier(num);
}
throw new TimeoutException($"Exceeded timeout of {timeOut.Value}");
}
可以看出,timeout有两个意思,1:如果成功加锁后锁的过期时间, :2:未成功加锁后阻塞等待的时间。数据锁服务通过检查value中时间戳来判断是否过期,并不是利用redis在key上设置expire time来通过key的过期实现。
2)非阻塞锁
尝试在redis中创建一个字符串结构缓存项,方法传入的key,value无意义,过期时间为传入的timeout。
若redis中没有这个key,则创建成功(即抢到锁),然后立即返回true。若已经有这个key,则立即返回false。
以上过程为全局单线程原子操作,整个过程为独占式操作。
IsLock可以检测key是否存在。
public static void Show(int i, string key, TimeSpan timeout)
{
using (var client = new RedisClient("127.0.0.1", 6379, "12345", 10))
{
// 非阻塞加锁 如果已经存在当前的key,则执行失败,然后false
// 把这个时间 timeout 设置长不就行了吗,但是你需要悠着点
// 没有完全之策 ,一般在生产环境,给一个不要超过3s就可以
bool isLocked = client.Add<string>("DataLock:" + key, key, timeout);
if (isLocked)
{
try
{
//库存数量
var inventory = client.Get<int>("inventoryNum");
if (inventory > 0)
{
client.Set<int>("inventoryNum", inventory - 1);
//订单数量
var orderNum = client.Incr("orderNum");
Console.WriteLine($"{i}抢购成功*****线程id:{ Thread.CurrentThread.ManagedThreadId.ToString("00")},库存:{inventory},订单数量:{orderNum}");
}
else
{
Console.WriteLine($"{i}抢购失败:原因,没有库存");
}
}
catch
{
throw;
}
finally
{
client.Remove("DataLock:" + key);
}
}
else
{
Console.WriteLine($"{i}抢购失败:原因:没有拿到锁");
}
}
}
注意:
timeout即成功加锁后锁的过期时间
利用redis在key上设置expire time来通过key的过期实现。
不要先用IsLock判断是否有锁再用Add加锁,因为这两个操作非原子性操作,期间会被其他操作干
针对上面的两种情况,非阻塞锁会出现库存卖不完的情况,但是性能比较高。
如果架构中采用微服务的形式,并且使用.net进行开发,肯定会选用上面两种情况实现。根据业务需求进行两种选择就可以了。
阻塞锁在asp.net core 3.1中的应用:demo
Redis之品鉴之旅(七)的更多相关文章
- Redis之品鉴之旅(一)
Redis之品鉴之旅(一) 好知识就如好酒,需要我们坐下来,静静的慢慢的去品鉴.Redis作为主流nosql数据库,在提升性能的方面是不可或缺的.下面就拿好小板凳,我们慢慢的来一一品鉴. 1)redi ...
- Redis之品鉴之旅(六)
持久化 快照的方式(RDB) 文件追加方式(AOF) 快照形式: save和bgsave能快速的备份数据.但是.........., Save命令:将内存数据镜像保存为rdb文件,由于redis是单线 ...
- Redis之品鉴之旅(五)
Redis事务 原子性:就是最小的单位 一致性:好多命令,要么全部执行成功,要么全部执行失败 隔离性:一个会话和另一个会话之间是互相隔离的 持久性:执行了就执行了,数据保存在硬盘上 典型例子:银行转账 ...
- Redis之品鉴之旅(二)
2)hash类型,上代码 using (RedisClient client = new RedisClient("127.0.0.1", 6379, "12345&qu ...
- Redis之品鉴之旅(四)
发布订阅,简单场景下的发布订阅完全可以使用. 可以简单的理解,将一个公众号视为发布者,关注公众号的人视作订阅者,公众号发布一条文章或者消息,凡事订阅公众号的都可以收到消息.一个人可以订阅多个公众号,一 ...
- Redis之品鉴之旅(三)
3)Set,可以去重的.无序的集合.可以取交集.并集.zset(sorted set),有序的.去重的集合,排序不是根据value排序,而是根据score排序. using (RedisClient ...
- redis成长之路——(七)
扩展性封装 虽说现在StackExchange.Redis免费,万一到时候和servicestack.redis一样要收费呢,所以先留一口,后续的可以再处理 实例代码点击这里查看 redis成长之路- ...
- Java8之旅(七) - 函数式备忘录模式优化递归
前言 在上一篇开始Java8之旅(六) -- 使用lambda实现Java的尾递归中,我们利用了函数的懒加载机制实现了栈帧的复用,成功的实现了Java版本的尾递归,然而尾递归的使用有一个重要的条件就是 ...
- Redis 设计与实现 (七)--事务
事务 *ACID,指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability) redi ...
随机推荐
- C# 异步锁 await async锁,lock,Monitor,SemaphoreSlim
异步方法内无法使用Monitor 和lock 所以只能用System.Threading.SemaphoreSlim了 //Semaphore (int initialCount, int maxim ...
- WPF---数据绑定(一)
一.何为数据绑定 场景:考虑一个Window上有一个TextBox和一个Slider两个元素,当我们拖动滑动条的时候,会在TextBox中显示当前滑动条的数值:当我们在TextBox中输入一个有效值, ...
- 深入Pulsar Consumer的使用方式&源码分析
原文链接 1.使用前准备 引入依赖: <dependency> <groupId>org.apache.pulsar</groupId> <artifactI ...
- The First Week luckyzpp
一 ,LINUX系列有很多版本,只是我们很少去了解到更多,我们熟知红帽CentOS,Ubuntu,Debian, Kali, Rocky各种版本系列. 二 目前Linux 生产主流版本如下: C ...
- 原生 JS 与 jQuery 中的 AJAX
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更 ...
- Mysql基本知识整理
一.简介 1.什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据. 2.关系型数据库 ...
- You have mail in /var/mail/xxx
因为配置 DDNS, 我添加了个 crontab 定时任务,每隔 1 分钟执行一段 python 脚本 然后就发现 terminal 经常提示 'You have mail in /var/mail/ ...
- 操作系统IO之零拷贝技术
磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝.直接 I/O.异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统 ...
- 图像处理之Canny边缘检测(一)
一:历史 Canny边缘检测算法是1986年有John F. Canny开发出来一种基于图像梯度计算的边缘 检测算法,同时Canny本人对计算图像边缘提取学科的发展也是做出了很多的贡献.尽 管至今已经 ...
- stream流思想应用
1.计算集合中某字段数值和 subTotal = subTotal+ complainCountResult.stream().filter(childSource->childSource.g ...