.NET:离线悲观锁 之 过期策略支持

背景

之前写了一篇文章防止并发修改 之 离线悲观锁代码示例(离线悲观锁),这篇文章回避了一个问题,就是如何处理用户直接关闭浏览器后导致的锁占用问题。本文就介绍一个思路。

思路

思路1

这是之前已经提供过的思路,只是没有贴出来,就是:当会话结束的时候清除所有用户持有的锁,这会导致个别锁在会话期间被长时间占用(可能超过几个小时)。

思路2

引入一个后台线程,每隔指定的分钟就清理一下被长时间占用的锁,如:清理那些占用超过10分钟的锁,这回导致一定的线程成本,因为这个线程需要频繁的运行。

思路3

引入过期策略,是否被锁完全取决于两个条件:是否拥有锁以及是否过期,这个思路下过期的锁会成为一种垃圾,如何清理这种垃圾又是一个问题,我们可以每6个小时清理一次或引入环形字典。

基于过期策略的实现

类图

.NET:离线悲观锁 之 过期策略支持

代码

基于内存的离线悲观锁管理器

  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, LockItem> _items = new Dictionary<string, LockItem>();

 18 

 19         /// <inheritdoc />

 20         public bool AcquireLock(string entity, string key, string owner, IExpirationPolicy expirationPolicy)

 21         {

 22             entity.MustNotNullAndNotWhiteSpace("entity");

 23             key.MustNotNullAndNotWhiteSpace("key");

 24             owner.MustNotNullAndNotWhiteSpace("owner");

 25             expirationPolicy.MustNotNull("expirationPolicy");

 26 

 27             var item = LockItem.Crete(entity, key, owner, expirationPolicy);

 28 

 29             lock (_items)

 30             {

 31                 if (!IsLocked(item.Identifier))

 32                 {

 33                     LockIt(item);

 34 

 35                     return true;

 36                 }

 37 

 38                 return IsLockedBy(item.Identifier, item.Owner);

 39             }

 40         }

 41 

 42         /// <inheritdoc />

 43         public void ReleaseLock(string entity, string key, string owner)

 44         {

 45             entity.MustNotNullAndNotWhiteSpace("entity");

 46             key.MustNotNullAndNotWhiteSpace("key");

 47             owner.MustNotNullAndNotWhiteSpace("owner");

 48 

 49             var identifier = LockItem.CreateIdentifier(entity, key);

 50             lock (_items)

 51             {

 52                 if (!IsLockedBy(identifier, owner))

 53                 {

 54                     throw new InvalidOperationException(string.Format(Messages.Error_CanNotReleaseLock, owner));

 55                 }

 56 

 57                 ReleaseLock(identifier);

 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                         ReleaseLock(keypair.Value.Identifier);

 71                     }

 72                 }

 73             }

 74         }

 75 

 76         /// <inheritdoc />

 77         public void ReleaseExpiredLocks()

 78         {

 79             lock (_items)

 80             {

 81                 foreach (var keypair in _items)

 82                 {

 83                     if (keypair.Value.ExpirationPolicy.IsExpired())

 84                     {

 85                         ReleaseLock(keypair.Value.Identifier);

 86                     }

 87                 }

 88             }

 89         }

 90 

 91         private static bool IsLocked(string identifier)

 92         {

 93             return

 94                 _items.ContainsKey(identifier)

 95                 &&

 96                 !_items[identifier].ExpirationPolicy.IsExpired();

 97         }

 98 

 99         private static bool IsLockedBy(string identifier, string owner)

100         {

101             if (!IsLocked(identifier))

102             {

103                 return false;

104             }

105 

106             return _items[identifier].Owner == owner;

107         }

108 

109         private static void LockIt(LockItem item)

110         {

111             _items[item.Identifier] = item;

112         }

113 

114         private static void ReleaseLock(string identifier)

115         {

116             _items.Remove(identifier);

117         }

118     }

119 }

基于时间的过期策略

 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     [Serializable]

13     public class DateTimeExpirationPolicy : IExpirationPolicy

14     {

15         private readonly DateTime _start = DateTime.Now;

16         private readonly TimeSpan _expiration;

17 

18         /// <summary>

19         /// 构造方法。

20         /// </summary>

21         /// <param name="expiration">过期时间间隔</param>

22         public DateTimeExpirationPolicy(TimeSpan expiration)

23         {

24             _expiration = expiration;

25         }

26 

27         /// <summary>

28         /// 构造方法。

29         /// </summary>

30         /// <param name="minute">过期的分钟</param>

31         public DateTimeExpirationPolicy(uint? minute)

32         {

33             _expiration = TimeSpan.FromMinutes((double)minute);

34         }

35 

36         /// <summary>

37         /// 是否过期。

38         /// </summary>

39         public bool IsExpired()

40         {

41             return (DateTime.Now - _start) > _expiration;

42         }

43     }

44 }

每隔6小时进行一次垃圾清理

1             var lockManager = BootstrapService.Current.Container.GetInstance<ILockManager>();

2             var timer = new Timer(state =>

3             {

4                 lockManager.ReleaseExpiredLocks();

5             }, null, 1000 * 60 * 6, 1000 * 60 * 6);

备注

早上来的路上想到一个思路可以避免6小时清理一下垃圾,就是使用环形字典,找个时间我试试。

注意:6小时清理一次垃圾,并不代表6小时才过期的。

 

你可能感兴趣的:(.net)