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