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

背景

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

思路

思路1

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

思路2

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

思路3

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

基于过期策略的实现

类图

代码

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

  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小时才过期的。

 

posted on 2013-06-05 08:11  幸福框架  阅读(1273)  评论(2编辑  收藏  举报

导航

我要啦免费统计