軟件開發中最經常使用的模式之一是緩存,這是一個簡單但很是有效的概念,想法是重用操做結果,執行繁重的操做時,咱們會將結果保存在緩存容器中,下次咱們須要該結果時,咱們將從緩存容器中取出它,而不是再次執行繁重的操做。web
例如,要得到某人的頭像,您可能須要前往數據庫。咱們不會每次都執行那次查詢,而是將結果保存在緩存中,每次須要時都將其從內存中刪除。redis
緩存很是適合不常常更改的數據,甚至永遠不會改變。不斷變化的數據不適合緩存,如當前機器的時間不該緩存,不然您將獲得錯誤的結果。數據庫
public class NaiveCache<T> { private static Dictionary<object, T> _cache = new Dictionary<object, T>(); public static T GetOrCreate(object key, Func<T> createItem) { T cacheEntry; if (!_cache.TryGetValue(key, out cacheEntry)) { cacheEntry = createItem(); _cache.Add(key, cacheEntry); } return cacheEntry; } }
//用法
NaiveCache<string>.GetOrCreate("test", () => { return "test123"; });
這個簡單的代碼解決了一個關鍵問題,要獲取test的值,只有第一個請求才會實際執行數據庫操做,而後將數據保存在進程存儲器中,之後有關test的請求都將從內存中提取,從而節省時間和資源。編程
可是,做爲編程中的大多數事情,沒有什麼是如此簡單。因爲許多緣由,上述解決方案並很差。首先,這種實現不是線程安全的,多個線程使用時可能會發生異常,除此以外,緩存的項目將永遠留在內存中,這實際上很是糟糕。緩存
例如:安全
List<Task> t1 = new List<Task>(); foreach (var item in list) { var a = Task.Run(() => { Console.Write($"{NaiveCache<string>.GetOrCreate(item, () => { return item.ToString(); })}"); }); t1.Add(a); } try { Task.WaitAll(t1.ToArray()); } catch { }
運行結果7234859,運行 的數據丟失了服務器
爲了處理這些問題,緩存框架具備驅逐策略(即刪除策略),這些是根據某些邏輯從緩存中刪除項目的規則,常見的驅逐政策是:架構
如今咱們知道了咱們須要什麼,讓咱們繼續尋找更好的解決方案。框架
令我很是沮喪的是,做爲博主,微軟已經建立了一個很棒的緩存實現,這剝奪了我本身建立相似實現的樂趣,但至少我寫這篇博文的工做較少。分佈式
我將向您展現Microsoft的解決方案,如何有效地使用它,以及如何在某些狀況下改進它。
微軟有2個解決方案,2個不一樣的NuGet包用於緩存,二者都很棒,根據微軟的建議,更喜歡使用Microsoft.Extensions.Caching.Memory
由於它與Asp更好地集成.NET核心。它能夠很容易地注入到Asp .NET Core的依賴注入機制中。
這是一個基本的例子Microsoft.Extensions.Caching.Memory
:
/// <summary> /// 利用微軟的庫寫的緩存 /// </summary> /// <typeparam name="T"></typeparam> public class SimpleMemoyCache<T> { private static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions()); public static T GetOrCreate(object key, Func<T> createItem) { T cacheEntry; if (!_cache.TryGetValue(key, out cacheEntry)) { cacheEntry = createItem(); _cache.Set(key, cacheEntry); } return cacheEntry; } }
用法:
SimpleMemoyCache<string>.GetOrCreate("test", () => { return "test123"; });
這與我本身很是類似NaiveCache
,因此改變了什麼?嗯,首先,這是一個線程安全的實現。您能夠安全地從多個線程一次調用它。
/// <summary> /// 帶有策略的緩存 /// </summary> /// <typeparam name="T"></typeparam> public class MemoryCacheWithPolicy<T> { /// <summary> /// 增長設置緩存大小 /// </summary> private static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions() { SizeLimit = 1024 }); public static T GetOrCreate(object key, Func<T> createItem) { T cacheEntry; if (!_cache.TryGetValue(key, out cacheEntry)) { cacheEntry = createItem(); var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSize(1) .SetPriority(CacheItemPriority.High) //設置優先級 .SetSlidingExpiration(TimeSpan.FromSeconds(2)) //2s沒有訪問刪除 .SetAbsoluteExpiration(TimeSpan.FromSeconds(10)); //10s過時 _cache.Set(key, cacheEntry, cacheEntryOptions); } return cacheEntry; } }
讓咱們分析一下新增內容:
SizeLimit
加入了MemoryCacheOptions,
這會將基於大小的策略添加到緩存容器中。相反,咱們須要在每一個緩存條目上設置大小,在這種狀況下,咱們每次設置爲1 SetSize(1),
這意味着緩存限制爲1024個項目。.SetPriority(CacheItemPriority.High)
。級別爲Low,Normal,High和NeverRemove。SetSlidingExpiration(TimeSpan.FromSeconds(2))
添加了,將滑動到期時間設置爲2秒,這意味着若是超過2秒內未訪問某個項目,它將被刪除。SetAbsoluteExpiration(TimeSpan.FromSeconds(10))
添加了,它將絕對到期時間設置爲10秒,這意味着若是物品還沒有在10秒內被驅逐。除了示例中的選項以外,您還能夠設置一個RegisterPostEvictionCallback
委託,當項目被驅逐時將調用該委託。
這是一個很是全面的功能集。它讓你想知道是否還有其餘東西要添加,實際上有幾件事。
這個實現中有幾個重要的缺失部分。
英文原文中有說明,可是以爲不太好,再次沒有翻譯。
英文原文地址:
代碼與所寫有所修改,可是大體意思同樣,若是感興趣,能夠看看英文。