Giới thiệu
Trong bài viết này, chúng ta sẽ thảo luận cách tạo khóa phân tán bằng Redis trong .NET Core.
Khi xây dựng hệ thống phân tán, chúng ta sẽ phải đối mặt với nhiều quy trình xử lý cùng một tài nguyên được chia sẻ. Nó sẽ gây ra một số vấn đề không mong muốn do thực tế là tại một thời điểm chỉ có một trong số họ có thể sử dụng tài nguyên được chia sẻ!
Chúng ta có thể sử dụng khóa phân tán để giải quyết vấn đề này.
Tại sao phải khóa phân phối?
Như thường lệ, chúng ta sẽ sử dụng khóa để xử lý vấn đề này.
Phần sau đây hiển thị một số mã mẫu minh họa cách sử dụng khóa.
public void SomeMethod()
{
// Do something...
lock (obj)
{
// Do ....
}
// Do something...
} Tuy nhiên, loại khóa này không thể giúp chúng ta giải quyết tốt vấn đề được! Đây là khóa trong quá trình chỉ có thể giải quyết một quy trình bằng tài nguyên được chia sẻ.
Đây cũng là lý do chính tại sao chúng ta cần khóa phân tán!
Tôi sẽ sử dụng Redis để tạo một khóa phân phối đơn giản tại đây.
Và tại sao tôi lại sử dụng Redis để làm công việc này? Do tính chất đơn luồng của Redis và khả năng thực hiện các hoạt động nguyên tử của nó.
Làm thế nào để tạo khóa?
Tôi sẽ tạo một ứng dụng .NET Core Console để cho bạn xem.
Trước bước tiếp theo, chúng ta nên chạy máy chủ Redis!
StackExchange.Redis là ứng dụng khách Reids phổ biến nhất trên .NET và chắc chắn rằng chúng tôi sẽ sử dụng nó để thực hiện các công việc sau.
Đầu tiên tạo kết nối với 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; Để yêu cầu khóa tài nguyên dùng chung, chúng tôi thực hiện như sau.
SET resource_name unique_value NX PX duration Resource_name là giá trị mà tất cả các phiên bản ứng dụng của bạn sẽ chia sẻ.
Unique_value là giá trị duy nhất cho mỗi phiên bản ứng dụng của bạn. Và mục đích của giá trị duy nhất này là để tháo khóa (mở khóa).
Cuối cùng, chúng tôi cũng cung cấp khoảng thời gian (tính bằng mili giây), sau đó khóa sẽ được Redis tự động xóa.
Đây là cách triển khai bằng mã 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;
} Đây là mã để kiểm tra khóa thu thập.
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();
} Sau khi chạy code, chúng ta có thể nhận được kết quả như sau.

Chỉ có một người có thể lấy được khóa! Những người khác đang chờ đợi.
Mặc dù khóa sẽ được Redis tự động xóa nhưng nó cũng không tận dụng tốt tài nguyên dùng chung!
Bởi vì khi một tiến trình hoàn thành công việc của mình, nó sẽ để cho những tiến trình khác sử dụng tài nguyên chứ không phải chờ đợi vô tận!
Vì vậy chúng ta cũng cần phải mở khóa.
Làm thế nào để mở khóa?
Để mở khóa, chúng tôi vừa xóa mục trong Redis!
Đối với những gì chúng tôi thực hiện khi tạo khóa, chúng tôi cần phải khớp với giá trị duy nhất của tài nguyên. Điều này sẽ giúp việc mở khóa bên phải an toàn hơn.
Khi khớp xong chúng ta sẽ xóa khóa tức là việc mở khóa đã thành công. Nếu không thì việc mở khóa không thành công.
Chúng ta cần thực thi các lệnh get và del cùng một lúc, vì vậy chúng ta sẽ sử dụng tập lệnh Lua để thực hiện việc này!
/// <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;
}
} Chúng ta nên gọi phương thức này khi quá trình kết thúc.
Khi một tiến trình nhận được khóa và không giải phóng khóa vì lý do nào đó, các tiến trình khác không thể đợi cho đến khi nó được giải phóng. Tại thời điểm này, các quy trình khác sẽ được tiếp tục.
Đây là mẫu để xử lý cảnh này.
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()}.");
}
}); Sau khi chạy mẫu, bạn có thể nhận được kết quả như sau.
Như bạn thấy, người 3 và 4 sẽ đi tiếp mà không cần khóa.
Đây là mã nguồn bạn có thể tìm thấy trên trang GitHub của tôi.
- RedisLockDemo
Tóm tắt
Bài viết này giới thiệu cách tạo khóa phân tán bằng Redis trong .NET Core. Và đây là phiên bản cơ bản, bạn có thể cải thiện tùy theo hoạt động kinh doanh của mình.
Tôi hy vọng điều này sẽ giúp ích cho bạn.