📖 数据密集型设计

分布式锁与一致性

深入探讨分布式锁实现与一致性保障

一、分布式锁概述

分布式锁是在分布式系统中实现互斥访问共享资源的机制。在分布式环境下,多个进程或服务需要协调对共享资源的访问,分布式锁能够保证同一时间只有一个进程访问该资源。

二、分布式锁特性

2.1 分布式锁核心特性

特性 描述 重要性
互斥性 同一时间只有一个客户端持有锁
超时释放 客户端崩溃后锁能自动释放
可重入性 同一客户端可重复获取同一锁
公平性 按请求顺序获取锁
高可用 锁服务故障不影响系统

三、Redis分布式锁

3.1 Redis锁原理

基于Redis的SET命令实现分布式锁:

sequenceDiagram participant Client1 as 客户端1 participant Client2 as 客户端2 participant Redis as Redis Client1->>Redis: SET lock_key random_value NX PX 30000 Redis-->>Client1: OK(获取锁成功) Client2->>Redis: SET lock_key random_value NX PX 30000 Redis-->>Client2: nil(获取锁失败) Client1->>Client1: 执行业务逻辑 Client1->>Redis: EVAL 释放锁脚本 Redis-->>Client1: 1(释放锁成功)

3.2 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<bool> AcquireAsync()
    {
        var result = await _cache.StringSetAsync(
            _lockKey,
            _lockValue,
            _expiry,
            When.NotExists
        );
        return result;
    }
    
    public async Task ReleaseAsync()
    {
        var script = @"
            if redis.call('GET', KEYS[1]) == ARGV[1] then
                return redis.call('DEL', KEYS[1])
            else
                return 0
            end
        ";
        
        await _cache.ScriptEvaluateAsync(script, 
            new RedisKey[] { _lockKey }, 
            new RedisValue[] { _lockValue });
    }
    
    public async Task<bool> TryAcquireAsync(TimeSpan timeout)
    {
        var startTime = DateTime.Now;
        
        while (DateTime.Now - startTime < timeout)
        {
            if (await AcquireAsync())
                return true;
            
            await Task.Delay(100);
        }
        
        return false;
    }
}

3.3 Redis Redlock算法

public class RedlockDistributedLock : IDistributedLock
{
    private readonly List<IDistributedCache> _redisInstances;
    private readonly string _lockKey;
    private readonly string _lockValue;
    private readonly TimeSpan _expiry;
    private readonly int _quorum;
    
    public RedlockDistributedLock(List<IDistributedCache> redisInstances, 
        string lockKey, TimeSpan expiry)
    {
        _redisInstances = redisInstances;
        _lockKey = lockKey;
        _lockValue = Guid.NewGuid().ToString();
        _expiry = expiry;
        _quorum = (redisInstances.Count / 2) + 1;
    }
    
    public async Task<bool> AcquireAsync()
    {
        var tasks = _redisInstances.Select(async redis =>
        {
            return await redis.StringSetAsync(_lockKey, _lockValue, _expiry, When.NotExists);
        });
        
        var results = await Task.WhenAll(tasks);
        var successCount = results.Count(r => r);
        
        return successCount >= _quorum;
    }
    
    public async Task ReleaseAsync()
    {
        var tasks = _redisInstances.Select(async redis =>
        {
            var script = @"
                if redis.call('GET', KEYS[1]) == ARGV[1] then
                    return redis.call('DEL', KEYS[1])
                else
                    return 0
                end
            ";
            await redis.ScriptEvaluateAsync(script, 
                new RedisKey[] { _lockKey }, 
                new RedisValue[] { _lockValue });
        });
        
        await Task.WhenAll(tasks);
    }
}

四、ZooKeeper分布式锁

4.1 ZooKeeper锁原理

基于ZooKeeper的临时顺序节点实现分布式锁:

sequenceDiagram participant Client1 as 客户端1 participant Client2 as 客户端2 participant ZK as ZooKeeper Client1->>ZK: 创建临时顺序节点 /lock/0000000001 ZK-->>Client1: 节点创建成功 Client1->>ZK: 获取/lock下所有子节点 ZK-->>Client1: [0000000001] Client1->>Client1: 自己是最小节点,获取锁成功 Client2->>ZK: 创建临时顺序节点 /lock/0000000002 ZK-->>Client2: 节点创建成功 Client2->>ZK: 获取/lock下所有子节点 ZK-->>Client2: [0000000001, 0000000002] Client2->>ZK: 监听前一个节点 /lock/0000000001 ZK-->>Client2: 监听成功

4.2 ZooKeeper锁实现

public class ZooKeeperDistributedLock : IDistributedLock
{
    private readonly IZooKeeperClient _zooKeeper;
    private readonly string _lockPath;
    private string _currentNodePath;
    private ManualResetEvent _waitEvent = new ManualResetEvent(false);
    
    public ZooKeeperDistributedLock(IZooKeeperClient zooKeeper, string lockPath)
    {
        _zooKeeper = zooKeeper;
        _lockPath = lockPath;
    }
    
    public async Task AcquireAsync()
    {
        await EnsureLockPathExists();
        
        _currentNodePath = await _zooKeeper.CreateAsync(
            $"{_lockPath}/lock-",
            data: null,
            CreateMode.EphemeralSequential
        );
        
        var children = await _zooKeeper.GetChildrenAsync(_lockPath);
        children.Sort();
        
        var currentIndex = children.IndexOf(Path.GetFileName(_currentNodePath));
        
        if (currentIndex == 0)
            return;
        
        var previousNode = children[currentIndex - 1];
        var previousNodePath = $"{_lockPath}/{previousNode}";
        
        await _zooKeeper.SubscribeDataChange(previousNodePath, (s, e) =>
        {
            if (e.ChangeType == Watcher.Event.EventType.NodeDeleted)
            {
                _waitEvent.Set();
            }
        });
        
        _waitEvent.WaitOne();
    }
    
    public async Task ReleaseAsync()
    {
        if (!string.IsNullOrEmpty(_currentNodePath))
        {
            await _zooKeeper.DeleteAsync(_currentNodePath);
            _currentNodePath = null;
        }
    }
    
    private async Task EnsureLockPathExists()
    {
        if (!await _zooKeeper.ExistsAsync(_lockPath))
        {
            await _zooKeeper.CreateAsync(_lockPath, data: null, CreateMode.Persistent);
        }
    }
}

五、etcd分布式锁

5.1 etcd锁原理

基于etcd的KV存储和watch机制实现分布式锁:

sequenceDiagram participant Client1 as 客户端1 participant Client2 as 客户端2 participant Etcd as etcd Client1->>Etcd: PUT /lock/my-lock with lease Etcd-->>Client1: OK(获取锁成功) Client2->>Etcd: PUT /lock/my-lock with lease Etcd-->>Client2: Error(key已存在) Client2->>Etcd: Watch /lock/my-lock Etcd-->>Client2: Watch建立 Client1->>Client1: 执行业务逻辑 Client1->>Etcd: DELETE /lock/my-lock Etcd-->>Client2: Watch触发(key删除) Client2->>Etcd: PUT /lock/my-lock with lease Etcd-->>Client2: OK(获取锁成功)

5.2 etcd锁实现

public class EtcdDistributedLock : IDistributedLock
{
    private readonly IEtcdClient _etcdClient;
    private readonly string _lockKey;
    private readonly string _lockValue;
    private long _leaseId;
    
    public EtcdDistributedLock(IEtcdClient etcdClient, string lockKey)
    {
        _etcdClient = etcdClient;
        _lockKey = lockKey;
        _lockValue = Guid.NewGuid().ToString();
    }
    
    public async Task<bool> AcquireAsync(TimeSpan expiry)
    {
        var leaseResponse = await _etcdClient.LeaseGrantAsync(expiry);
        _leaseId = leaseResponse.ID;
        
        var putResponse = await _etcdClient.PutAsync(_lockKey, _lockValue, 
            new PutOptions { Lease = _leaseId, PrevExist = false });
        
        if (!putResponse.Succeeded)
        {
            await _etcdClient.LeaseRevokeAsync(_leaseId);
            return false;
        }
        
        return true;
    }
    
    public async Task ReleaseAsync()
    {
        if (_leaseId > 0)
        {
            await _etcdClient.LeaseRevokeAsync(_leaseId);
            _leaseId = 0;
        }
    }
    
    public async Task<bool> TryAcquireAsync(TimeSpan expiry, TimeSpan timeout)
    {
        var startTime = DateTime.Now;
        
        while (DateTime.Now - startTime < timeout)
        {
            if (await AcquireAsync(expiry))
                return true;
            
            await WaitForLockRelease();
        }
        
        return false;
    }
    
    private async Task WaitForLockRelease()
    {
        using var watcher = _etcdClient.Watch(_lockKey);
        
        await foreach (var response in watcher)
        {
            foreach (var @event in response.Events)
            {
                if (@event.Type == Mvccpb.Event.Types.EventType.Delete)
                    return;
            }
        }
    }
}

六、分布式锁对比

6.1 分布式锁方案对比

方案 互斥性 超时释放 公平性 性能 适用场景
Redis 较好 支持 高频场景
Redlock 支持 高可用场景
ZooKeeper 支持 一致性要求高
etcd 支持 K8s生态

七、分布式锁与数据一致性

7.1 分布式锁保障一致性

flowchart TD A[业务操作] --> B{获取分布式锁} B -->|成功| C[执行业务逻辑] B -->|失败| D[等待或返回] C --> E[更新数据库] C --> F[更新缓存] E --> G[释放锁] F --> G G --> H[操作完成] D --> I[重试或降级]

7.2 锁粒度选择

public class LockGranularityService
{
    public IDistributedLock GetLock(LockType type, string resourceId)
    {
        return type switch
        {
            LockType.Global => new RedisDistributedLock(_cache, "global-lock", TimeSpan.FromSeconds(30)),
            LockType.Product => new RedisDistributedLock(_cache, $"product:{resourceId}", TimeSpan.FromSeconds(10)),
            LockType.Order => new RedisDistributedLock(_cache, $"order:{resourceId}", TimeSpan.FromSeconds(15)),
            LockType.User => new RedisDistributedLock(_cache, $"user:{resourceId}", TimeSpan.FromSeconds(20)),
            _ => throw new ArgumentOutOfRangeException(nameof(type))
        };
    }
}

public enum LockType
{
    Global,
    Product,
    Order,
    User
}

7.3 锁与事务结合

public async Task ProcessOrderAsync(int orderId)
{
    var lockKey = $"order:{orderId}";
    
    using var distributedLock = new RedisDistributedLock(_cache, lockKey, TimeSpan.FromSeconds(30));
    
    if (!await distributedLock.TryAcquireAsync(TimeSpan.FromSeconds(10)))
    {
        throw new TimeoutException("获取锁超时");
    }
    
    try
    {
        using var transaction = await _dbContext.Database.BeginTransactionAsync();
        
        var order = await _dbContext.Orders.FindAsync(orderId);
        if (order == null)
            throw new NotFoundException("订单不存在");
        
        order.Status = OrderStatus.Processing;
        await _dbContext.SaveChangesAsync();
        
        await _cache.SetStringAsync($"order:{orderId}", JsonSerializer.Serialize(order));
        
        await transaction.CommitAsync();
    }
    finally
    {
        await distributedLock.ReleaseAsync();
    }
}

八、分布式锁最佳实践

8.1 设置合理的超时时间

超时时间应大于业务执行时间,避免锁提前释放。

8.2 使用唯一标识释放锁

使用唯一标识防止误释放其他客户端的锁。

8.3 锁的续期机制

public class RedisDistributedLockWithRenewal : RedisDistributedLock
{
    private Timer _renewalTimer;
    private readonly TimeSpan _renewalInterval;
    
    public RedisDistributedLockWithRenewal(IDistributedCache cache, 
        string lockKey, TimeSpan expiry) : base(cache, lockKey, expiry)
    {
        _renewalInterval = expiry / 3;
    }
    
    public override async Task<bool> AcquireAsync()
    {
        var acquired = await base.AcquireAsync();
        
        if (acquired)
        {
            _renewalTimer = new Timer(async _ =>
            {
                await RenewLockAsync();
            }, null, _renewalInterval, _renewalInterval);
        }
        
        return acquired;
    }
    
    private async Task RenewLockAsync()
    {
        var script = @"
            if redis.call('GET', KEYS[1]) == ARGV[1] then
                return redis.call('PEXPIRE', KEYS[1], ARGV[2])
            else
                return 0
            end
        ";
        
        await _cache.ScriptEvaluateAsync(script, 
            new RedisKey[] { _lockKey }, 
            new RedisValue[] { _lockValue, _expiry.TotalMilliseconds });
    }
    
    public override async Task ReleaseAsync()
    {
        _renewalTimer?.Dispose();
        await base.ReleaseAsync();
    }
}

8.4 锁的降级策略

public async Task<T> ExecuteWithLockAsync<T>(
    string lockKey, 
    Func<Task<T>> operation,
    Func<Task<T>> fallbackOperation)
{
    using var distributedLock = new RedisDistributedLock(_cache, lockKey, TimeSpan.FromSeconds(30));
    
    try
    {
        if (await distributedLock.TryAcquireAsync(TimeSpan.FromSeconds(5)))
        {
            return await operation();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "获取锁失败");
    }
    
    return await fallbackOperation();
}

8.5 锁的监控与告警

public class LockMonitor
{
    public async Task MonitorLockUsage()
    {
        var lockStats = await _cache.GetLockStatisticsAsync();
        
        foreach (var stat in lockStats)
        {
            if (stat.WaitTime > TimeSpan.FromSeconds(30))
            {
                await _alertService.SendAlert("锁等待时间过长", 
                    $"锁: {stat.LockKey}, 等待时间: {stat.WaitTime}");
            }
            
            if (stat.FailedAttempts > 100)
            {
                await _alertService.SendAlert("锁获取失败次数过多", 
                    $"锁: {stat.LockKey}, 失败次数: {stat.FailedAttempts}");
            }
        }
    }
}

九、分布式锁常见问题

9.1 死锁问题

通过超时释放机制避免死锁。

9.2 锁提前释放

通过锁续期机制解决。

9.3 锁竞争激烈

通过细粒度锁和排队机制缓解。

9.4 锁服务故障

通过多节点部署和降级策略应对。

十、总结

分布式锁是分布式系统中保证数据一致性的关键机制。Redis锁性能高适合高频场景,ZooKeeper锁一致性强适合严格场景,etcd锁适合K8s生态。合理选择锁方案、设置超时时间、实现锁续期、监控锁状态,能够构建可靠的分布式锁系统,保障数据一致性。