Caching 使用Windows Server AppFabric缓存的分布式锁服务
我在Windows Server AppFabric SDK中找到了Microsoft.ApplicationServer.Caching.DataCache对象的扩展方法,如下所示:Caching 使用Windows Server AppFabric缓存的分布式锁服务,caching,appfabric,distributed-caching,Caching,Appfabric,Distributed Caching,我在Windows Server AppFabric SDK中找到了Microsoft.ApplicationServer.Caching.DataCache对象的扩展方法,如下所示: using System; using System.Collections.Generic; using Microsoft.ApplicationServer.Caching; namespace Caching { public static class CacheExtensions {
using System;
using System.Collections.Generic;
using Microsoft.ApplicationServer.Caching;
namespace Caching
{
public static class CacheExtensions
{
private static Dictionary<string, object> locks = new Dictionary<string, object>();
public static T Fetch<T>(this DataCache @this, string key, Func<T> func)
{
return @this.Fetch(key, func, TimeSpan.FromSeconds(30));
}
public static T Fetch<T>(this DataCache @this, string key, Func<T> func, TimeSpan timeout)
{
var result = @this.Get(key);
if (result == null)
{
lock (GetLock(key))
{
result = @this.Get(key);
if (result == null)
{
result = func();
if (result != null)
{
@this.Put(key, result, timeout);
}
}
}
}
return (T)result;
}
private static object GetLock(string key)
{
object @lock = null;
if (!locks.TryGetValue(key, out @lock))
{
lock (locks)
{
if (!locks.TryGetValue(key, out @lock))
{
@lock = new object();
locks.Add(key, @lock);
}
}
}
return @lock;
}
}
}
DataCache cache = factory.GetCache("MyCache");
using (cache.AcquireLock("MyLock"))
{
// Lock acquired, perform synchronized work here
}
锁定限制了对单个线程执行可能长时间运行的函数调用,但只能在同一台机器上的单个进程内执行。您将如何扩展此模式以使锁定分布式,以防止多个进程/机器同时执行该功能?AppFabric有自己的分布式锁定机制,您可以通过
GetAndLock/PutAndUnlock
方法系列访问该机制。如果您的项目被锁定,正常的Get
调用仍将成功并返回最后一个值,但进一步的GetAndLock
调用将引发异常。在客户机第一次请求缓存对象的情况下,您仍然可以锁定密钥,即使它实际上还不存在(它更像是一个保留,而不是一个可靠的锁)
publicstatict-Fetch(this的这个数据缓存,字符串键,Func-Func,TimeSpan超时)
{
var result=@this.Get(key);
如果(结果==null)
(
DataCacheLockHandle;
//我们需要一个时间跨度来允许func运行时间
TimeSpan funcTimespan=新的TimeSpan(0,1,0);
尝试
{
//锁上钥匙
//如果此处出现问题,它将在时间跨度结束时解锁
var result=@this.GetAndLock(key,funcTimespan,handle);
如果(结果==null)
{
//仍然没有值,所以继续运行func
结果=func();
@此.PutAndUnlock(键、结果、句柄、超时);
}
其他的
{
//现在有一个值,所以我们将解锁密钥并重置它的超时
@此。解锁(钥匙、手柄、超时);
}
}
捕获(DataCacheException ex)
{
如果(ex.ErrorCode==DataCacheErrorCode.ObjectLocked)
{
//另一个进程已锁定密钥,因此func必须立即运行
//我们将向客户端返回null
结果=空;
}
}
如果(结果==null)
{
返回null;
}
其他的
{
返回(T)结果;
}
)
}
我正在寻找一个很好的实现方案,并提出了自己的:本质上,它是DataCache类的AcquireLock()扩展方法,您可以这样使用:
using System;
using System.Collections.Generic;
using Microsoft.ApplicationServer.Caching;
namespace Caching
{
public static class CacheExtensions
{
private static Dictionary<string, object> locks = new Dictionary<string, object>();
public static T Fetch<T>(this DataCache @this, string key, Func<T> func)
{
return @this.Fetch(key, func, TimeSpan.FromSeconds(30));
}
public static T Fetch<T>(this DataCache @this, string key, Func<T> func, TimeSpan timeout)
{
var result = @this.Get(key);
if (result == null)
{
lock (GetLock(key))
{
result = @this.Get(key);
if (result == null)
{
result = func();
if (result != null)
{
@this.Put(key, result, timeout);
}
}
}
}
return (T)result;
}
private static object GetLock(string key)
{
object @lock = null;
if (!locks.TryGetValue(key, out @lock))
{
lock (locks)
{
if (!locks.TryGetValue(key, out @lock))
{
@lock = new object();
locks.Add(key, @lock);
}
}
}
return @lock;
}
}
}
DataCache cache = factory.GetCache("MyCache");
using (cache.AcquireLock("MyLock"))
{
// Lock acquired, perform synchronized work here
}
这接近于我所寻找的,但我不想在另一个进程锁定密钥的情况下将null返回给客户机。我想等待另一个进程填充缓存,获取值,然后在调用方不知道任何不同的情况下返回。我最近用了一些Thread.Sleep来做这个。不是很理想,但只要你对重试设置一个上限,以防止死线程,应该是可行的。我发现这是一个非常好的解决方案,正是我所需要的,谢谢@盖伊·戈丁:你能详细说明一下“在这里执行同步工作”吗。我试图以原子的方式将一个对象放在缓存的不同区域中,使用相同的键。我在using块中未触及我的代码,但Dispose()方法中的unlock调用引发了一个异常:引用的对象未被任何客户端锁定。@omatrot也许您不小心移动了获得锁定的条目?“同步工作”是指在给定时间内只有一个服务实例在工作。@GuyGodin我使用cache.Put而不是cache.Add来管理缓存,这可能是问题的根源。@GuyGodin我通过稍微修改您的代码,将带有原始密钥的对象放在特定区域,以避免与using块内同步工作中的代码冲突,从而使其正常工作。工作得很漂亮。