分布式多线程的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操作
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 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) { } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)