分布式redislock使用注意事项
采用技术框架:csredis
业务逻辑:单个数据做判重,不重复增加,后续update
实现:使用redislock +分布式redis key的方式双重机制
问题:一个过程耗时72s
代码:
public async Task<long> AddBasicCustomerLog(BaseEmpInfo empInfo, long buid, DateTime dataTime, IDbConnection connection, IDbTransaction transaction = null)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
StringBuilder stringBuilder = new StringBuilder("【AddBasicCustomerLog】buid=" + buid);
long res = 0;
string token = DateTime.Now.ToLongTimeString() + Guid.NewGuid().ToString();
string lockKey = LockKeyCustomerLog + buid ;
CSRedisClientLock lockRedis = null;
//这块还是要释放,不然还是有问题,会导致程序占用的太多了 todo
int i = 0;
while (lockRedis == null)
{
lockRedis = RedisHelper.Lock(lockKey, 15);
//lockRedis = await _database.LockTakeAsync(lockKey, token, TimeSpan.FromSeconds(15));
if (lockRedis == null)
{
//if (connection.State != ConnectionState.Closed)
//{
// connection.Close();
//}
Thread.Sleep(3000);//等待3s
}
i++;
if (i > 3)
{
_logger.LogCritical(i + "次获取锁,依然失败,本次放弃对buid:" + buid + "的新增CustomerLog事件");
return res;
}
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
stringBuilder.AppendLine("2拿锁" + stopWatch.Elapsed.TotalSeconds);
//获取锁后再次查看是否已有 如果没有就新增
var statisDefeat = await _statisCustomerLogRepository.GetStatisCustomerIdByEmpId(buid, dataTime, connection, transaction);
stringBuilder.AppendLine("3GetStatisCustomerIdByEmpId-" + stopWatch.Elapsed.TotalSeconds);
if (statisDefeat <= 0)
{
res = await _statisCustomerLogRepository.SavBasicRecord(empInfo, buid, dataTime, connection, transaction);
stringBuilder.AppendLine("4SavBasicRecord-" + stopWatch.Elapsed.TotalSeconds);
}
else
{
return statisDefeat;
}
//await _database.LockReleaseAsync(lockKey, token);
lockRedis?.Unlock();
stopWatch.Stop();
stringBuilder.AppendLine("3结束" + stopWatch.Elapsed.TotalSeconds);
if (stopWatch.Elapsed.TotalSeconds > 2)
{
_logger.LogInformation(stringBuilder.ToString());
}
return res;
}
问题1:如果是一个已存在的数据 那么 可能存在没有释放lock , return statisDefeat;这一步
问题2:lock 没有释放,自动延期
问题3:lockrediskey 不唯一
/// <summary>开启分布式锁,若超时返回null</summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">超时(秒)</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <returns></returns>
public static CSRedisClientLock Lock(
string name,
int timeoutSeconds,
bool autoDelay = true)
{
return RedisHelper<TMark>.Instance.Lock(name, timeoutSeconds, true);
}
/// <summary>开启分布式锁,若超时返回null</summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">超时(秒)</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <returns></returns>
public CSRedisClientLock Lock(string name, int timeoutSeconds, bool autoDelay = true)
{
name = "CSRedisClientLock:" + name;
DateTime now = DateTime.Now;
while (DateTime.Now.Subtract(now).TotalSeconds < (double) timeoutSeconds)
{
string str = Guid.NewGuid().ToString();
if (this.Set(name, (object) str, timeoutSeconds, new RedisExistence?(RedisExistence.Nx)))
return new CSRedisClientLock(this, name, str, timeoutSeconds, autoDelay);
Thread.CurrentThread.Join(3);
}
return (CSRedisClientLock) null;
}
解决方案:1:return 前一定释放lock 2:redislock设置为可过期的 3:设置rediskey的时候设置成业务唯一的
新代码
public async Task<long> AddBasicCustomerLog(BaseEmpInfo empInfo, long buid, long compId, DateTime dataTime, IDbConnection connection, IDbTransaction transaction = null)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
StringBuilder stringBuilder = new StringBuilder("【AddBasicCustomerLog】buid=" + buid);
long res = 0;
string token = DateTime.Now.ToLongTimeString() + Guid.NewGuid().ToString();
string lockKey = LockKeyCustomerLog + buid + "_" + compId;
CSRedisClientLock lockRedis = null;
//这块还是要释放,不然还是有问题,会导致程序占用的太多了 todo
int i = 0;
while (lockRedis == null)
{
lockRedis = RedisHelper.Lock(lockKey, 15, false);
//lockRedis = await _database.LockTakeAsync(lockKey, token, TimeSpan.FromSeconds(15));
if (lockRedis == null)
{
//if (connection.State != ConnectionState.Closed)
//{
// connection.Close();
//}
Thread.Sleep(3000);//等待3s
}
i++;
if (i > 3)
{
_logger.LogCritical(i + "次获取锁,依然失败,本次放弃对buid:" + buid + "的新增CustomerLog事件");
return res;
}
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
stringBuilder.AppendLine("2拿锁" + stopWatch.Elapsed.TotalSeconds);
//获取锁后再次查看是否已有 如果没有就新增
var statisDefeat = await _statisCustomerLogRepository.GetStatisCustomerIdByEmpId(buid, dataTime, connection, transaction);
stringBuilder.AppendLine("3GetStatisCustomerIdByEmpId-" + stopWatch.Elapsed.TotalSeconds);
if (statisDefeat <= 0)
{
res = await _statisCustomerLogRepository.SavBasicRecord(empInfo, buid, dataTime, connection, transaction);
stringBuilder.AppendLine("4SavBasicRecord-" + stopWatch.Elapsed.TotalSeconds);
}
else
{
res = statisDefeat;
}
//await _database.LockReleaseAsync(lockKey, token);
lockRedis?.Unlock();
stopWatch.Stop();
stringBuilder.AppendLine("3结束" + stopWatch.Elapsed.TotalSeconds);
if (stopWatch.Elapsed.TotalSeconds > 2)
{
_logger.LogInformation(stringBuilder.ToString());
}
return res;
}

浙公网安备 33010602011771号