⒈简介
RedLock 分布式锁算法由 Redis 的作者提出,大部分语言都有对应的实现,查看,RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现,用来解决分布式下的并发问题。
RedLock 的思想是使用多台 Redis Master ,节点之间完全独立,节点间不需要进行数据同步,因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave,被选为 Master 的 Slave 就丢掉了锁,另一个客户端就可以再次拿到锁。
锁通过 setNX(原子操作) 命令设置,在有效时间内当获得锁的数量大于 (n/2+1) 代表成功,失败后需要向所有节点发送释放锁的消息。
获取锁:
1 SET resource_name my_random_value NX PX 30000
释放锁:
1 if redis.call("get",KEYS[1]) == ARGV[1] then 2 return redis.call("del",KEYS[1]) 3 else 4 return 0 5 end
⒉使用
1.创建 .NETCore API 项目
2.Nuget 安装 RedLock.net
1 Install-Package RedLock.net
3.appsettings.json 添加 redis 配置
1 { 2 "Logging": { 3 "LogLevel": { 4 "Default": "Warning" 5 } 6 }, 7 "AllowedHosts": "*", 8 "RedisUrls": [ 9 "127.0.0.1:6379", 10 "192.168.214.128:6379" 11 ] 12 }
4.添加 ProductService.cs,模拟商品购买
1 // 有10个商品库存,如果同时启动多个API服务进行测试,这里改成存数据库或其他方式 2 private static int stockCount = 10; 3 public async Task<bool> BuyAsync() 4 { 5 // 模拟执行的逻辑代码花费的时间 6 await Task.Delay(new Random().Next(100, 500)); 7 if (stockCount > 0) 8 { 9 stockCount--; 10 return true; 11 } 12 return false; 13 }
5.修改 Startup.cs ,创建 RedLockFactory
1.定义RedLockFactory属性
1 private RedLockFactory lockFactory 2 { 3 get 4 { 5 var redisUrls = Configuration.GetSection("RedisUrls").GetChildren().Select(s => s.Value).ToArray(); 6 if(redisUrls.Length <= 0) 7 { 8 throw new ArgumentException("RedisUrl 不能为空"); 9 } 10 var endPoints = new List<RedLockEndPoint>(); 11 foreach (var item in redisUrls) 12 { 13 var arr = item.Split(":"); 14 endPoints.Add(new DnsEndPoint(arr[0], Convert.ToInt32(arr[1]))); 15 } 16 return RedLockFactory.Create(endPoints); 17 } 18 }
2.在 ConfigureServices 注入 IDistributedLockFactory:
1 // This method gets called by the runtime. Use this method to add services to the container. 2 public void ConfigureServices(IServiceCollection services) 3 { 4 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 5 services.AddSingleton(typeof(IDistributedLockFactory), lockFactory); 6 services.AddScoped(typeof(ProductService)); 7 }
3.修改 Configure,应用程序结束时释放 lockFactory
1 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 2 public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime) 3 { 4 if (env.IsDevelopment()) 5 { 6 app.UseDeveloperExceptionPage(); 7 } 8 9 app.UseMvc(); 10 11 lifetime.ApplicationStopping.Register(() => 12 { 13 lockFactory.Dispose(); 14 }); 15 16 }
6.在 Controller 添加方法 DistributedLockTest
1 private readonly IDistributedLockFactory _distributedLockFactory; 2 private readonly ProductService _productService; 3 4 public HomeController(IDistributedLockFactory distributedLockFactory, 5 ProductService productService) 6 { 7 _distributedLockFactory = distributedLockFactory; 8 _productService = productService; 9 } 10 11 [HttpGet] 12 public async Task<bool> DistributedLockTest() 13 { 14 var productId = "id"; 15 // resource 锁定的对象 16 // expiryTime 锁定过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放 17 // waitTime 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间 18 // retryTime 等待时间内,多久尝试获取一次 19 using (var redLock = await _distributedLockFactory.CreateLockAsync(productId, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(20))) 20 { 21 if (redLock.IsAcquired) 22 { 23 var result = await _productService.BuyAsync(); 24 return result; 25 } 26 else 27 { 28 Console.WriteLine($"获取锁失败:{DateTime.Now}"); 29 } 30 } 31 return false; 32 }
在文章RedLock 实现分布式锁基础之上修改部分代码编写。