Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

การใช้ Redis Distributed Lock ใน .NET Core

บทนำ

ในบทความนี้ เราจะพูดถึงวิธีสร้างการล็อกแบบกระจายด้วย Redis ใน .NET Core

เมื่อเราสร้างระบบแบบกระจาย เราจะเผชิญกับกระบวนการหลายอย่างที่จัดการทรัพยากรที่ใช้ร่วมกันร่วมกัน มันจะทำให้เกิดปัญหาที่ไม่คาดคิดเนื่องจากมีเพียงหนึ่งในนั้นเท่านั้นที่สามารถใช้ทรัพยากรที่ใช้ร่วมกันได้ในแต่ละครั้ง!

เราสามารถใช้การล็อคแบบกระจายเพื่อแก้ไขปัญหานี้ได้

ทำไมต้องกระจายล็อค

ตามปกติ เราจะใช้การล็อคเพื่อจัดการกับปัญหานี้

ต่อไปนี้แสดงโค้ดตัวอย่างบางส่วนที่สาธิตการใช้ล็อค

public void SomeMethod() 
{ 
 // Do something... 
 lock (obj) 
 { 
 // Do .... 
 } 
 // Do something... 
}

แต่การล็อคแบบนี้ก็ไม่สามารถช่วยให้เราแก้ไขปัญหาได้ดีนัก! นี่คือการล็อคในกระบวนการที่สามารถแก้ไขได้เพียงกระบวนการเดียวด้วยทรัพยากรที่ใช้ร่วมกัน

นี่คือเหตุผลหลักว่าทำไมเราถึงต้องมีการล็อคแบบกระจาย!

ฉันจะใช้ Redis เพื่อสร้างการล็อคแบบกระจายอย่างง่ายที่นี่

และเหตุใดฉันจึงใช้ Redis เพื่อทำงานนี้ เนื่องจากลักษณะเธรดเดี่ยวของ Redis และความสามารถในการดำเนินการระดับอะตอมมิก

จะสร้างล็อคได้อย่างไร

ฉันจะสร้างแอปพลิเคชัน .NET Core Console เพื่อแสดงให้คุณเห็น

ก่อนขั้นตอนต่อไป เราควรเรียกใช้เซิร์ฟเวอร์ Redis!

StackExchange.Redis เป็นไคลเอ็นต์ Reids ที่ได้รับความนิยมสูงสุดบน .NET และไม่ต้องสงสัยเลยว่าเราจะใช้ไคลเอ็นต์นี้เพื่อทำงานต่อไปนี้

การสร้างการเชื่อมต่อกับ 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 duration

resources_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} cannot eat food due to not getting the lock."); 
 } 
 }); 
 Console.WriteLine("end"); 
 Console.Read(); 
} 

หลังจากรันโค้ดแล้ว เราอาจได้รับผลลัพธ์ดังต่อไปนี้

การใช้ Redis Distributed Lock ใน .NET Core

มีเพียงคนเดียวเท่านั้นที่จะได้ล็อค! คนอื่นกำลังรออยู่

แม้ว่าการล็อกจะถูกลบออกโดยอัตโนมัติโดย Redis แต่ก็ไม่ได้ใช้ประโยชน์จากทรัพยากรที่ใช้ร่วมกันเช่นกัน!

เพราะเมื่อกระบวนการเสร็จสิ้น งานควรปล่อยให้ผู้อื่นใช้ทรัพยากร ไม่ใช่รออย่างไม่มีที่สิ้นสุด!

ดังนั้นเราจึงต้องปลดล็อคด้วยเช่นกัน

จะปลดล็อคได้อย่างไร

หากต้องการปลดล็อค เราเพิ่งลบไอเท็มใน Redis!

สำหรับสิ่งที่เราดำเนินการในการสร้างล็อค เราจำเป็นต้องจับคู่มูลค่าเฉพาะของทรัพยากร ซึ่งจะทำให้ปลดล็อคด้านขวาได้อย่างปลอดภัยยิ่งขึ้น

เมื่อจับคู่เราจะลบล็อคซึ่งหมายความว่าปลดล็อคสำเร็จ มิฉะนั้น การปลดล็อคไม่สำเร็จ

เราจำเป็นต้องดำเนินการคำสั่ง 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 จะเดินหน้าต่อไปโดยไม่มีการล็อค

นี่คือซอร์สโค้ดที่คุณสามารถหาได้บนหน้า GitHub ของฉัน

  • การสาธิต RedisLock

สรุป

บทความนี้แนะนำวิธีสร้างการล็อกแบบกระจายด้วย Redis ใน .NET Core และเป็นเวอร์ชันพื้นฐานที่คุณสามารถปรับปรุงตามธุรกิจของคุณได้

ฉันหวังว่านี่จะช่วยคุณได้