分布式多线程的Lock示例

 场景实例:

      现在比较流行的分布式,多线程的项目中,往往会遇到这么一个问题,就是当多个application或者多user并发的访问或者更改数据库同一DB数据时,可能会导致数据的不一致性,那怎么解决呢???

解决方案:

    1、第一种方式借用数据库事务(transaction):访问并操作数据项的数据库操作序列,这些操作要不全部执行,要不全部不执行,是一个不可分割的单元。

       为什么说数据库事务可以解决这个问题呢?

       这就不得不说数据库事务的几个特性了,简称(ACID)

       1)、原子性(Atomacity):事务中的全部操作在数据库中是不可分割的,要不全部执行,要不全部不执行

       2)、一致性(consistency):几个并行执行的事务,其执行结果和按某一顺序执行的结果必须是一致的。

       3)、隔离性(isolation):一个事务的执行不受其他事务的干扰。

       4)、永久性(durability):已提交的事务,保证对数据库中数据的修改是不丢失的,即使数据库出现故障。

       事务的ACID特性是由关系数据库(DBMS)来实现的,DBMS采用日志来保证数据库的原子性,一致性和永久性,日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志撤销事务对数据库所做的更新,使得数据库回滚到事务开始前的状态。

     对于事务的隔离性,DBMS是采用锁机制来实现的。当多个事务同时更新数据中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,知道前一个事务释放了锁,其他事务才有机会更新该数据。

     数据库事务的有点:

      把逻辑相关的操作分成一个组;

      在数据永久改变前可以预览数据变化;

       能够保证数据的读写一致性;

2、第二种方式是自己实现锁机制

      核心思想:

      1)、DB中添加一张表,包括2个字段(LockId, ExpirationTime)

      2)、当多个Request需要更新DB中同一数据时,通过LockId Lock住所需要更新这条数据,直到当前request结束后者过期,才开始下一个Request操作

     核心代码:

   

    public class CommonLockHelper
    {
        private TimeSpan _onceWaitTime;
        private bool _isWait = true;
        private static readonly TimeSpan _defaultWaitTime = TimeSpan.FromMinutes(10);
        private static readonly TimeSpan _defaultLockTimeout = TimeSpan.FromMinutes(30);

        public CommonLockHelper()
        {
            _onceWaitTime = TimeSpan.FromSeconds(5);
        }
        public CommonLockHelper(TimeSpan onceWaitTime)
        {
            _onceWaitTime = onceWaitTime;
        }
        public CommonLockHelper(bool isWait)
        {
            if (isWait)
            {
                _onceWaitTime = TimeSpan.FromSeconds(5);
            }
            else
            {
                _onceWaitTime = TimeSpan.FromMilliseconds(1);
            }
            _isWait = isWait;
        }

        #region for string
        public LockObject GetLockObject(IDBRepository dBRepository, string actionId)
        {
            return GetLockObject(dBRepository, actionId, _defaultWaitTime, _defaultLockTimeout);
        }
        public LockObject GetLockObject(IDBRepository dBRepository, string actionId, TimeSpan waitTimeOut)
        {
            return GetLockObject(dBRepository, actionId, waitTimeOut, _defaultLockTimeout);
        }
        public LockObject GetLockObject(IDBRepository dBRepository, string actionId, TimeSpan waitTimeOut, TimeSpan lockTimeOut)
        {
            return GetLockObject(dBRepository, Convert2Guid(actionId), waitTimeOut, lockTimeOut);
        }
        #endregion

        #region for guid
        public LockObject GetLockObject(IDBRepository dBRepository, Guid actionId)
        {
            return GetLockObject(dBRepository, actionId, _defaultWaitTime, _defaultLockTimeout);
        }
        public LockObject GetLockObject(IDBRepository dBRepository, Guid actionId, TimeSpan waitTimeOut)
        {
            return GetLockObject(dBRepository, actionId, waitTimeOut, _defaultLockTimeout);
        }
        public LockObject GetLockObject(IDBRepository dBRepository, Guid actionId, TimeSpan waitTimeOut, TimeSpan lockTimeOut)
        {
            DateTime now = DateTime.UtcNow;
            while (DateTime.UtcNow - now < waitTimeOut)
            {
                if (TryAddLockObjectToDatabase(dBRepository, actionId, lockTimeOut))
                {
                    return new LockObject(() =>
                    {
                        Delete(dBRepository, actionId);
                    });
                }
                if (!_isWait)
                {
                    return new LockObject();
                }
            }
            throw new TimeoutException($"wait timeout, actionId:{actionId}, startTimeUTC:{now}, waitTimeOut:{waitTimeOut}");
        }
        #endregion

        private Guid Convert2Guid(string myStr)
        {
            using (var md5 = MD5.Create())
            {
                byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(myStr));
                return new Guid(hash);
            }
        }


        private bool TryAddLockObjectToDatabase(IDBRepository dBRepository, Guid actionId, TimeSpan lockTimeOut)
        {
            try
            {
                var expirationTime = DateTime.UtcNow + lockTimeOut;
                var lockEntity = FindOne(dBRepository, actionId);
                if (lockEntity != null)
                {
                    if (lockEntity.ExpirationTime < DateTime.UtcNow)
                    {
                        //the current lock is expired, update expirationTime and reuse it
                        Update(dBRepository, actionId, expirationTime);
                    }
                    else
                    {
                        //current lock is not expired, wait and return false
                        Thread.Sleep(_onceWaitTime);
                        return false;
                    }
                }
                else
                {
                    Add(dBRepository, actionId, expirationTime);
                }
            }
            catch (Exception e)
            {
                Thread.Sleep(_onceWaitTime);
                return false;
            }
            return true;
        }
        private DBLock FindOne(IDBRepository dBRepository, Guid actionId)
        {
            var sql = @"SELECT * FROM [Common_Lock] WHERE [Id] = @Id";
            var result = dBRepository.SqlQuery<DBLock>(sql, new SqlParameter("@Id", actionId));
            return result.FirstOrDefault();
        }
        private void Add(IDBRepository dBRepository, Guid actionId, DateTime expirationTime)
        {
            var sql = @"INSERT INTO [dbo].[Common_Lock]([Id],[ExpirationTime])VALUES(@Id, @ExpirationTime)";
            dBRepository.ExecuteSqlCommand(sql, new SqlParameter("@Id", actionId), new SqlParameter("@ExpirationTime", expirationTime));
        }
        private void Update(IDBRepository dBRepository, Guid actionId, DateTime expirationTime)
        {
            var sql = @"UPDATE [Common_Lock] set ExpirationTime = @ExpirationTime WHERE [Id] = @Id";
            dBRepository.ExecuteSqlCommand(sql, new SqlParameter("@Id", actionId), new SqlParameter("@ExpirationTime", expirationTime));
        }
        private void Delete(IDBRepository dBRepository, Guid actionId)
        {
            var sql = @"DELETE FROM [Common_Lock] WHERE [Id] = @Id";
            dBRepository.ExecuteSqlCommand(sql, new SqlParameter("@Id", actionId));
        }
    }

    public class LockObject : IDisposable
    {
        private static readonly AveLogger logger = AveLogger.GetInstance(MethodBase.GetCurrentMethod().DeclaringType);
        private Action _disposeAction;
        public bool IsLockEnabled { get; private set; }
        public LockObject(Action disposeAction)
        {
            _disposeAction = disposeAction;
            IsLockEnabled = true;
        }
        public LockObject()
        {
            IsLockEnabled = false;
        }

        public void Dispose()
        {
            try
            {
                _disposeAction?.Invoke();
            }
            catch (Exception e)
            {
            }
        }
    }

  

 

posted @ 2020-03-03 17:41  云霄宇霁  阅读(245)  评论(0编辑  收藏  举报