c# 操作redisclient 设置过期时间_C# Redis分布式锁单节点

(给DotNet加星标,提升.Net技能)

转自:热敷哥cnblogs.com/refuge/p/13774008.html

为什么要用分布式锁?

先上一张截图,这是在浏览别人的博客时看到的.

736654d5792c25908e1222287ea890b4.png

在了解为什么要用分布式锁之前,我们应该知道到底什么是分布式锁.

锁按照不同的维度,有多种分类.比如

1、悲观锁,乐观锁;

2、公平锁,非公平锁;

3、独享锁,共享锁;

4、线程锁,进程锁;

等等.

我们平时用的锁,比如 lock,它是线程锁,主要用来给方法,代码块加锁.由于进程的内存单元是被其所有线程共享的,所以线程锁控制的实际是多个线程对同一块内存区域的访问.

有线程锁,就必然有进程锁.顾名思义,进程锁的目的是控制多个进程对共享资源的访问.因为进程之间彼此独立,各个进程是无法控制其他进程对资源的访问,所以只能通过操作系统来控制.比如 Mutex.

但是进程锁有一个前提,那就是需要多个进程在同一个系统中,如果多个进程不在同一个系统,那就只能使用分布式锁来控制了.

分布式锁是控制分布式系统中不同系统之间访问共享资源的一种锁实现.它和线程锁,进程锁的作用都是一样,只是范围不一样.

所以要实现分布式锁,就必须依靠第三方存储介质来存储锁的信息.因为各个进程之间彼此谁都不服谁,只能找一个带头大哥咯;

以下示例需引用NUGET: CSRedisCore

示例一

CSRedisClient redisClient = new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0");
var lockKey = "lockKey";
var stock = 5;//商品库存
var taskCount = 10;//线程数量
redisClient.Del(lockKey);//测试前,先把锁删了.
for (int i = 0; i < taskCount; i++)
{
Task.Run(() =>
{
//获取锁
do
{
//setnx : key不存在才会成功,存在则失败.
var success = redisClient.SetNx(lockKey, 1);
if (success == true)
{
break;
}
Thread.Sleep(TimeSpan.FromSeconds(1));//休息1秒再尝试获取锁
} while (true);
Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");
if (stock <= 0)
{
Console.WriteLine($"库存不足,线程:{Task.CurrentId} 抢购失败!");
redisClient.Del(lockKey);
return;
}
stock--;
//模拟处理业务
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 3)));
Console.WriteLine($"线程:{Task.CurrentId} 消费完毕!剩余 {stock} 个");
//业务处理完后,释放锁.
redisClient.Del(lockKey);
});
}

运行结果:

fa6a7bac484e600014302f20436cbf5b.png

看起来貌似没毛病,实际上上述代码有个致命的问题:

当某个线程拿到锁之后,如果系统崩溃了,那么锁永远都不会被释放.因此,我们应该给锁加一个过期时间,当时间到了,还没有被主动释放,我们就让redis释放掉它,以保证其他消费者可以拿到锁,进行消费.

这里给锁加过期时间也有讲究,不能拿到锁后再加,比如:

......
//setnx : key不存在才会成功,存在则失败.
var success = redisClient.SetNx(lockKey, 1);
if (success == true)
{
redisClient.Set(lockKey, 1, expireSeconds: 5);
break;
}

这样操作的话,获取锁和设置锁的过期时间就不是原子操作,同样会出现上面提到的问题.Redis 提供了一个合而为一的操作可以解决这个问题.

//set : key存在则失败,不存在才会成功,并且过期时间5秒
var success = redisClient.Set(lockKey, 1, expireSeconds: 5, exists: RedisExistence.Nx);

这个问题虽然解决了,但随之产生了一个新的问题:

假设有3个线程A,B,C

当线程A拿到锁后执行业务的时候超时了,超过了锁的过期时间还没执行完,这时候锁被Redis释放了,

于是线程B拿到了锁并开始执行业务逻辑.

当线程B的业务逻辑还没执行完的时候,线程A的业务逻辑执行完了,于是乎就跑去释放掉了锁.

这时候线程C就可以拿到锁开始执行它的业务逻辑.

这不就乱套了么...

因此,线程在释放锁的时候应该判断这个锁还属不属于自己.

所以,在设置锁的时候,redis的value值不能像上面代码那样,随便给个1,而应该给一个随机值,代表当前线程.

var id = Guid.NewGuid().ToString("N");
//获取锁
do
{
//set : key存在则失败,不存在才会成功,并且过期时间5秒
var success = redisClient.Set(lockKey, id, expireSeconds: 5, exists: RedisExistence.Nx);
if (success == true)
{
break;
}
Thread.Sleep(TimeSpan.FromSeconds(1));//休息1秒再尝试获取锁
} while (true);
Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");
.........
//业务处理完后,释放锁.
var value = redisClient.Get<string>(lockKey);
if (value == id)
{
redisClient.Del(lockKey);
}

完美了吗?

不完美.还是老生常谈的问题,取value和删除key 分了两步走,不是原子操作.

并且,这里还不能用pipe,因为需要根据取到的value来决定下一个操作.上面设置过期时间倒是可以用pipe.

所以,这里只能用lua.

完整的代码如下:

CSRedisClient redisClient = new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0");
var lockKey = "lockKey";
var stock = 5;//商品库存
var taskCount = 10;//线程数量
var script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//释放锁的redis脚本
redisClient.Del(lockKey);//测试前,先把锁删了.
for (int i = 0; i < taskCount; i++)
{
Task.Run(() =>
{
var id = Guid.NewGuid().ToString("N");
//获取锁
do
{
//set : key存在则失败,不存在才会成功,并且过期时间5秒
var success = redisClient.Set(lockKey, id, expireSeconds: 5, exists: RedisExistence.Nx);
if (success == true)
{
break;
}
Thread.Sleep(TimeSpan.FromSeconds(1));//休息1秒再尝试获取锁
} while (true);
Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");
if (stock <= 0)
{
Console.WriteLine($"库存不足,线程:{Task.CurrentId} 抢购失败!");
redisClient.Eval(script,lockKey,id);
return;
}
stock--;
//模拟处理业务,这里不考虑失败的情况
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 3)));
Console.WriteLine($"线程:{Task.CurrentId} 消费完毕!剩余 {stock} 个");
//业务处理完后,释放锁.
redisClient.Eval(script, lockKey, id);
});
}

这篇文章只介绍了单节点Redis的分布式锁,因为单节点,所以不是高可用.

多节点Redis则需要用官方介绍的RedLock,这玩意有点绕,我需要捋一捋.

- EOF -

推荐阅读  点击标题可跳转

如何实现一个模块化方案二

.NET中使用DiagnosticSource

三种方式让你轻松监控Entity Framework中的sql流转

看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

723b82ae304871ef23fbffc23f7e867c.png

好文章,我在看❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505407.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络数据链路层检错编码 --- 循环冗余码CRC

实例说明 假如要发送的数据是1101 0110 11, 采用CRC校验, 生成多项式是10011, 那么最终发送的数据应该是? 发送端发送过程: 1. 最终发送的数据 要发送的数据 帧检验序列FCS(冗余码) 2. 利用生成多项式计算冗余码 计算冗余码的方法: 1. 加0, 要根据生成多项式中的阶为, 则…

计算机网络中数据链路层编码纠错编码 --- 海明码

1 概述 首先, 海明码是计算机网络中数据链路层的针对帧的位错提出的一种纠错编码方式. 海明码可以发现双比特错, 但纠正单比特错. 工作原理(简单解释): 牵一发动全身 2 工作流程 2.1 确定校验码位数r 海明不等式: 其中为冗余信息位数, 为信息位数 如果给定要发送的数据, …

计算机操作系统同步互斥

1 背景 在计算机系统里面, 多道程序设计是现代操作系统的重要特征, 且并行起到了很大的作用, 所以操作系统抽象出来了线程/进程的概念用来支持多道程序设计, 同时, 各个进程之间需要进行交互, CPU也需要进行调度来支持多进程. 多进程会涉及到共享资源访问的问题, 如果操作系统…

conda安装tensorflow-gpu简洁版_win10 tensorflow2.2 安装注意事项

学习新技术有两座大山&#xff0c;一座是安装配置环境&#xff0c;另一座是调试bug。对于想学习人工智能开发的人来说&#xff0c;安装配置tensorflow是必不可少的一步&#xff0c;这个过程对于初次接触的人来说&#xff0c;到处都是火坑。下面大家跟我一起&#xff0c;看看都会…

操作系统中死锁避免算法 --- 银行家算法

1. 背景 在银行系统中, 客户完成项目需要申请贷款的数量是有限的, 每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量, 在满足所有贷款要求并完成项目时, 客户应及时归还. 银行家在客户申请的贷款数量不超过自己拥有的最大值时, 都应尽量满足客户的需要. 在这样的描…

python字符串对齐_Python - 字符串对齐

字符串对齐 本文地址: http://blog.csdn.net/caroline_wendy/article/details/20463231 Python中, 字符串对齐, 使用ljust(), 左对齐;rjust(), 右对齐; center(), 中间对齐; 也可以修改第三个参数, 修改填充数据, 默认使用空格; 代码如下: # -*- coding: utf-8 -*- # #File: Te…

操作系统中的死锁问题

1 死锁问题概述 一组阻塞的进程持有一种资源等待获取另一个进程所占有的一个资源.例子: 系统有两个磁带驱动器, P1和P2各有一个, 都需要另外一个. 2 系统模型 资源类型: , , ..., 包括CPU cycles, memory space, I/O devices 每个资源类型有实例. 每个进程使用资源过程如下…

chrome控制台如何把vw显示成px_你可能不知道的chrome调试技巧

本文是对常用的chrome调试技巧进行总结整理&#xff0c;如果你没有深入了解过chrome调试工具&#xff0c;此处总有你不知道的惊喜&#xff01;从 Chrome 说起对于大部分人来说&#xff0c;Chrome 可能只是个浏览器&#xff0c;但是对于开发人员来说&#xff0c;它更是一个强大无…

gns3中两个路由器分别连接主机然后分析ip数据转发报文arp协议_ARP协议在同网段及跨网段下的工作原理...

前言&#xff1a;ARP协议是在链路层通讯中&#xff0c;已知目标IP地址,但是&#xff0c;仅有IP 地址是不够的&#xff0c;因为IP数据报必须封装成帧才能通过数据链路进行发送&#xff0c;而数据帧必须要有目的MAC地址&#xff0c;每一个网络设备在数据封装前都需要获取下一跳的…

操作系统文件系统

1 基本概念 1.1 文件系统和文件 1.1.1 简述 文件系统: 一种用于持久性存储的系统抽象 在存储器上: 组织, 控制, 导航, 访问和检索数据大多数计算机系统包含文件系统个人电脑, 服务器, 笔记本电脑ipod, Tivo/机顶盒, 手机/掌上电脑google可能是由一个文件系统构成的 文件: 文…

c++代码整洁之道pdf_别再问如何用python提取PDF内容了

作者&#xff1a;陈熹 来源&#xff1a;早起Python大家好&#xff0c;在之前的办公自动化系列文章中我们已经详细介绍了如何使用python批量处理PDF文件&#xff0c;包括合并、拆分、水印、加密等操作。今天我们再次回到PDF&#xff0c;详细讲解如何使用python从PDF提取指定的信…

查看文章影响因子的插件_Scholarscope--在新版PubMed中实现基于影响因子的文献筛选...

小编之前介绍过如何在Pubmed上直接显示杂志影响因子的方法&#xff0c;这个方法主要是依托Scholarscope插件&#xff0c;其实除了显示影响因子&#xff0c;这个插件还可以帮助大家根据影响因子筛选文献哦&#xff0c;操作也很简单&#xff0c;只要生成自定义过滤器即可&#xf…

leetcode32 --- longestValidParentheses

1 题目 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 2 解法 2.1 动态规划方法 维护一个字符串长度的数组cur_max_len, 第i个元素代表以当前(或者)结束的最长有效括号的长度. 这样就会利用动态规划递推…

armitage识别不了漏洞_Shiro RememberMe 漏洞检测的探索之路

前言Shiro 是 Apache 旗下的一个用于权限管理的开源框架&#xff0c;提供开箱即用的身份验证、授权、密码套件和会话管理等功能。该框架在 2016 年报出了一个著名的漏洞——Shiro-550&#xff0c;即 RememberMe 反序列化漏洞。4年过去了&#xff0c;该漏洞不但没有沉没在漏洞的…

css响应式布局_用 CSS Grid 布局制作一个响应式柱状图

最新一段时间比较喜欢玩弄图表&#xff0c;出于好奇&#xff0c;我想找出比较好的用 CSS 制作图表的方案。开始学习网上开源图表库&#xff0c;它对我学习新的和不熟悉的前端技术很有帮助&#xff0c;比如这个&#xff1a;CSS Grid。今天和大家分享我学到的新知识&#xff1a;如…

vs code 插件_[VSCode插件开发] 由浅入深,带你了解如何打造百万级产品

去年&#xff0c;笔者有幸在微软技术暨生态大会上做了个演讲&#xff0c;主题是“从零开始开发一款属于你的 Visual Studio Code 插件”。演讲内容主要覆盖了VS Code插件开发的四个方面&#xff1a;设计、实现、推广和维护。作为一个开发者&#xff0c;我们往往会把大多数的时间…

leetcode45 --- jump

1 题目 给定一个非负整数数组&#xff0c;你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 假设你总是可以到达数组的最后一个位置。 2 解法 2.1 从终点遍历的方法(时间复杂度)…

python怎么查看网页编码格式_怎么用python爬取网页文字?

用Python进行爬取网页文字的代码&#xff1a;#!/usr/bin/python# -*- coding: UTF-8 -*-import requestsimport re# 下载一个网页url htt用python进行爬取网页文字的代码&#xff1a;#!/usr/bin/python# -*- coding: UTF-8 -*-import requestsimport re# 下载一个网页url htt…

leetcode41 --- firstMissingPositive

1 题目 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 2 解法 最笨的方法是从1开始试, 看1在数组里面是否出现过, 2, 3, ....不过时间复杂度是. 2.1 hash 可以考虑…

计算机网络中的数据链路层

概述 结点: 主机, 路由器 链路: 网络中两个结点之间的物理通道, 链路的传输介质主要有双绞线, 光纤和微波, 分为有线链路和无线链路. 数据链路: 网络中两个结点之间的逻辑通道, 把实现控制数据传输协议的硬件和软件加到链路上就构成数据链路. 帧: 链路层的协议数据单元…