分布式锁

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之品鉴之旅(七)的更多相关文章

  1. Redis之品鉴之旅(一)

    Redis之品鉴之旅(一) 好知识就如好酒,需要我们坐下来,静静的慢慢的去品鉴.Redis作为主流nosql数据库,在提升性能的方面是不可或缺的.下面就拿好小板凳,我们慢慢的来一一品鉴. 1)redi ...

  2. Redis之品鉴之旅(六)

    持久化 快照的方式(RDB) 文件追加方式(AOF) 快照形式: save和bgsave能快速的备份数据.但是.........., Save命令:将内存数据镜像保存为rdb文件,由于redis是单线 ...

  3. Redis之品鉴之旅(五)

    Redis事务 原子性:就是最小的单位 一致性:好多命令,要么全部执行成功,要么全部执行失败 隔离性:一个会话和另一个会话之间是互相隔离的 持久性:执行了就执行了,数据保存在硬盘上 典型例子:银行转账 ...

  4. Redis之品鉴之旅(二)

    2)hash类型,上代码 using (RedisClient client = new RedisClient("127.0.0.1", 6379, "12345&qu ...

  5. Redis之品鉴之旅(四)

    发布订阅,简单场景下的发布订阅完全可以使用. 可以简单的理解,将一个公众号视为发布者,关注公众号的人视作订阅者,公众号发布一条文章或者消息,凡事订阅公众号的都可以收到消息.一个人可以订阅多个公众号,一 ...

  6. Redis之品鉴之旅(三)

    3)Set,可以去重的.无序的集合.可以取交集.并集.zset(sorted set),有序的.去重的集合,排序不是根据value排序,而是根据score排序. using (RedisClient ...

  7. redis成长之路——(七)

    扩展性封装 虽说现在StackExchange.Redis免费,万一到时候和servicestack.redis一样要收费呢,所以先留一口,后续的可以再处理 实例代码点击这里查看 redis成长之路- ...

  8. Java8之旅(七) - 函数式备忘录模式优化递归

    前言 在上一篇开始Java8之旅(六) -- 使用lambda实现Java的尾递归中,我们利用了函数的懒加载机制实现了栈帧的复用,成功的实现了Java版本的尾递归,然而尾递归的使用有一个重要的条件就是 ...

  9. Redis 设计与实现 (七)--事务

    事务 *ACID,指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability) redi ...

随机推荐

  1. Qt元对象和属性系统详解

    Qt 是一个用标准 C++ 编写的跨平台开发类库,它对标准 C++ 进行了扩展,引入了元对象系统.信号与槽.属性等特性,使应用程序的开发变得更高效. 本节将介绍 Qt 的这些核心特点,对于理解和编写高 ...

  2. 【springcloud】API Gateway 的路由和过滤(Zuul--1)

    转自:https://blog.csdn.net/pengjunlee/article/details/87084646 Zuul是什么? API Gateway 是随着微服务(Microservic ...

  3. 由Eratosthenes筛法演变出的一种素数新筛法

    这两天和walls老师交流讨论了一个中学竞赛题,我把原题稍作增强和变形,得到如下一个题: 从105到204这100个数中至少要选取多少个数才能保证选出的数中必有两个不是互素的? 我们知道最小的几个素数 ...

  4. Java并发之AQS原理解读(一)

    前言 本文简要介绍AQS以及其中两个重要概念:state和Node. AQS 抽象队列同步器AQS是java.util.concurrent.locks包下比较核心的类之一,包括AbstractQue ...

  5. Kubernetes-kubectl介绍

    前言 本篇是Kubernetes第三篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战.本篇重要介绍kubectl的使用. Kubernetes系列文章: Kubernetes介绍 Kuber ...

  6. Identity用户管理入门六(判断是否登录)

    目前用户管理的增删改查及登录功能已经全部实现,但存在一个问题,登录后要取消登录按钮显示退出按钮,未登录应该有注册按钮,现实现过程如下 一.Startup.cs中增加服务 app.UseAuthenti ...

  7. web、html概念快速入门

    1.C/S和B/S 总结,理论上如果硬件不考虑(带宽.(图像资源加载)显卡等),B/S可以完全替代C/S: 2.静态资源和动态资源 其中,访问时动态资源,服务器后台也会通过视图解析器转换成静态资源,因 ...

  8. jq给动态标签绑定事件

    $(document).on("click", ".autocompleteDiv .autocomplete_ul li", function () { lo ...

  9. 常见shell脚本测试题 if/case语句

    1.检查用户家目录中的 test.sh 文件是否存在,并且检查是否有执行权限2.提示用户输入100米赛跑的秒数,要求判断秒数大于0且小于等于10秒的进入选拔赛,大于10秒的都淘汰,如果输入其它字符则提 ...

  10. 放码来战!HMS Core线上Codelabs挑战赛正式开始

    亲爱的开发者,在1024程序员节即将到来之际,HMS Core准备了一场线上Codelabs挑战赛,现向你发出诚挚邀请,希望你能将新奇的想法和对产品的思考融入代码,用技术与世界对话. HMS Core ...