• 离线悲观锁 之 过期策略支持


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

    背景

     

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

     

    思路

     

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

  • 相关阅读:
    cenos安装memcache
    微信开发——测试号申请,接口配置,JS接口安全域名,自定义菜单
    mysql设计-优化
    mysql设计-基本操作
    CI框架部署后访问出现404
    ueditor的bug
    git操作
    github基本操作
    基于SSH协议clone GitHub远端仓库到本地-git
    Thinkphp5.0 路由
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3119055.html
Copyright © 2020-2023  润新知