Caching 使用Windows Server AppFabric缓存的分布式锁服务

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 {

我在Windows Server AppFabric SDK中找到了Microsoft.ApplicationServer.Caching.DataCache对象的扩展方法,如下所示:

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块内同步工作中的代码冲突,从而使其正常工作。工作得很漂亮。