您的位置:首页 > 其它

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

2013-06-05 08:11 232 查看

背景

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

思路

思路1

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

思路2

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

思路3

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

基于过期策略的实现

类图



代码

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Happy.DesignByContract;
using Happy.Application.PessimisticLock.Internal;

namespace Happy.Application.PessimisticLock
{
/// <summary>
/// 基于内存的离线悲观锁管理器。
/// </summary>
public sealed class MemoryLockManager : ILockManager
{
private static readonly Dictionary<string, LockItem> _items = new Dictionary<string, LockItem>();

/// <inheritdoc />
public bool AcquireLock(string entity, string key, string owner, IExpirationPolicy expirationPolicy)
{
entity.MustNotNullAndNotWhiteSpace("entity");
key.MustNotNullAndNotWhiteSpace("key");
owner.MustNotNullAndNotWhiteSpace("owner");
expirationPolicy.MustNotNull("expirationPolicy");

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

lock (_items)
{
if (!IsLocked(item.Identifier))
{
LockIt(item);

return true;
}

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

/// <inheritdoc />
public void ReleaseLock(string entity, string key, string owner)
{
entity.MustNotNullAndNotWhiteSpace("entity");
key.MustNotNullAndNotWhiteSpace("key");
owner.MustNotNullAndNotWhiteSpace("owner");

var identifier = LockItem.CreateIdentifier(entity, key);
lock (_items)
{
if (!IsLockedBy(identifier, owner))
{
throw new InvalidOperationException(string.Format(Messages.Error_CanNotReleaseLock, owner));
}

ReleaseLock(identifier);
}
}

/// <inheritdoc />
public void ReleaseLocks(string owner)
{
lock (_items)
{
foreach (var keypair in _items)
{
if (keypair.Value.Owner == owner)
{
ReleaseLock(keypair.Value.Identifier);
}
}
}
}

/// <inheritdoc />
public void ReleaseExpiredLocks()
{
lock (_items)
{
foreach (var keypair in _items)
{
if (keypair.Value.ExpirationPolicy.IsExpired())
{
ReleaseLock(keypair.Value.Identifier);
}
}
}
}

private static bool IsLocked(string identifier)
{
return
_items.ContainsKey(identifier)
&&
!_items[identifier].ExpirationPolicy.IsExpired();
}

private static bool IsLockedBy(string identifier, string owner)
{
if (!IsLocked(identifier))
{
return false;
}

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

private static void LockIt(LockItem item)
{
_items[item.Identifier] = item;
}

private static void ReleaseLock(string identifier)
{
_items.Remove(identifier);
}
}
}


基于时间的过期策略

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Happy.Application.PessimisticLock
{
/// <summary>
/// 基于时间的过期策略。
/// </summary>
[Serializable]
public class DateTimeExpirationPolicy : IExpirationPolicy
{
private readonly DateTime _start = DateTime.Now;
private readonly TimeSpan _expiration;

/// <summary>
/// 构造方法。
/// </summary>
/// <param name="expiration">过期时间间隔</param>
public DateTimeExpirationPolicy(TimeSpan expiration)
{
_expiration = expiration;
}

/// <summary>
/// 构造方法。
/// </summary>
/// <param name="minute">过期的分钟</param>
public DateTimeExpirationPolicy(uint? minute)
{
_expiration = TimeSpan.FromMinutes((double)minute);
}

/// <summary>
/// 是否过期。
/// </summary>
public bool IsExpired()
{
return (DateTime.Now - _start) > _expiration;
}
}
}


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

var lockManager = BootstrapService.Current.Container.GetInstance<ILockManager>();
var timer = new Timer(state =>
{
lockManager.ReleaseExpiredLocks();
}, null, 1000 * 60 * 6, 1000 * 60 * 6);


备注

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

注意:6小时清理一次垃圾,并不代表6小时才过期的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: