⒈簡介
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 實現分布式鎖基礎之上修改部分代碼編寫。