MST

星途 面试题库

面试题:C# 中基于 Redis 实现分布式锁的关键要点及代码实现

在 C# 项目中,基于 Redis 实现分布式锁,需要考虑哪些关键因素,比如锁的获取、释放、防止死锁等。请详细阐述并给出关键代码实现,同时说明如何处理锁竞争场景。
45.4万 热度难度
编程语言C#

知识考点

AI 面试

面试题答案

一键面试

关键因素

  1. 锁的获取
    • 要确保在分布式环境中,同一时间只有一个客户端能获取到锁。Redis 中可以使用 SETNX 命令(SET if Not eXists),如果键不存在,则设置键的值,返回 1 表示成功获取锁;如果键已存在,返回 0 表示获取锁失败。
    • 为锁设置一个合理的过期时间,防止由于程序异常未释放锁导致死锁。
  2. 锁的释放
    • 只有获取锁的客户端才能释放锁,防止误释放其他客户端的锁。可以在获取锁时设置一个唯一标识(如 UUID),释放锁时验证该标识。
    • 使用 DEL 命令删除 Redis 中的锁键来释放锁。
  3. 防止死锁
    • 设置锁的过期时间,即使获取锁的客户端出现异常未能主动释放锁,过期后锁也会自动释放。但要注意过期时间不能设置过短,以免业务未执行完锁就过期,导致数据不一致等问题。

关键代码实现

using StackExchange.Redis;
using System;
using System.Threading.Tasks;

public class RedisDistributedLock
{
    private readonly ConnectionMultiplexer _redis;
    private readonly IDatabase _database;
    private readonly string _lockKey;
    private readonly string _lockValue;
    private readonly TimeSpan _expiryTime;

    public RedisDistributedLock(ConnectionMultiplexer redis, string lockKey, TimeSpan expiryTime)
    {
        _redis = redis;
        _database = redis.GetDatabase();
        _lockKey = lockKey;
        _lockValue = Guid.NewGuid().ToString();
        _expiryTime = expiryTime;
    }

    public async Task<bool> AcquireLockAsync()
    {
        return await _database.StringSetAsync(_lockKey, _lockValue, _expiryTime, When.NotExists, CommandFlags.FireAndForget);
    }

    public async Task ReleaseLockAsync()
    {
        if (await _database.StringGetAsync(_lockKey) == _lockValue)
        {
            await _database.KeyDeleteAsync(_lockKey, CommandFlags.FireAndForget);
        }
    }
}

处理锁竞争场景

  1. 重试机制
    • 当获取锁失败时,客户端可以进行重试。可以设置一个最大重试次数和重试间隔时间。例如:
public async Task<bool> TryAcquireLockWithRetryAsync(int maxRetries, TimeSpan retryInterval)
{
    for (int i = 0; i < maxRetries; i++)
    {
        if (await AcquireLockAsync())
        {
            return true;
        }
        await Task.Delay(retryInterval);
    }
    return false;
}
  1. 队列化处理
    • 可以将获取锁失败的请求放入一个队列(如 Redis 的 LIST 数据结构)中。获取锁的客户端处理完业务后,从队列中取出下一个请求,让其获取锁并执行。这样可以有序地处理锁竞争,避免大量无效的重试。

通过上述方式,可以在 C# 项目中基于 Redis 实现较为健壮的分布式锁,并有效处理锁竞争场景。