可能导致性能问题的原因
- 网络延迟:分布式系统中,C# 应用与 Redis 服务器之间的网络通信延迟会影响锁的获取与释放速度。每次请求都需要经过网络传输,高并发时网络拥塞会加剧延迟。
- 锁竞争激烈:高并发场景下,多个客户端同时竞争 Redis 分布式锁,频繁的锁请求和释放操作会使 Redis 服务器压力增大,导致性能下降。
- 锁粒度问题:如果锁的粒度设置不合理,过于粗粒度的锁会导致大量不必要的等待,降低系统并发度;而细粒度的锁又可能增加锁管理的复杂度和开销。
- 锁操作开销:获取和释放锁的操作本身需要执行 Redis 命令,包括 SETNX(SET if Not eXists)等,这些操作在高并发时会产生较大开销。
优化 Redis 分布式锁性能的策略及代码优化思路
- 减少网络通信次数
- 思路:采用批量操作。在获取或释放多个锁时,尽量将多个操作合并为一次网络请求。例如,使用 Lua 脚本在 Redis 服务器端原子性地执行多个命令,减少客户端与服务器之间的往返次数。
- 示例代码:
using StackExchange.Redis;
public class RedisLockHelper
{
private readonly ConnectionMultiplexer _redis;
private readonly IDatabase _database;
public RedisLockHelper(string connectionString)
{
_redis = ConnectionMultiplexer.Connect(connectionString);
_database = _redis.GetDatabase();
}
public bool TryAcquireLock(string key, string value, TimeSpan expiration)
{
var script = @"
if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return 1
else
return 0
end";
var parameters = new RedisParameter[]
{
key,
value,
(long)expiration.TotalMilliseconds
};
return (long)_database.ScriptEvaluate(script, parameters) == 1;
}
public void ReleaseLock(string key)
{
var script = @"
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end";
var value = Guid.NewGuid().ToString(); // 假设这里获取锁时的唯一标识
var parameters = new RedisParameter[]
{
key,
value
};
_database.ScriptEvaluate(script, parameters);
}
}
- 优化锁竞争
- 思路:采用公平锁机制,例如使用 Redis 的 ZSet 数据结构来实现公平排队获取锁。每个客户端在请求锁时,将自己的标识和时间戳等信息作为成员插入到 ZSet 中,按照时间戳排序,先插入的客户端先获取锁。
- 示例代码:
public class FairRedisLock
{
private readonly IDatabase _database;
private readonly string _lockKey;
private readonly string _queueKey;
public FairRedisLock(IDatabase database, string lockKey)
{
_database = database;
_lockKey = lockKey;
_queueKey = lockKey + ":queue";
}
public bool TryAcquireLock(string clientId)
{
var now = DateTime.UtcNow.Ticks;
_database.SortedSetAdd(_queueKey, clientId, now);
if (_database.SortedSetRank(_queueKey, clientId) == 0)
{
_database.StringSet(_lockKey, clientId);
return true;
}
return false;
}
public void ReleaseLock(string clientId)
{
_database.KeyDelete(_lockKey);
_database.SortedSetRemove(_queueKey, clientId);
}
}
- 合理调整锁粒度
- 思路:根据业务场景分析,将大的业务操作分解为多个小的操作,并对每个小操作设置合理粒度的锁。例如,在电商系统中,如果有一个订单创建和库存扣减的操作,可以将库存扣减单独设置为一个细粒度的锁,在订单创建完成后再执行库存扣减,避免整个订单操作期间一直持有粗粒度的锁。
- 示例代码:
public class OrderService
{
private readonly RedisLockHelper _redisLockHelper;
public OrderService(RedisLockHelper redisLockHelper)
{
_redisLockHelper = redisLockHelper;
}
public void CreateOrder(Order order)
{
var orderLockKey = $"order:{order.OrderId}:lock";
var stockLockKey = $"stock:{order.ProductId}:lock";
var lockValue = Guid.NewGuid().ToString();
try
{
if (!_redisLockHelper.TryAcquireLock(orderLockKey, lockValue, TimeSpan.FromSeconds(10)))
{
throw new Exception("Failed to acquire order lock");
}
// 订单创建逻辑
//...
if (!_redisLockHelper.TryAcquireLock(stockLockKey, lockValue, TimeSpan.FromSeconds(10)))
{
throw new Exception("Failed to acquire stock lock");
}
// 库存扣减逻辑
//...
}
finally
{
_redisLockHelper.ReleaseLock(orderLockKey);
_redisLockHelper.ReleaseLock(stockLockKey);
}
}
}
- 优化锁操作开销
- 思路:使用连接池来复用 Redis 连接,减少每次获取和释放连接的开销。同时,在获取锁时可以设置合理的重试策略,避免因瞬时失败而频繁重试。
- 示例代码:
public class RedisLockWithPool
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect("your_connection_string");
});
private static ConnectionMultiplexer Connection => LazyConnection.Value;
private readonly IDatabase _database;
public RedisLockWithPool()
{
_database = Connection.GetDatabase();
}
public bool TryAcquireLock(string key, string value, TimeSpan expiration, int retryCount = 3, int retryIntervalMs = 100)
{
for (int i = 0; i < retryCount; i++)
{
if (_database.StringSet(key, value, expiration, When.NotExists))
{
return true;
}
if (i < retryCount - 1)
{
Thread.Sleep(retryIntervalMs);
}
}
return false;
}
public void ReleaseLock(string key)
{
_database.KeyDelete(key);
}
}