分布式多线程的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)
{
}
}
}

浙公网安备 33010602011771号