📖 数据密集型设计

分布式锁原理与实现

深入探讨分布式锁的实现方案与最佳实践

一、分布式锁概述

分布式锁是分布式系统中控制多个节点访问共享资源的机制,在数据密集型应用中,分布式锁是保障数据一致性和避免并发冲突的关键。

二、分布式锁特性

2.1 分布式锁特性

特性 描述 重要性 实现难点
互斥性 同一时间只有一个客户端持有锁 多节点竞争
锁超时 锁自动释放防止死锁 超时时间设置
可重入性 同一客户端可重复获取锁 锁计数管理
公平性 按顺序获取锁 队列管理
高性能 获取和释放锁快速 减少网络开销
高可用 锁服务不可用时降级 故障转移

三、Redis分布式锁实现

3.1 Redis分布式锁基础实现

public class RedisDistributedLock : IDistributedLock
{
    private readonly IDistributedCache _cache;
    private readonly string _lockKey;
    private readonly string _lockValue;
    private readonly TimeSpan _expiry;
    
    public RedisDistributedLock(IDistributedCache cache, string lockKey, TimeSpan expiry)
    {
        _cache = cache;
        _lockKey = lockKey;
        _lockValue = Guid.NewGuid().ToString();
        _expiry = expiry;
    }
    
    public async Task AcquireAsync()
    {
        var result = await _cache.StringSetAsync(
            _lockKey, 
            _lockValue, 
            _expiry,
            When.NotExists);
        
        return result;
    }
    
    public async Task ReleaseAsync()
    {
        var luaScript = @"
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        ";
        
        var result = await _cache.ExecuteAsync(luaScript, 
            new RedisKey[] { _lockKey }, 
            new RedisValue[] { _lockValue });
        
        return (long)result == 1;
    }
    
    public async Task RenewAsync()
    {
        var luaScript = @"
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('expire', KEYS[1], ARGV[2])
            else
                return 0
            end
        ";
        
        var result = await _cache.ExecuteAsync(luaScript,
            new RedisKey[] { _lockKey },
            new RedisValue[] { _lockValue, (int)_expiry.TotalSeconds });
        
        return (long)result == 1;
    }
    
    public async Task ExecuteWithLockAsync(Func> func)
    {
        if (!await AcquireAsync())
        {
            throw new LockAcquisitionException("Failed to acquire lock");
        }
        
        try
        {
            return await func();
        }
        finally
        {
            await ReleaseAsync();
        }
    }
}

3.2 Redis分布式锁优化

public class RedisDistributedLockOptimized : IDistributedLock
{
    private readonly IDistributedCache _cache;
    private readonly string _lockKey;
    private readonly string _lockValue;
    private readonly TimeSpan _expiry;
    private CancellationTokenSource _renewalTokenSource;
    
    public async Task AcquireAsync(int retryCount = 3, TimeSpan retryDelay = default)
    {
        retryDelay = retryDelay == default ? TimeSpan.FromMilliseconds(100) : retryDelay;
        
        for (int i = 0; i < retryCount; i++)
        {
            if (await AcquireOnceAsync())
            {
                StartRenewal();
                return true;
            }
            
            if (i < retryCount - 1)
            {
                await Task.Delay(retryDelay);
            }
        }
        
        return false;
    }
    
    private async Task AcquireOnceAsync()
    {
        var result = await _cache.StringSetAsync(
            _lockKey,
            _lockValue,
            _expiry,
            When.NotExists);
        
        return result;
    }
    
    private void StartRenewal()
    {
        _renewalTokenSource = new CancellationTokenSource();
        
        _ = Task.Run(async () =>
        {
            while (!_renewalTokenSource.Token.IsCancellationRequested)
            {
                await Task.Delay(_expiry / 3);
                await RenewAsync();
            }
        });
    }
    
    public async Task ReleaseAsync()
    {
        _renewalTokenSource?.Cancel();
        
        var luaScript = @"
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        ";
        
        var result = await _cache.ExecuteAsync(luaScript,
            new RedisKey[] { _lockKey },
            new RedisValue[] { _lockValue });
        
        return (long)result == 1;
    }
}

四、ZooKeeper分布式锁实现

4.1 ZooKeeper分布式锁原理

graph TD A[ZooKeeper锁节点] --> B[/locks/] B --> C[lock-000000001] B --> D[lock-000000002] B --> E[lock-000000003] F[客户端1] --> G[创建临时顺序节点] G --> H[获取最小节点] H --> I{是否最小?} I -->|是| J[获取锁] I -->|否| K[监听前一个节点] K --> L[等待通知] L --> H M[客户端2] --> G

4.2 ZooKeeper分布式锁实现

public class ZooKeeperDistributedLock : IDistributedLock
{
    private readonly IZooKeeperClient _zooKeeper;
    private readonly string _lockPath;
    private readonly string _nodePath;
    private EventWaitHandle _waitHandle;
    
    public ZooKeeperDistributedLock(IZooKeeperClient zooKeeper, string lockPath)
    {
        _zooKeeper = zooKeeper;
        _lockPath = lockPath;
    }
    
    public async Task AcquireAsync()
    {
        _nodePath = await _zooKeeper.CreateAsync(
            $"{_lockPath}/lock-",
            data: null,
            ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);
        
        var lockNodeName = _nodePath.Substring(_lockPath.Length + 1);
        
        var children = await _zooKeeper.GetChildrenAsync(_lockPath);
        var sortedChildren = children.OrderBy(c => c).ToList();
        
        var index = sortedChildren.IndexOf(lockNodeName);
        
        if (index == 0)
        {
            return true;
        }
        
        var previousNode = $"{_lockPath}/{sortedChildren[index - 1]}";
        
        _waitHandle = new AutoResetEvent(false);
        
        await _zooKeeper.SubscribeAsync(previousNode, async (watcher) =>
        {
            if (watcher.Type == Watcher.Event.EventType.NodeDeleted)
            {
                _waitHandle.Set();
            }
        });
        
        _waitHandle.WaitOne();
        
        return true;
    }
    
    public async Task ReleaseAsync()
    {
        _waitHandle?.Dispose();
        
        await _zooKeeper.DeleteAsync(_nodePath);
        
        return true;
    }
}

五、etcd分布式锁实现

5.1 etcd分布式锁原理

public class EtcdDistributedLock : IDistributedLock
{
    private readonly IEtcdClient _etcdClient;
    private readonly string _lockKey;
    private readonly string _leaseId;
    private readonly TimeSpan _expiry;
    
    public EtcdDistributedLock(IEtcdClient etcdClient, string lockKey, TimeSpan expiry)
    {
        _etcdClient = etcdClient;
        _lockKey = lockKey;
        _expiry = expiry;
    }
    
    public async Task AcquireAsync()
    {
        var leaseResponse = await _etcdClient.LeaseGrantAsync((int)_expiry.TotalSeconds);
        _leaseId = leaseResponse.ID.ToString();
        
        var txnResponse = await _etcdClient.TxnAsync(txn => txn
            .If(Compare.Version(_lockKey).Equal(0))
            .Then(Op.Put(_lockKey, _leaseId, leaseResponse.ID))
            .Else(Op.Get(_lockKey)));
        
        if (!txnResponse.Succeeded)
        {
            await WaitForLockAsync();
            return true;
        }
        
        StartLeaseKeepAlive();
        
        return true;
    }
    
    private async Task WaitForLockAsync()
    {
        var watcher = _etcdClient.Watch(_lockKey);
        
        await foreach (var response in watcher)
        {
            foreach (var @event in response.Events)
            {
                if (@event.Type == EventType.Delete)
                {
                    await AcquireAsync();
                    return;
                }
            }
        }
    }
    
    private void StartLeaseKeepAlive()
    {
        _ = Task.Run(async () =>
        {
            var keepAlive = await _etcdClient.LeaseKeepAliveAsync(_leaseId);
            
            await foreach (var response in keepAlive)
            {
                if (response == null)
                {
                    break;
                }
            }
        });
    }
    
    public async Task ReleaseAsync()
    {
        await _etcdClient.DeleteAsync(_lockKey);
        await _etcdClient.LeaseRevokeAsync(_leaseId);
        
        return true;
    }
}

六、分布式锁方案对比

6.1 分布式锁方案对比表

方案 性能 可靠性 公平性 实现复杂度 适用场景
Redis 极高 高并发
ZooKeeper 中等 极高 中等 强一致性
etcd 极高 中等 K8s环境

七、分布式锁最佳实践

7.1 锁超时策略

public class DistributedLockBestPractices
{
    public TimeSpan CalculateLockTimeout(TimeSpan estimatedOperationTime)
    {
        return estimatedOperationTime + TimeSpan.FromSeconds(10);
    }
    
    public async Task ExecuteWithTimeoutAsync(Func> func, TimeSpan timeout)
    {
        using var cts = new CancellationTokenSource(timeout);
        
        var task = func();
        
        var completedTask = await Task.WhenAny(task, Task.Delay(timeout));
        
        if (completedTask == task)
        {
            return await task;
        }
        
        throw new TimeoutException("Operation timed out");
    }
}

7.2 锁重入实现

public class ReentrantDistributedLock : IDistributedLock
{
    private readonly IDistributedLock _innerLock;
    private int _lockCount;
    
    public async Task AcquireAsync()
    {
        if (_lockCount > 0)
        {
            _lockCount++;
            return true;
        }
        
        var acquired = await _innerLock.AcquireAsync();
        
        if (acquired)
        {
            _lockCount = 1;
        }
        
        return acquired;
    }
    
    public async Task ReleaseAsync()
    {
        if (_lockCount > 1)
        {
            _lockCount--;
            return true;
        }
        
        _lockCount = 0;
        
        return await _innerLock.ReleaseAsync();
    }
}

7.3 锁降级策略

public class LockDegradationService
{
    public async Task ExecuteWithDegradationAsync(
        Func> lockedOperation,
        Func> degradedOperation)
    {
        try
        {
            return await lockedOperation();
        }
        catch (LockAcquisitionException)
        {
            return await degradedOperation();
        }
        catch (Exception)
        {
            return await degradedOperation();
        }
    }
    
    public async Task ExecuteWithFallbackAsync(
        Func> primaryOperation,
        Func> fallbackOperation,
        int maxRetries = 3)
    {
        for (int i = 0; i < maxRetries; i++)
        {
            try
            {
                return await primaryOperation();
            }
            catch (Exception)
            {
                if (i == maxRetries - 1)
                {
                    return await fallbackOperation();
                }
                
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
            }
        }
        
        return await fallbackOperation();
    }
}

7.4 锁监控与告警

public class DistributedLockMonitor
{
    public async Task GetMetricsAsync()
    {
        var metrics = new LockMetrics();
        
        var locks = await _lockRepository.GetAllLocksAsync();
        
        foreach (var @lock in locks)
        {
            metrics.TotalLocks++;
            
            if (@lock.IsAcquired)
            {
                metrics.AcquiredLocks++;
                
                if (@lock.AcquiredAt.Add(@lock.Expiry) < DateTime.UtcNow)
                {
                    metrics.ExpiredLocks++;
                }
            }
            
            metrics.TotalWaitTime += @lock.WaitTime;
        }
        
        metrics.AverageWaitTime = locks.Count > 0 
            ? metrics.TotalWaitTime / locks.Count 
            : TimeSpan.Zero;
        
        return metrics;
    }
    
    public async Task MonitorAsync()
    {
        var metrics = await GetMetricsAsync();
        
        if (metrics.ExpiredLocks > 0)
        {
            await _alertService.SendAlert("分布式锁过期", 
                $"过期锁数: {metrics.ExpiredLocks}");
        }
        
        if (metrics.AverageWaitTime > TimeSpan.FromSeconds(5))
        {
            await _alertService.SendAlert("分布式锁等待过长", 
                $"平均等待时间: {metrics.AverageWaitTime}");
        }
    }
}

public class LockMetrics
{
    public int TotalLocks { get; set; }
    public int AcquiredLocks { get; set; }
    public int ExpiredLocks { get; set; }
    public TimeSpan TotalWaitTime { get; set; }
    public TimeSpan AverageWaitTime { get; set; }
}

八、总结

分布式锁是分布式系统中控制多个节点访问共享资源的关键机制。Redis适合高并发场景,ZooKeeper适合强一致性场景,etcd适合Kubernetes环境。通过合理选择锁方案、实现锁超时和重入机制、做好监控和告警,能够构建可靠的分布式锁系统。