บทนำ
ในบทความนี้ เราจะพูดถึงวิธีสร้างการล็อกแบบกระจายด้วย 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 แต่ก็ไม่ได้ใช้ประโยชน์จากทรัพยากรที่ใช้ร่วมกันเช่นกัน!
เพราะเมื่อกระบวนการเสร็จสิ้น งานควรปล่อยให้ผู้อื่นใช้ทรัพยากร ไม่ใช่รออย่างไม่มีที่สิ้นสุด!
ดังนั้นเราจึงต้องปลดล็อคด้วยเช่นกัน
จะปลดล็อคได้อย่างไร
หากต้องการปลดล็อค เราเพิ่งลบไอเท็มใน 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 และเป็นเวอร์ชันพื้นฐานที่คุณสามารถปรับปรุงตามธุรกิจของคุณได้
ฉันหวังว่านี่จะช่วยคุณได้