背景
系統會出現並發,上篇文章我介紹了如何使用“離線樂觀鎖”保證並發,離線樂觀鎖適合處理那些重新編輯成本不大的單據,如果某個單據用戶花了10分鍾進行編輯,提交時你告訴他出現並發了,他心里肯定會罵娘的,今天介紹的“離線悲觀鎖”就可以避免這種情況。
思路
小明簽出了源代碼,小強就不能簽出了,我們目前的源代碼系統就是用的這種悲觀策略。
實現
核心代碼
離線悲觀鎖管理器接口
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Happy.Application.PessimisticLock 8 { 9 /// <summary> 10 /// 離線悲觀鎖管理器接口。 11 /// </summary> 12 public interface ILockManager 13 { 14 /// <summary> 15 /// 獲取鎖。 16 /// </summary> 17 /// <param name="entity">鎖的主體,如:表名或聚合名。</param> 18 /// <param name="key">鎖的主體的唯一標識,如:主鍵或唯一索引。</param> 19 /// <param name="owner">鎖的擁有者,如:UserId或SessionId。</param> 20 /// <returns>獲取鎖成功就返回true,否則返回false。</returns> 21 bool AcquireLock(string entity, string key, string owner); 22 23 /// <summary> 24 /// 釋放鎖。 25 /// </summary> 26 /// <param name="entity">鎖的主體,如:表名或聚合名。</param> 27 /// <param name="key">鎖的主體的唯一標識,如:主鍵或唯一索引。</param> 28 /// <param name="owner">鎖的擁有者,如:UserId或SessionId。</param> 29 void ReleaseLock(string entity, string key, string owner); 30 31 32 /// <summary> 33 /// 釋擁有者的所有鎖。 34 /// </summary> 35 /// <param name="owner">鎖的擁有者,如:UserId或SessionId。</param> 36 void ReleaseLocks(string owner); 37 } 38 }
基於內存的離線悲觀鎖管理器
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Happy.DesignByContract; 8 using Happy.Application.PessimisticLock.Internal; 9 10 namespace Happy.Application.PessimisticLock 11 { 12 /// <summary> 13 /// 基於內存的離線悲觀鎖管理器。 14 /// </summary> 15 public sealed class MemoryLockManager : ILockManager 16 { 17 private static readonly Dictionary<string, MemoryLockItem> _items = new Dictionary<string, MemoryLockItem>(); 18 19 /// <inheritdoc /> 20 public bool AcquireLock(string entity, string key, string owner) 21 { 22 entity.MustNotNullAndNotWhiteSpace("entity"); 23 key.MustNotNullAndNotWhiteSpace("key"); 24 owner.MustNotNullAndNotWhiteSpace("owner"); 25 26 var item = MemoryLockItem.Crete(entity, key, owner); 27 28 lock (_items) 29 { 30 if (!IsLocked(item.Identifier)) 31 { 32 SetLockItem(item); 33 34 return true; 35 } 36 37 return IsLockedBy(item); 38 } 39 } 40 41 /// <inheritdoc /> 42 public void ReleaseLock(string entity, string key, string owner) 43 { 44 entity.MustNotNullAndNotWhiteSpace("entity"); 45 key.MustNotNullAndNotWhiteSpace("key"); 46 owner.MustNotNullAndNotWhiteSpace("owner"); 47 48 var item = MemoryLockItem.Crete(entity, key, owner); 49 50 lock (_items) 51 { 52 if (!IsLockedBy(item)) 53 { 54 throw new InvalidOperationException(string.Format(Messages.Error_CanNotReleaseLock, owner)); 55 } 56 57 RemoveLockItem(item); 58 } 59 } 60 61 /// <inheritdoc /> 62 public void ReleaseLocks(string owner) 63 { 64 lock (_items) 65 { 66 foreach (var keypair in _items) 67 { 68 if (keypair.Value.Owner == owner) 69 { 70 RemoveLockItem(keypair.Value); 71 } 72 } 73 } 74 } 75 76 private static bool IsLocked(string identifier) 77 { 78 return _items.ContainsKey(identifier); 79 } 80 81 private static void SetLockItem(MemoryLockItem item) 82 { 83 _items[item.Identifier] = item; 84 } 85 86 private static bool IsLockedBy(MemoryLockItem item) 87 { 88 if (!IsLocked(item.Identifier)) 89 { 90 return false; 91 } 92 93 return _items[item.Identifier].Owner == item.Owner; 94 } 95 96 private static void RemoveLockItem(MemoryLockItem item) 97 { 98 _items.Remove(item.Identifier); 99 } 100 } 101 }
離線悲觀鎖代理
1 /** 2 * 離線悲觀鎖代理。 3 * 4 * @static 5 * @class PessimisticLockProxy 6 * @namespace Happy.server 7 */ 8 Ext.define('Happy.server.PessimisticLockProxy', { 9 alternateClassName: ['PessimisticLockProxy'], 10 singleton: true, 11 requires: ['Happy.Ajax'], 12 13 acquireLock: function (entity, key, success, failure) { 14 var me = this; 15 16 Happy.Ajax.callAction({ 17 url: '/LockManager/AcquireLock', 18 params: { entity: entity, key: key }, 19 success: success, 20 failure: failure 21 }); 22 }, 23 24 releaseLock: function (entity, key, success, failure) { 25 var me = this; 26 27 Happy.Ajax.callAction({ 28 url: '/LockManager/ReleaseLock', 29 params: { entity: entity, key: key }, 30 success: success, 31 failure: failure 32 }); 33 } 34 });
運行效果
代碼下載
地址:http://happy.codeplex.com/SourceControl/latest。因為項目正在重構中,請下載最新源代碼,不要下載Happy-1.0.0.3。
如何使用代碼
備注
盡量通過合理的設計規避離線悲觀鎖,應用場景不會有很多,有使用過的朋友,請留下您寶貴的意見。