Giải pháp khóa phân tán RedLockNet trong .NET Core

Địa chỉ mã nguồn trên GitHub samcook/RedLock.net - Triển khai thuật toán Redlock bằng C#

Cấu trúc mã nguồn Các lớp chính: RedLockFactory, RedLock

Khởi tạo và giải phóng khóa Phân tích từ lớp RedLock:

private readonly ICollection<RedisConnection> redisNodes; // Tập hợp node Redis
private readonly int requiredNodes; // Số lượng cần thiết để đạt quorum
private readonly int retryCount;
private readonly int retryDelayMs;

private readonly TimeSpan lockExpiry; // Thời gian hết hạn khóa
private readonly TimeSpan? waitTimeout; // Thời gian chờ tối đa
private readonly TimeSpan? retryInterval; // Khoảng cách giữa các lần thử

Tính toán tham số

requiredNodes = redisNodes.Count / 2 + 1; // Công thức quorum
retryCount = retryConfig?.RetryCount ?? DefaultRetryCount;
retryDelayMs = retryConfig?.RetryDelayMs ?? DefaultRetryDelayMs;

Khởi tạo khóa

internal static RedLock Create(
    ILogger<RedLock> logger,
    ICollection<RedisConnection> nodes,
    string resourceKey,
    TimeSpan expiry,
    TimeSpan? waitTime = null,
    TimeSpan? retryInterval = null,
    RedLockRetryConfiguration retryConfig = null,
    CancellationToken? token = null)
{
    var locker = new RedLock(
        logger, nodes, resourceKey, expiry, 
        waitTime, retryInterval, retryConfig, token);
    
    locker.Start();
    return locker;
}

Khởi động tiến trình khóa

private void Start()
{
    if (HasRetryConfig())
    {
        var timer = Stopwatch.StartNew();
        while (!IsLocked && timer.Elapsed <= waitTimeout.Value)
        {
            (Status, Summary) = AcquireLock();
            if (!IsLocked) DelayAndCheckCancellation();
        }
    }
    else
    {
        (Status, Summary) = AcquireLock();
    }

    logger.LogInfo($"Trạng thái khóa: {Status} ({Summary}), {ResourceKey} ({LockToken})");

    if (IsLocked) StartAutoRenewal();
}

Quy trình lấy khóa

private (LockStatus, LockSummary) AcquireLock()
{
    var summary = new LockSummary();

    for (var i = 0; i < retryCount; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var attempt = i + 1;
        logger.LogDebug($"Thử khóa lần {attempt}/{retryCount}: {ResourceKey} ({LockToken})");

        var stopwatch = Stopwatch.StartNew();
        summary = ExecuteLockAttempt();
        
        var validity = CalculateValidity(stopwatch);
        logger.LogDebug($"Đã khóa {summary.Acquired}/{redisNodes.Count} node, quorum: {requiredNodes}, thời gian hiệu lực: {validity}");

        if (summary.Acquired >= requiredNodes && validity > 0)
        {
            return (LockStatus.Success, summary);
        }

        ReleaseAllLocks();
        if (i < retryCount - 1) RandomDelay();
    }

    return (DetermineFailureStatus(summary), summary);
}

Cơ chế khóa phân tán

private LockSummary ExecuteLockAttempt()
{
    var results = new ConcurrentBag<LockResult>();
    Parallel.ForEach(redisNodes, node => 
        results.Add(LockSingleNode(node)));
    
    return BuildSummary(results);
}

private LockResult LockSingleNode(RedisConnection node)
{
    var redisKey = FormatKey(node.KeyFormat, ResourceKey);
    var host = GetHost(node.Connection);
    try
    {
        var redisResult = node.Connection.GetDatabase(node.DbIndex)
            .StringSet(redisKey, LockToken, lockExpiry, When.NotExists, CommandFlags.DemandMaster);
            
        return redisResult ? LockResult.Success : LockResult.Conflict;
    }
    catch
    {
        return LockResult.Error;
    }
}

Giải phóng khóa

private void ReleaseAllLocks()
{
    extendUnlockSemaphore.Wait();
    try
    {
        Parallel.ForEach(redisNodes, ReleaseSingleNode);
    }
    finally
    {
        extendUnlockSemaphore.Release();
    }
}

private void ReleaseSingleNode(RedisConnection node)
{
    var redisKey = FormatKey(node.KeyFormat, ResourceKey);
    try
    {
        node.Connection.GetDatabase(node.DbIndex)
            .ScriptEvaluate(UnlockScript, new RedisKey[] {redisKey}, 
            new RedisValue[] {LockToken}, CommandFlags.DemandMaster);
    }
    catch { }
}

Script Lua giải phóng khóa

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

Trạng thái khóa

public enum LockStatus
{
    Unlocked, // Chưa khóa
    Success,  // Khóa thành công
    NoQuorum, // Không đạt quorum
    Conflict, // Bị xung đột
    Expired   // Hết thời gian hiệu lực
}

Nguyên lý hoạt động

Khởi tạo đối tượng RedLock với tham số cấu hình Gọi phương thức Start() để bắt đầu quy trình khóa Thử thiết lập khóa trên tất cả node Redis với TTL xác định Kiểm tra điều kiện quorum (đạt tối thiểu N/2+1 node thành công) Giải phóng khóa trên tất cả node khi hoàn tất hoặc xảy ra lỗi

]]>

Thẻ: Redlock Redis .net core Distributed Locking Concurrency control

Đăng vào ngày 2 tháng 8 lúc 01:19