分布式锁

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. spring学习日志四

    一.spring对JDBC的支持 JdbcTemplate 简介 为了使 JDBC 更加易于使用, Spring 在 JDBC API 上定义了一个抽象层, 以此建立一个 JDBC 存取框架. 作为 ...

  2. 13.SpringMVC之全局异常

    我们知道,系统中异常包括:编译时异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生.在开发中,不管是dao层 ...

  3. (一)响应式web设计。。。freecodecamp笔记

    HTML基础 HTML 的全称是 HyperText Markup Language(超文本标记语言),它是一种用来描述网页结构的标记语言. h1用作主标题,h2用作副标题,还有h3.h4.h5.h6 ...

  4. hive -- 外部表、内部表、临时表

    1.外部表 关键字:EXTERNAL 外部表创建时需要指定LOCATION 删除外部表时,数据不被删除 CREATE EXTERNAL TABLE page_view(viewTime INT, us ...

  5. Sublime Text3 显示左侧的目录树

    file->open folder选择一个文件夹,打开一个新窗口把原来的关掉 View->Sise Bar->Hide Side Bar就可以了

  6. Spring笔记(3)

    一.JDBC Template基本使用 1.开发步骤 1.1直接使用template 导入spring-jdbc和spring-tx坐标 <!-- JDBC--> <dependen ...

  7. android activity pass data to accessibilityservice 数据传递

    不同类型的 service 传递数据的方式不同,accessibilityservice 运行在独立进程,且被系统接管,比较特别 在 AccessibilityService 的 onCreate 内 ...

  8. Windows下Rancher复制Pod内文件到本地

    Rancher 未提供直接获取 Pod 内文件的工具(如果有请评论告知下,蟹蟹),但提供了 Rancher 的 CLI 客户端,通过 CLI 可以调用 k8s 的 CLI (kubectl) 命令来操 ...

  9. kubernetes使用jenkins Pipeline 部署Nginx

    文章原文 环境需求 kubernetes 未安装参考使用kubeadm安装kubernetes 1.21 jenkins github/gitee/gitlab 静态页面 镜像仓库(我使用的 hub. ...

  10. shell中的引号

    单引号: 所见即所得 原封不动输出 双引号: 与单引号类似 特殊符号进行解析 ( $ $() `` ! ) 无引号: 与双引号类似 支持通配符( {} * ) 反引号: 优先执行 优先执行里面的命令, ...