面试题答案
一键面试关键因素
- 锁的获取:
- 要确保在分布式环境中,同一时间只有一个客户端能获取到锁。Redis 中可以使用
SETNX
命令(SET if Not eXists
),如果键不存在,则设置键的值,返回1
表示成功获取锁;如果键已存在,返回0
表示获取锁失败。 - 为锁设置一个合理的过期时间,防止由于程序异常未释放锁导致死锁。
- 要确保在分布式环境中,同一时间只有一个客户端能获取到锁。Redis 中可以使用
- 锁的释放:
- 只有获取锁的客户端才能释放锁,防止误释放其他客户端的锁。可以在获取锁时设置一个唯一标识(如 UUID),释放锁时验证该标识。
- 使用
DEL
命令删除 Redis 中的锁键来释放锁。
- 防止死锁:
- 设置锁的过期时间,即使获取锁的客户端出现异常未能主动释放锁,过期后锁也会自动释放。但要注意过期时间不能设置过短,以免业务未执行完锁就过期,导致数据不一致等问题。
关键代码实现
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);
}
}
}
处理锁竞争场景
- 重试机制:
- 当获取锁失败时,客户端可以进行重试。可以设置一个最大重试次数和重试间隔时间。例如:
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;
}
- 队列化处理:
- 可以将获取锁失败的请求放入一个队列(如 Redis 的
LIST
数据结构)中。获取锁的客户端处理完业务后,从队列中取出下一个请求,让其获取锁并执行。这样可以有序地处理锁竞争,避免大量无效的重试。
- 可以将获取锁失败的请求放入一个队列(如 Redis 的
通过上述方式,可以在 C# 项目中基于 Redis 实现较为健壮的分布式锁,并有效处理锁竞争场景。