采用技術框架: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;
}
