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