Redis单节点的分布式锁只需要注意三点就可以了:

1.加锁并设置锁的过期时间必须是原子操作;

2.锁的value值必须要有唯一性;

3.释放锁的时候要验证其value值,不是自己加的锁不能释放.

但是单节点分布式锁最大的缺点就是,它只作用在一个Redis节点上,如果该节点挂了,那就挂了.

那可不可以通过哨兵机制来保证高可用呢?

答案是不行.

因为Redis在进行主从复制的时候是异步的.

假设 clientA 拿到锁后,在 master 还没同步到 slave 时,master 发生了故障,这时候 salve 升级为 master,导致锁丢失.

RedLock 的思想是:假设有5个Redis节点.这些节点完全相互独立,不存在主从或者集群机制,都是 master.并且这5个Redis实例运行在5台机器上,这样保证他们不会同时宕掉.

客户端应该按照以下操作来获取锁:

1.获取当前时间戳,假设是T1.

2.依次尝试从这5个Redis实例获取锁.当客户端向Redis请求获取锁时,客户端应该设置超时时间,并且这个超时时间应该小于锁的失效时间.比如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间.这样可以避免Redis已经挂掉的情况下,客户端还在等待响应结果.如果Redis没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁.

3.请求完所有的Redis节点后,只有满足如下两点,才算真正的获取到锁:

  1)当前时间 - T1 的时间差小于锁的过期时间.比如T1=00:00:00,然后从5个Redis节点都拿到了锁,当前时间是 00:00:05,也就是说获取锁一共用了5秒钟.假设锁的过期时间是3秒,那么这次获取锁的操作就算失败了.

  2)从(N/2+1)个Redis节点都获取到锁.这个很好理解,5个节点,你拿2个,我拿2个,到底算谁的?

  总结一句话就是:从开始获取锁计时,只要在锁的过期时间内成功获取到一半以上的锁便算成功,否则算失败.

4.当客户端获取到了锁,锁的真正有效时间 = 锁的过期时间 - 获取锁所使用的时间(也就是第3步计算出来的时间).

5.如果客户端由于某些原因(比如获取锁的实例个数小于N/2+1,或者已经超过了有效时间),没有获取到锁,客户端便会在所有的Redis实例上进行解锁(即使某些Redis实例根本就没有加锁成功),因为可能已经获取了小于 N/2+1个锁,必须释放掉,否则会影响其他客户端获取锁.

关于是否启动AOF永久存储,需要有所取舍.

1.永久启动,由于Redis的过期机制是按照unix时间戳走的,所以当我们重启Redis后,依然会按照规定的时间过期.但是永久启动对性能有一定影响;

2.采用默认的1秒1次.如果在1秒内断电,会导致数据丢失,这时候如果立刻重启会导致锁的互斥性实效.

所以有效的解决方案是,采用AOF,1秒1次,不管什么原因宕机后,等待一定时间再重启.这个时间就是锁的过期时间.

Demo:

安装官方提供的 RedLock.net

Startup:

  1. public class Startup
  2. {
  3. private RedLockFactory _redLockFactory;
  4.  
  5. public void ConfigureServices(IServiceCollection services)
  6. {
  7. services.AddControllers();
  8.  
  9. var endPoints = new List<RedLockEndPoint>
  10. {
  11. new DnsEndPoint("127.0.0.1", 6379),
  12. new DnsEndPoint("127.0.0.1", 6380),
  13. new DnsEndPoint("127.0.0.1", 6381)
  14. };
  15. _redLockFactory = RedLockFactory.Create(endPoints);
  16. services.AddSingleton(typeof(IDistributedLockFactory), _redLockFactory);
  17. }
  18.  
  19. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
  20. {
  21. if (env.IsDevelopment())
  22. {
  23. app.UseDeveloperExceptionPage();
  24. }
  25.  
  26. //应用程序结束时释放,因为不是容器创建的对象
  27. applicationLifetime.ApplicationStopping.Register(() =>
  28. {
  29. _redLockFactory.Dispose();
  30. });
  31.  
  32. app.UseRouting();
  33. app.UseEndpoints(endpoints =>
  34. {
  35. endpoints.MapControllers();
  36. });
  37. }
  38. }

测试api:

  1. [ApiController]
  2. public class ValuesController : ControllerBase
  3. {
  4. private static int _stock = 10;
  5.  
  6. private readonly IDistributedLockFactory _distributedLockFactory;
  7.  
  8. public ValuesController(IDistributedLockFactory distributedLockFactory)
  9. {
  10. _distributedLockFactory = distributedLockFactory;
  11. }
  12.  
  13. [Route("lockTest")]
  14. [HttpGet]
  15. public async Task<int> DistributedLockTest()
  16. {
  17. // resource 锁定的资源
  18. var resource = "the-thing-we-are-locking-on";
  19.  
  20. // expiryTime 锁的过期时间
  21. var expiry = TimeSpan.FromSeconds(5);
  22.  
  23. // waitTime 等待时间
  24. var wait = TimeSpan.FromSeconds(1);
  25.  
  26. // retryTime 等待时间内,多久重试一次
  27. var retry = TimeSpan.FromMilliseconds(250);
  28.  
  29. using (var redLock = await _distributedLockFactory.CreateLockAsync(resource, expiry, wait, retry))
  30. {
  31. if (redLock.IsAcquired)
  32. {
  33. // 模拟执行业务逻辑
  34. await Task.Delay(new Random().Next(100, 500));
  35. if (stock > 0)
  36. {
  37. stock--;
  38. return stock;
  39. }
  40. return stock;
  41. }
  42. Console.WriteLine($"{DateTime.Now} : 获取锁失败");
  43. }
  44. return -99;
  45. }
  46. }

测试控制台:

  1. static void Main(string[] args)
  2. {
  3. HttpClient client = new HttpClient();
  4. var result = Parallel.For(0, 20, (i) =>
  5. {
  6. var stopwatch = new Stopwatch();
  7. stopwatch.Start();
  8. var response = client.GetAsync($"http://localhost:5000/locktest").Result;
  9. stopwatch.Stop();
  10. var data = response.Content.ReadAsStringAsync().Result;
  11. Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
  12. });
  13. client.Dispose();
  14. Console.ReadKey();
  15. }

测试结果:

C# Redis分布式锁(RedLock) - 多节点的更多相关文章

  1. 七种方案!探讨Redis分布式锁的正确使用姿势

    前言 日常开发中,秒杀下单.抢红包等等业务场景,都需要用到分布式锁.而Redis非常适合作为分布式锁使用.本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式.如果有不正确的地方,欢迎大家 ...

  2. Redlock(redis分布式锁)原理分析

    Redlock:全名叫做 Redis Distributed Lock;即使用redis实现的分布式锁: 使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击) ...

  3. Redlock:Redis分布式锁最牛逼的实现

    普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获取锁(unique ...

  4. 【Redis】分布式锁RedLock

    普通实现 说道Redis分布式锁大部分人都会想到: 1.setnx+lua, 2.setkey value px milliseconds nx. - 获取锁(unique_value可以是UUID等 ...

  5. C# Redis分布式锁 - 单节点

    为什么要用分布式锁? 先上一张截图,这是在浏览别人的博客时看到的. 在了解为什么要用分布式锁之前,我们应该知道到底什么是分布式锁. 锁按照不同的维度,有多种分类.比如 1.悲观锁,乐观锁; 2.公平锁 ...

  6. Redis分布式锁升级版RedLock及SpringBoot实现

    分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式.但是现在 ...

  7. RedLock.Net - 基于Redis分布式锁的开源实现

    工作中,经常会遇到分布式环境中资源访问冲突问题,比如商城的库存数量处理,或者某个事件的原子性操作,都需要确保某个时间段内只有一个线程在访问或处理资源. 因此现在网上也有很多的分布式锁的解决方案,有数据 ...

  8. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  9. Lua脚本在redis分布式锁场景的运用

    目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...

随机推荐

  1. 阿里面试竟如此轻松,2招带你过关斩将拿下offer

    在找工作之前首先是要认清一个问题,虽然这个问题比较俗,但是很现实,就是为什么追求高工资? 这个问题我想不用说大家心里也清楚.大部分人都不是当前城市的本地人,说好听了叫来上班,说的不好听其实叫“外来务工 ...

  2. python习题 随机密码生成 + 连续质数计算

    随机密码生成 描述 补充编程模板中代码,完成如下功能:‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪ ...

  3. 【Spring注解驱动开发】AOP核心类解析,这是最全的一篇了!!

    写在前面 昨天二狗子让我给他讲@EnableAspectJAutoProxy注解,讲到AnnotationAwareAspectJAutoProxyCreator类的源码时,二狗子消化不了了.这不,今 ...

  4. <string name="xxx"> 的复杂用法:格式化及使用html标签

    1.官方文档: https://developer.android.com/guide/topics/resources/string-resource 2.格式化字符串 2.1 示例 <res ...

  5. 小程序mpvue中flyio的使用方法

    Fly.js 一个基于Promise的.强大的.支持多种JavaScript运行时的http请求库. 有了它,您可以使用一份http请求代码在浏览器.微信小程序.Weex.Node.React Nat ...

  6. 剑指 Offer 43. 1~n整数中1出现的次数

    题目描述 输入一个整数 n ,求1-n这n个整数的十进制表示中1出现的次数. 例如,输入12,1-12这些整数中包含1 的数字有1.10.11和12,1一共出现了5次. 示例 1: 输入:n = 12 ...

  7. java 将map转为实体类

    使用反射将map转为对象,如果不使用反射的话需要一个get一个set写起来麻烦,并且不通用,所以写了一个通用的方法将map集合转为对象,直接看代码,注释也都挺清楚的 public static < ...

  8. 关于h5游戏开发,你想了解的一切都在这儿!

    ​2020年,受疫情影响,线下产业红利褪去,线上迎来的新一轮的高峰.众多商家纷纷抓住了转型时机,开启了流量争夺战.H5游戏定制无疑是今年引流的大热门.如何开发一款有趣.有爆点.用户爱买单的好游戏呢? ...

  9. SpringBoot 消息国际化配置

    一.目的 针对不同地区,设置不同的语言信息. SpringBoot国际化配置文件默认放在classpath:message.properties,如果自定义消息配置文件,需要application.p ...

  10. python3 变量

    python 3变量名不能以数字开头但能数字结尾 变量名大小写敏感 在多个单词组成的变量名中以下划线间隔