.NET Cache Research


What's this

一个对.NET平台下缓存解决方案的探索关于:

Microsoft MemoryCache

Add Cache

Add方法会在已存在相同Key的缓存时返回false,所以不会重复添加。


MemoryCache.Default.Add(Warehouse.CACHE_KEY, Warehouse.Warehouses, DateTimeOffset.Now.AddSeconds(2));
var warehousesFromCache = MemoryCache.Default.Get(Warehouse.CACHE_KEY) as List<Warehouse>;
Assert.That(warehousesFromCache, Is.Not.Null);
Assert.That(warehousesFromCache.Count, Is.EqualTo(2));
		

Add Or Get Existing

返回新添加或已存在相同Key的缓存Value。


MemoryCache.Default.Add(Warehouse.CACHE_KEY, Warehouse.Warehouses, MemoryCache.InfiniteAbsoluteExpiration);
var caches = MemoryCache.Default.AddOrGetExisting(Warehouse.CACHE_KEY, Warehouse.Warehouses.Where(o => o.WarehouseNumber == "07").ToList(), MemoryCache.InfiniteAbsoluteExpiration) as List<Warehouse>;
Assert.That(caches.Count, Is.Not.EqualTo(1));
Assert.That(caches.Count, Is.EqualTo(2));
		

Update Cache

更新(替换)缓存。


MemoryCache.Default.Add(Warehouse.CACHE_KEY, Warehouse.Warehouses, MemoryCache.InfiniteAbsoluteExpiration);
var specialWarehouse = Warehouse.Warehouses.Where(o => o.WarehouseNumber == "07").ToList();
MemoryCache.Default.Set(Warehouse.CACHE_KEY, specialWarehouse, MemoryCache.InfiniteAbsoluteExpiration);
var caches = MemoryCache.Default.Get(Warehouse.CACHE_KEY) as List<Warehouse>;
Assert.That(caches, Is.Not.Null);
Assert.That(caches.Count, Is.EqualTo(1));
Assert.That(caches[0].WarehouseNumber == "07");
		

Cache Expire

有两种缓存过期策略:


MemoryCache.Default.Add(Warehouse.CACHE_KEY+"1", Warehouse.Warehouses, DateTimeOffset.Now.AddSeconds(2));
Thread.Sleep(2000);
var warehousesFromCache = MemoryCache.Default.Get(Warehouse.CACHE_KEY+"1") as List<Warehouse>;
Assert.That(warehousesFromCache, Is.Null);
		

Cache Refresh

当Cache丢失前,MemoryCache会回掉一个Callback委托进行通知,此时,为了让应用程序没有该缓存真空期,我们应该立即刷新缓存,保持最新的同时防止客户端出现缓存未命中(缓存丢失)的现象发生。


[Test]
public void CacheShouldRefreshSuccessAfterExpired()
{
    MemoryCache.Default.Set(Warehouse.CACHE_KEY, Warehouse.Warehouses, new CacheItemPolicy()
    {
        AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2),
        UpdateCallback = this.CacheRefreshCallback
    });

    Thread.Sleep(5000);
    var warehousesFromCache = MemoryCache.Default.Get(Warehouse.CACHE_KEY) as List<Warehouse>;
    Assert.That(warehousesFromCache, Is.Not.Null);
}

private void CacheRefreshCallback(CacheEntryUpdateArguments args)
{
    var cacheItem = MemoryCache.Default.GetCacheItem(args.Key);
    var cacheObj = cacheItem.Value;

    cacheItem.Value = cacheObj;
    args.UpdatedCacheItem = cacheItem;
    var policy = new CacheItemPolicy
    {
        UpdateCallback = new CacheEntryUpdateCallback(CacheRefreshCallback),
        AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(2)
    };

    args.UpdatedCacheItemPolicy = policy;
}
		

Cache Config File

在需要缓存文件内容时适用;且当文件内容更改时自动刷新缓存。


var configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "warehouse.txt");
var contents = MemoryCache.Default.Get("WarehouseConfig");
if (contents == null)
{
    var policy = new CacheItemPolicy()
    {
        AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30),
    };

    policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List<string>() { configFilePath }));
    MemoryCache.Default.Set("WarehouseConfig", File.ReadAllText(configFilePath), policy);
}

Thread.Sleep(5000);
var contentsFromCache = MemoryCache.Default.Get("WarehouseConfig");
Assert.That(contentsFromCache, Is.Not.Null);
Assert.That(contentsFromCache, Is.EqualTo("01,02,03"));
		

Open Source CacheManager

CacheManager是GitHub上一个开源的专注于.NET Cache领域项目,其有许多新的思想和概念是.NET MemoryCache所不具备的;相对于MemoryCache,其有以下特有feature:

Add Cache

添加缓存;已存在时返回false。


var cacheManager = CacheFactory.Build<List<Warehouse>>(setting =>
{
    setting.WithDictionaryHandle();
    setting.WithSystemRuntimeCacheHandle();
});

cacheManager.Add(Warehouse.CACHE_KEY, Warehouse.Warehouses);
var caches = cacheManager.Get(Warehouse.CACHE_KEY);
Assert.That(caches.Count, Is.EqualTo(2));
Assert.That(cacheManager.CacheHandles.Count, Is.EqualTo(2));
		

Add Or Get Existing

返回新添加的或已存在相同Key的缓存。


var cacheManager = CacheFactory.Build<List<Warehouse>>(settings =>
{
    settings.WithDictionaryHandle();
});

var caches = cacheManager.GetOrAdd(Warehouse.CACHE_KEY, Warehouse.Warehouses);
Assert.That(caches.Count, Is.EqualTo(2));
		

Add Or Update Existing

不存在就添加,存在就更新。


var manager = CacheFactory.Build<List<Company>>(settings =>
{
    settings.WithDictionaryHandle();
});

manager.Add(Company.CACHE_KEY, Company.MovieCompanies);
manager.AddOrUpdate(Company.CACHE_KEY, Company.ITCompanies, o => Company.ITCompanies);

var caches = manager.Get(Company.CACHE_KEY);
Assert.That(caches, Is.Not.Null);
Assert.That(caches.Count, Is.EqualTo(2));
Assert.That(caches[0].CompanyType, Is.EqualTo(CompanyType.ITCompany));
		

Update Cache

更新缓存。


var manager = CacheFactory.Build(settings =>
{
    settings.WithDictionaryHandle();
});

manager.Add(Company.CACHE_KEY, Company.MovieCompanies);
manager.Update(Company.CACHE_KEY, o => Company.ITCompanies.Union(o as List<Company>).ToList());
var caches = manager.Get<List<Company>>(Company.CACHE_KEY);

Assert.That(caches.Count, Is.EqualTo(4));
		

Create Cache Instance From Config

从配置文件加载缓存策略等信息。

Cache With Multiple Layers

将同一份数据缓存到多个Layer,会按Layer(Handle)的添加顺序去命中缓存(返回第一个命中的)。


var manager = CacheFactory.Build<List<Company>>(settings =>
 {
     settings.WithDictionaryHandle().WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(1));
     settings.WithSystemRuntimeCacheHandle();
     settings.WithUpdateMode(CacheUpdateMode.None);
 });

// all cache layer hold the ITCompanies.
manager.Add(Company.CACHE_KEY, Company.ITCompanies);

Thread.Sleep(1000);

var caches = manager.Get(Company.CACHE_KEY); // ONLY When GET: If find some layer was difference from others, the "Update" strategy will be executed.

Assert.That(caches, Is.Not.Null);
Assert.That(caches.Count, Is.EqualTo(2));

Assert.That(manager.CacheHandles.Count, Is.EqualTo(2));
Assert.That(manager.CacheHandles.First().Count, Is.EqualTo(0));
Assert.That(manager.CacheHandles.Last().Count, Is.GreaterThanOrEqualTo(1));
		

Cache Expire

缓存过期是针对每个Layer的,可以用连续调用的方式来初始化一个带有过期策略的CacheManager。


var manager = CacheFactory.Build<List<Company>>(settings =>
{
 settings.WithDictionaryHandle().WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(1));
 settings.WithSystemRuntimeCacheHandle();
 settings.WithUpdateMode(CacheUpdateMode.None);
});
		

注意这里的WithUpdateMode,CacheUpdateMode有三种值,分别代表:

Cache Statistics

Cache在各种Operation下的次数统计信息输出。


var manager = CacheFactory.Build<List<Company>>(settings =>
{
    settings.WithDictionaryHandle().WithExpiration(ExpirationMode.Absolute,             TimeSpan.FromSeconds(1)).EnableStatistics().EnablePerformanceCounters();    
    settings.WithSystemRuntimeCacheHandle().EnableStatistics();
    settings.WithUpdateMode(CacheUpdateMode.None);
});

// all cache layer hold the ITCompanies.
manager.Add(Company.CACHE_KEY, Company.ITCompanies);
manager.Put(Company.CACHE_KEY, Company.MovieCompanies);
manager.Remove(Company.CACHE_KEY);
manager.Add(Company.CACHE_KEY, Company.ITCompanies);

var cachesFinal = manager.Get(Company.CACHE_KEY);
foreach (var handle in manager.CacheHandles)
{
    var stats = handle.Stats;
    Console.WriteLine(string.Format(
            "Items: {0}, Hits: {1}, Miss: {2}, Remove: {3}, ClearRegion: {4}, Clear: {5}, Adds: {6}, Puts: {7}, Gets: {8}",
                stats.GetStatistic(CacheStatsCounterType.Items),
                stats.GetStatistic(CacheStatsCounterType.Hits),
                stats.GetStatistic(CacheStatsCounterType.Misses),
                stats.GetStatistic(CacheStatsCounterType.RemoveCalls),
                stats.GetStatistic(CacheStatsCounterType.ClearRegionCalls),
                stats.GetStatistic(CacheStatsCounterType.ClearCalls),
                stats.GetStatistic(CacheStatsCounterType.AddCalls),
                stats.GetStatistic(CacheStatsCounterType.PutCalls),
                stats.GetStatistic(CacheStatsCounterType.GetCalls)
            ));
}
		

Cache Logging

支持Cache在各种Operation下的日志记录。


var manager = CacheFactory.Build<List<Company>>(settings =>
{
    settings.WithDictionaryHandle().WithExpiration(ExpirationMode.Absolute,  TimeSpan.FromSeconds(1)).EnableStatistics().EnablePerformanceCounters();
    settings.WithSystemRuntimeCacheHandle().EnableStatistics();
    settings.WithUpdateMode(CacheUpdateMode.None);
    settings.WithLogging(typeof(CustomerLogFactory));
});

// all cache layer hold the ITCompanies.
manager.Add(Company.CACHE_KEY, Company.ITCompanies);
manager.Put(Company.CACHE_KEY, Company.MovieCompanies);
manager.Remove(Company.CACHE_KEY);
manager.Add(Company.CACHE_KEY, Company.ITCompanies);

var caches = manager.Get(Company.CACHE_KEY);
Assert.That(caches, Is.Not.Null);
		

OutPut Like Below:


CacheManager.Core.BaseCacheManager<object>: Trace: Add or update: key .
CacheManager.Core.BaseCacheManager<object>: Trace: Add: key 
CacheManager.Core.BaseCacheManager<object>: Trace: Add: key  to handle redis FAILED. Evicting items from other handles.
CacheManager.Core.BaseCacheManager<object>: Trace: Evict from other handles: key : excluding handle 1.
CacheManager.Core.BaseCacheManager<object>: Trace: Evict from handle: key : on handle default.
CacheManager.Core.BaseCacheManager<object>: Trace: Add or update: key : add failed, trying to update...
CacheManager.Core.BaseCacheManager<object>: Trace: Update: key .
CacheManager.Core.BaseCacheManager<object>: Trace: Update: key : tried on handle redis: result: Success.
CacheManager.Core.BaseCacheManager<object>: Trace: Evict from handles above: key : above handle 1.
CacheManager.Core.BaseCacheManager<object>: Trace: Evict from handle: key : on handle default.
CacheManager.Core.BaseCacheManager<object>: Trace: Add to handles below: key : below handle 1.
CacheManager.Core.BaseCacheManager<object>: Trace: Add or update: key : successfully updated.

		

(打赏)

If you want to pay for this
I will list your account name here.
HA HA!