分布式锁

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. 【java文件处理】java项目路径下的文件下载处理

    1. controller类: package com.neo.controller; import javax.servlet.http.HttpServletResponse; import or ...

  2. 【mysql】截取查询分析

    1. 慢查询日志 1.1 是什么 (1)MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL ...

  3. Nacos集群部署:

    Nacos集群部署: 官网:    https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html 1: 下载 Nacos1.2.0 链接:http ...

  4. 天翼云安装jdk(注意有坑)

    1.下载jdk8 查看Linux位数,到oracle官网下载对应的jdk ① sudo uname --m  确认32位还是64位 ② https://www.oracle.com/technetwo ...

  5. tensorflow实现Word2vec

    # coding: utf-8 ''' Note: Step 3 is missing. That's why I left it. ''' from __future__ import absolu ...

  6. 虚拟机--第二章java内存区域与内存溢出异常--(抄书)

    这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第二章java内存区域与内存溢出 ...

  7. Java抽象类(abstract)

    抽象类和接口 一.抽象类 1.什么是抽象类? 类和类之间具有共同的特征,将这些特征提取出来,形成的就是抽象类. 类到对象是实例化,对象到类是抽象. 抽象类和具体类是相对的概念."抽象&quo ...

  8. netty系列之:轻轻松松搭个支持中文的服务器

    目录 简介 netty的HTTP支持 netty中使用HTTP的原理 100 (Continue) Status 为netty搭建HTTP服务器 总结 简介 之前讲了那么多关于netty的文章,都是讲 ...

  9. redis内存回收

    1.定时过期expilre expire key TTL 10定时器 主动淘汰 2.惰性过期 被动淘汰 3getCommand expireIfNeed() 设置内存上线 set memory 上线 ...

  10. cs_play

    # -*-coding:utf-8-*-__author__ = "logan.xu"###构造函数#class Role:# n = 123# # 类变量 比如 n = 123# ...