【问题标题】:Distributed Lock Service with Windows Server AppFabric Caching具有 Windows Server AppFabric 缓存的分布式锁服务
【发布时间】:2011-02-23 16:51:00
【问题描述】:

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

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

【问题讨论】:

    标签: caching appfabric distributed-caching


    【解决方案1】:

    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;
            }
        )
    }
    

    【讨论】:

    • 这与我正在寻找的内容很接近,但我不想在另一个进程锁定密钥的情况下将 null 返回给客户端。我想等待其他进程填充缓存,获取值,然后在调用者不知道任何不同的情况下返回。
    • 所以简单地围绕 GetAndLock 行为封装重试逻辑。我最近使用一些 Thread.Sleep 完成了这项工作。不理想,但只要你限制重试以防止死线程,应该是可行的。
    【解决方案2】:

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

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

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

    【讨论】:

    • 我发现这是一个非常好的解决方案,正是我所需要的,谢谢!
    • @Guy Godin:您能否详细说明“在此处执行同步工作”。我正在尝试以原子方式将对象放在缓存的不同区域中,并使用相同的键。我的代码在 using 块内保持不变,但 Dispose() 方法中的解锁调用引发异常:被引用的对象未被任何客户端锁定。
    • @omatrot 也许您不小心移动了您获得锁定的条目?同步工作是指在给定时间只有一个服务实例在工作。
    • @GuyGodin 我用的是cache.Put而不是cache.Add来管理缓存,可能这是问题的根源。
    • @GuyGodin 我已经设法通过稍微修改您的代码以将对象与从原始对象派生的密钥放在特定区域中来使其工作,以避免与内部同步工作中的代码冲突使用块。效果很好。
    猜你喜欢
    • 2011-12-24
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 2012-10-22
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    相关资源
    最近更新 更多