使用Redis创建分布式锁
在本文中,我們將討論如何在.NET Core中使用Redis創建分布式鎖。
當我們構建分布式系統時,我們將面臨多個進程一起處理共享資源,由于其中只有一個可以一次使用共享資源,因此會導致一些意外問題!
我們可以使用分布式鎖來解決這個問題。
首先在非集群單體應用下,我們使用鎖來處理這個問題。
以下顯示了一些演示鎖的使用的示例代碼。
但是,這種類型的鎖不能幫助我們很好地解決問題!這是一個進程內鎖,只能用共享資源解決一個進程。?
這也是我們需要分布式鎖的主要原因!?
我將使用Redis在這里創建一個簡單的分布式鎖。
為什么我使用Redis來完成這項工作?由于Redis的單線程特性及其執行原子操作的能力。
我將創建一個.NET Core Console應用程序來向您展示大概流程。
在下一步之前,我們應該運行Redis服務器!
StackExchange.Redis是.NET中最受歡迎的Reids客戶端,我們將使用它來完成以下工作。
首先與Redis建立聯系。
/// <summary> ? /// The lazy connection. ? /// </summary> ? private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => ? { ? ConfigurationOptions configuration = new ConfigurationOptions ? { ? AbortOnConnectFail = false, ? ConnectTimeout = 5000, ? }; ? configuration.EndPoints.Add("localhost", 6379); ? return ConnectionMultiplexer.Connect(configuration.ToString()); ? }); ? /// <summary> ? /// Gets the connection. ? /// </summary> ? /// <value>The connection.</value> ? public static ConnectionMultiplexer Connection => lazyConnection.Value;為了請求鎖定共享資源,我們執行以下操作:
SET resource_name unique_value NX PX durationresource_name是應用程序的所有實例將共享的值。
unique_value必須對應用程序的每個實例都是唯一的。而他的主要目的是取消鎖定(解鎖)。
最后,我們還提供一個持續時間(以毫秒為單位),之后Redis將自動刪除鎖定。
這是C#代碼中的實現。
/// <summary> ? /// Acquires the lock. ? /// </summary> ? /// <returns><c>true</c>, if lock was acquired, <c>false</c> otherwise.</returns> ? /// <param name="key">Key.</param> ? /// <param name="value">Value.</param> ? /// <param name="expiration">Expiration.</param> ? static bool AcquireLock(string key, string value, TimeSpan expiration) { ? bool flag = false; ? try { ? flag = Connection.GetDatabase().StringSet(key, value, expiration, When.NotExists); ? } ? catch (Exception ex) ? { ? Console.WriteLine($"Acquire lock fail...{ex.Message}"); ? flag = true; ? } ? return flag; ? }這是測試獲取鎖定的代碼。
static void Main(string[] args) { ? string lockKey = "lock:eat"; ? TimeSpan expiration = TimeSpan.FromSeconds(5); ? //5 person eat something... ? Parallel.For(0, 5, x => ? { ? string person = $"person:{x}"; ? bool isLocked = AcquireLock(lockKey, person, expiration); ? if (isLocked) ? { ? Console.WriteLine($"{person} begin eat food(with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}."); ? } ? else { ? Console.WriteLine($"{person} can not eat food due to don't get the lock."); ? } ? }); ? Console.WriteLine("end"); ? Console.Read(); ? } ??運行代碼后,我們可能會得到以下結果。
只有一個人可以獲得鎖定!其他人等待。
雖然Redis會自動刪除鎖,但它也沒有很好地利用共享資源!
因為當一個進程完成它的工作時,應該讓其他人使用該資源,而不是無休止地等待!
所以我們也需要釋放鎖。
如何釋放鎖定?要釋放鎖,我們只需刪除Redis中對應的key/value!
正如我們在創建鎖中所做的那樣,我們需要匹配資源的唯一值,這樣可以更安全地釋放正確的鎖。
匹配時,我們將刪除鎖定,這意味著解鎖成功。否則,解鎖不成功。
我們需要一次執行get和del命令,因此我們將使用lua腳本來執行此操作!
/// <summary> ? /// Releases the lock. ? /// </summary> ? /// <returns><c>true</c>, if lock was released, <c>false</c> otherwise.</returns> ? /// <param name="key">Key.</param> ? /// <param name="value">Value.</param> ? static bool ReleaseLock(string key, string value) { ? string lua_script = @" ? if (redis.call('GET', KEYS[1]) == ARGV[1]) then ? redis.call('DEL', KEYS[1]) ? return true ? else ? return false ? end ? "; ? try { ? var res = Connection.GetDatabase().ScriptEvaluate(lua_script, ? new RedisKey[] { key }, ? new RedisValue[] { value }); ? return (bool)res; ? } ? catch (Exception ex) ? { ? Console.WriteLine($"ReleaseLock lock fail...{ex.Message}"); ? return false; ? } ? } ?我們應該在進程完成后調用此方法。
當進程獲得鎖定并且由于某些原因而未釋放鎖定時,其他進程不能等到它被釋放。此時,其他流程應該繼續進行。
這是一個處理這個場景的示例。
Parallel.For(0, 5, x => ? { ? string person = $"person:{x}"; ? var val = 0; ? bool isLocked = AcquireLock(lockKey, person, expiration); ? while (!isLocked && val <= 5000) ? { ? val += 250; ? System.Threading.Thread.Sleep(250); ? isLocked = AcquireLock(lockKey, person, expiration); ? } ? if (isLocked) ? { ? Console.WriteLine($"{person} begin eat food(with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}."); ? if (new Random().NextDouble() < 0.6) ? { ? Console.WriteLine($"{person} release lock {ReleaseLock(lockKey, person)} ?{DateTimeOffset.Now.ToUnixTimeMilliseconds()}"); ? } ? else { ? Console.WriteLine($"{person} do not release lock ...."); ? } ? } ? else { ? Console.WriteLine($"{person} begin eat food(without lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}."); ? } ? }); ?運行該示例后,您將會得到以下結果。
如圖所示,第3和第4在無鎖情況下運行。
總結
以上是生活随笔為你收集整理的使用Redis创建分布式锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xxl-job dotnet core
- 下一篇: 动手造轮子:基于 Redis 实现 Ev