Это может быть похоже на просьбу о Луне на палке; но существует ли «Потоко-безопасный кэш 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?
GetOrAdd
метод довольно просто реализовать с помощью методовGet
иSet
, доступных наMemoryCache
- вам не нужно использоватьAddOrGetExisting
. Что касается соблюдения ограничений по памяти, то практически невозможно реализовать это в строгом смысле в .NET, но если в вашей системе не настроена виртуальная память (что является ПЛОХОЙ идеей, и я сомневаюсь, что это так), на практике кеш будет выселять элементы очень быстро, как только вы достигнете высокого уровня использования памяти, и никогда не должно вызывать ошибку нехватки памяти. - person Mike Marynowski   schedule 21.07.2018