Служба распределенной блокировки с Windows Server AppFabric Caching

У меня есть метод расширения для объекта Microsoft.ApplicationServer.Caching.DataCache в Windows Server AppFabric SDK, который выглядит следующим образом:

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 ответа

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

Я искал хорошую реализацию этого и придумал свое:
Распределенная блокировка с кешированием AppFabric

По сути, это метод расширения AcquireLock() для класса DataCache, который вы можете использовать следующим образом:

DataCache cache = factory.GetCache("MyCache");
using (cache.AcquireLock("MyLock"))
{
    // Lock acquired, perform synchronized work here
}
Другие вопросы по тегам