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

时间:2011-02-23 16:51:00

标签: 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
    {
        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());

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

2 个答案:

答案 0 :(得分: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;
        }
    )
}

答案 1 :(得分:4)

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

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

DataCache cache = factory.GetCache("MyCache");
using (cache.AcquireLock("MyLock"))
{
    // Lock acquired, perform synchronized work here
}