zoukankan      html  css  js  c++  java
  • 分布式多线程的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)
                {
                }
            }
        }
    

      

  • 相关阅读:
    1.时间复杂度与空间复杂度分析
    数据结构与算法之重头再来
    MySQL时间字段与业务代码取出的时间不一致问题
    [redtiger]在线靶场level3
    win10 卡顿 MsMpEng.exe进程
    react 笔记 局部打印 print
    react table td 自动换行
    kali apt-get update release文件过期
    ubuntu怎么切换到root用户,切换到root账号方法
    winscp连接kali 使用预置密码验证 拒绝访问
  • 原文地址:https://www.cnblogs.com/qindy/p/12403469.html
Copyright © 2011-2022 走看看