C # Производственный поток-безопасный кэш LRU в памяти с истечением срока действия?

Это может быть похоже на просьбу о Луне на палке; но существует ли «Потоко-безопасный кэш LRU в памяти производственного качества C # с истечением срока действия»? Или у кого-нибудь есть идея передового опыта для достижения того же?

(LRU - "наименее недавно использованный" - http://en.wikipedia.org/wiki/Cache_algorithms#LRU)

Чтобы уточнить: я хочу поддерживать кеш памяти на сайте ASP.Net MVC со следующим интерфейсом:

public interface ICache
{
    T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class;
    bool Remove(string key);
}
  • Я хочу "GetOrAdd", потому что я хочу, чтобы эти операции были "атомарными", то есть во избежание условий гонки вокруг двух потоков, пытающихся одновременно запросить кеш.
  • Мне нужна функция Create, потому что создание этих объектов дорого (требует сложного доступа к базе данных)
  • Мне нужен срок действия, так как эти объекты действительно истекают через определенный период времени.

Лучшим решением от Microsoft кажется "System.Runtime.Caching.MemoryCache", однако, похоже, с парой оговорок:

  • Ему необходимо периодически опрашивать кеш, чтобы соответствовать установленным ограничениям памяти. У меня не может быть нехватки памяти в моей системе. Я прочитал этот пост, который меня беспокоит: MemoryCache не подчиняется ограничениям памяти в конфигурации
  • Кажется, что у него есть «AddOrGetExisting» для поддержки моего интерфейса, который принимает сконструированный объект в качестве второго параметра - если создание этого объекта дорого, разве его предварительное построение не побеждает точку кеширования?

Код будет выглядеть примерно так:

public sealed class Cache : ICache
{
    private readonly MemoryCache _cache;

    public Cache()
    {
        _cache = MemoryCache.Default;
    }

    public T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class
    {
        // This call kinda defeats the point of the cache ?!?
        var newValue = create();

        return _cache.AddOrGetExisting(key, newValue, DateTimeOffset.UtcNow + timeToLive) as T;
    }

    public bool Remove(string key)
    {
        _cache.Remove(key);
        return true;
    }
}

Или, может быть, что-то получше с Lazy ‹T>, которое позволяет создать результат только один раз, но похоже на взлом (есть ли последствия для кеширования Func?):

class Program
{
    static void Main(string[] args)
    {
        Func<Foo> creation = () =>
        {
            // Some expensive thing
            return new Foo();
        };

        Cache cache = new Cache();
        // Result 1 and 2 are correctly the same instance. Result 3 is correctly a new instance...
        var result1 = cache.GetOrAdd("myKey", creation, TimeSpan.FromMinutes(30));
        var result2 = cache.GetOrAdd("myKey", creation, TimeSpan.FromMinutes(30));
        var result3 = cache.GetOrAdd("myKey3", creation, TimeSpan.FromMinutes(30));

        return;
    }
}

public sealed class Foo
{
    private static int Counter = 0;
    private int Index = 0;

    public Foo()
    {
        Index = ++Counter;
    }
}

public sealed class Cache
{
    private readonly MemoryCache _cache;

    public Cache()
    {
        _cache = MemoryCache.Default;
    }

    public T GetOrAdd<T>(string key, Func<T> create, TimeSpan timeToLive) where T : class
    {
        var newValue = new Lazy<T>(create, LazyThreadSafetyMode.PublicationOnly);
        var value = (Lazy<T>)_cache.AddOrGetExisting(key, newValue, DateTimeOffset.UtcNow + timeToLive);
        return (value ?? newValue).Value;
    }

    public bool Remove(string key)
    {
        _cache.Remove(key);
        return true;
    }
}

Другие мысли:

  • Я также нашел эту реализацию, но она не позволяет указать срок действия на основе времени: Есть ли LRU-реализация IDictionary?
  • Может быть, есть реализация, использующая ReaderWriterLock?
  • Какая-то оболочка вокруг ConcurrentDictionary?

person Jon Rea    schedule 27.05.2015    source источник
comment
Попробуйте это   -  person Yuval Itzchakov    schedule 27.05.2015
comment
Этот проект устарел. Вы имеете в виду LurchTable CSharpTest.Net.Collections? github.com/csharptest/CSharpTest.Net.Collections - срок действия не поддерживается. ..   -  person Jon Rea    schedule 28.05.2015
comment
Странно, что нет хорошего проекта кеширования LRU. Может, придется самому завести :)   -  person Yuval Itzchakov    schedule 28.05.2015
comment
Ваш GetOrAdd метод довольно просто реализовать с помощью методов Get и Set, доступных на MemoryCache - вам не нужно использовать AddOrGetExisting. Что касается соблюдения ограничений по памяти, то практически невозможно реализовать это в строгом смысле в .NET, но если в вашей системе не настроена виртуальная память (что является ПЛОХОЙ идеей, и я сомневаюсь, что это так), на практике кеш будет выселять элементы очень быстро, как только вы достигнете высокого уровня использования памяти, и никогда не должно вызывать ошибку нехватки памяти.   -  person Mike Marynowski    schedule 21.07.2018


Ответы (1)


Я реализовал потокобезопасный псевдо-LRU, предназначенный для параллельных рабочих нагрузок - в настоящее время используется в производственной системе. Производительность очень близка к ConcurrentDictionary, примерно в 10 раз быстрее, чем MemoryCache, а частота совпадений лучше, чем у обычного LRU. Полный анализ представлен по ссылке на github ниже.

Использование выглядит так:

int capacity = 666;
var lru = new ConcurrentTLru<int, SomeItem>(capacity, TimeSpan.FromMinutes(5));

var value = lru.GetOrAdd(1, (k) => new SomeItem(k));
bool removed = lru.TryRemove(1);

GitHub: https://github.com/bitfaster/BitFaster.Caching

Install-Package BitFaster.Caching
person Alex Peck    schedule 16.06.2020
comment
Я вижу, что вы сравнивали с System.Runtime.Caching.MemoryCache. Как BitFaster.Caching по сравнению с Microsoft.Extensions.Caching.Memory.MemoryCache ? - person Theodor Zoulias; 16.06.2020
comment
AFAIK семейство классов MemoryCache имеет аналогичные характеристики производительности (System.Runtime.Caching.MemoryCache, HttpRuntime.Cache, System.Web.Caching, MicrosoftExtensions.Caching.Memory.MemoryCache). Все базовые реализации очень похожи. - person Alex Peck; 16.06.2020
comment
Microsoft.Extensions.Caching.Memory.MemoryCache новее, имеет object ключей и предлагает больше вариантов приоритизации. Я понятия не имею, эффективнее ли это, чем более старая реализация, но, честно говоря, мои ожидания таковы, что, вероятно, так оно и есть! - person Theodor Zoulias; 16.06.2020
comment
Я обновил свой тест попаданий в кеш, чтобы протестировать Microsoft.Extensions.Caching.Memory.MemoryCache, и в моем тесте он был немного медленнее, чем System.Runtime.Caching.MemoryCache. Этот тест показывает 100% попаданий в кэш, поэтому он никогда не будет хорошим предсказателем реальной производительности (где промахи обходятся дорого). Это просто показатель чистой скорости. Тем не менее, новый класс имеет те же проблемы с последовательным сканированием и неконтролируемым использованием памяти. Базовый алгоритм тот же, и у него есть те же плюсы и минусы. - person Alex Peck; 17.06.2020
comment
Спасибо, Алекс, что уделили время этим тестам. Я буду помнить о библиотеке BitFaster.Caching на тот случай, если мне когда-нибудь понадобится быстрое реализация кеш-памяти! - person Theodor Zoulias; 17.06.2020