5

我有一个在 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;
        }
    }
}

目的是让开发人员编写代码,“通过先尝试缓存来获取一些数据。如果缓存中不可用,则执行指定的函数,将结果放入缓存中以供下一个调用者使用,然后返回结果”。像这样:

var data = dataCache.Fetch("key", () => SomeLongRunningOperation());

锁定限制了对单个线程执行可能长时间运行的函数调用,但仅限于同一台机器上的单个进程内。您将如何扩展此模式以使锁定分布式以防止多个进程/机器同时执行该功能?

4

2 回答 2

8

AppFabric 有它自己的分布式锁定机制,您可以通过GetAndLock/PutAndUnlock一系列方法访问它。如果您的项目被锁定,正常Get调用仍会成功并返回最后一个值,但进一步GetAndLock调用将引发异常。在您的客户端第一次请求缓存对象的情况下,您仍然可以锁定密钥,即使它实际上还不存在(它更像是保留而不是实体锁定)。

public static T Fetch<T>(this DataCache @this, string key, Func<T> func, TimeSpan timeout)
{
    var result = @this.Get(key);

    if (result == null)
    (
        DataCacheLockHandle handle;
        // We need a timespan to allow func time to run
        TimeSpan funcTimespan = New TimeSpan(0,1,0);

        try
        {
            // Lock the key
            // If something goes wrong here it will unlock at the end of funcTimespan
            var result = @this.GetAndLock(key, funcTimespan, handle);

            if (result == null)
            {
                // Still no value so go and run func
                result = func();

                @this.PutAndUnlock(key, result, handle, timeout);
            }
            else
            {
                // There's a value now so we'll unlock the key and reset it's timeout
                @this.Unlock(key, handle, timeout);
            }
        }
        catch (DataCacheException ex)
        {
            if (ex.ErrorCode == DataCacheErrorCode.ObjectLocked)
            {
                // Another process has locked the key so func must be running right now
                // We'll return null to the client
                result = null;
            }
        }

        if (result == null)
        {
            return null;
        }
        else
        {
            return (T)result;
        }
    )
}
于 2011-02-24T12:58:30.803 回答
4

我一直在寻找一个很好的实现,并想出了我自己的:
Distributed Lock with AppFabric Caching

本质上,它是 DataCache 类的 AcquireLock() 扩展方法,您可以像这样使用它:

DataCache cache = factory.GetCache("MyCache");
using (cache.AcquireLock("MyLock"))
{
    // Lock acquired, perform synchronized work here
}
于 2013-07-30T19:30:56.327 回答