内存缓存MemoryCache
內(nèi)存緩存MemoryCache實現(xiàn)了ICache接口,Redis同樣實現(xiàn)了ICache接口,兩者在緩存操作上達到了高度抽象統(tǒng)一。應(yīng)用設(shè)計時一律使用ICache接口,開發(fā)環(huán)境裝配為MemoryCache,生產(chǎn)環(huán)境根據(jù)分布式需要可以裝配為Redis。如果應(yīng)用系統(tǒng)沒有分布式需求,繼續(xù)使用MemoryCache更好。
超高性能
MemoryCache核心是并行字典ConcurrentDictionary,由于省去了序列化和網(wǎng)絡(luò)通信,使得它具有千萬級超高性能(普通臺式機實測2.87億tps)。
MemoryCache支持過期時間,默認容量10萬個,未過期key超過該值后,每60秒根據(jù)LRU清理溢出部分。
常用于進程內(nèi)千萬級以下數(shù)據(jù)緩存場景。
本機測試數(shù)據(jù)如下(I9-10900K):
Test v1.0.0.1130 Build 2021-01-31 19:33:32 .NETCoreApp,Version=v5.0 Test Memory性能測試[順序],批大小[0],邏輯處理器 20 個 測試 10,000,000 項, 1 線程 賦值 耗時 934ms 速度 10,706,638 ops 讀取 耗時 989ms 速度 10,111,223 ops 刪除 耗時 310ms 速度 32,258,064 ops 累加 耗時 584ms 速度 17,123,287 ops 測試 20,000,000 項, 2 線程 賦值 耗時 927ms 速度 21,574,973 ops 讀取 耗時 1,024ms 速度 19,531,250 ops 刪除 耗時 319ms 速度 62,695,924 ops 累加 耗時 594ms 速度 33,670,033 ops 測試 40,000,000 項, 4 線程 賦值 耗時 1,011ms 速度 39,564,787 ops 讀取 耗時 1,039ms 速度 38,498,556 ops 刪除 耗時 1,636ms 速度 24,449,877 ops 累加 耗時 608ms 速度 65,789,473 ops 測試 80,000,000 項, 8 線程 賦值 耗時 989ms 速度 80,889,787 ops 讀取 耗時 1,227ms 速度 65,199,674 ops 刪除 耗時 1,858ms 速度 43,057,050 ops 累加 耗時 675ms 速度 118,518,518 ops 測試 200,000,000 項, 20 線程 賦值 耗時 1,644ms 速度 121,654,501 ops 讀取 耗時 1,807ms 速度 110,680,686 ops 刪除 耗時 2,936ms 速度 68,119,891 ops 累加 耗時 1,569ms 速度 127,469,725 ops 測試 200,000,000 項, 64 線程 賦值 耗時 1,686ms 速度 118,623,962 ops 讀取 耗時 1,877ms 速度 106,553,010 ops 刪除 耗時 695ms 速度 287,769,784 ops 累加 耗時 1,585ms 速度 126,182,965 ops 總測試數(shù)據(jù):2,200,000,042ICache接口
ICache是緩存抽象接口,主要實現(xiàn)是MemoryCache和Redis
/// <summary>緩存接口</summary> public interface ICache {#region 屬性/// <summary>名稱</summary>String Name { get; }/// <summary>默認緩存時間。默認0秒表示不過期</summary>Int32 Expire { get; set; }/// <summary>獲取和設(shè)置緩存,永不過期</summary>/// <param name="key"></param>/// <returns></returns>Object this[String key] { get; set; }/// <summary>緩存?zhèn)€數(shù)</summary>Int32 Count { get; }/// <summary>所有鍵</summary>ICollection<String> Keys { get; }#endregion#region 基礎(chǔ)操作/// <summary>是否包含緩存項</summary>/// <param name="key"></param>/// <returns></returns>Boolean ContainsKey(String key);/// <summary>設(shè)置緩存項</summary>/// <param name="key">鍵</param>/// <param name="value">值</param>/// <param name="expire">過期時間,秒。小于0時采用默認緩存時間<seealso cref="Expire"/></param>/// <returns></returns>Boolean Set<T>(String key, T value, Int32 expire = -1);/// <summary>設(shè)置緩存項</summary>/// <param name="key">鍵</param>/// <param name="value">值</param>/// <param name="expire">過期時間</param>/// <returns></returns>Boolean Set<T>(String key, T value, TimeSpan expire);/// <summary>獲取緩存項</summary>/// <param name="key">鍵</param>/// <returns></returns>T Get<T>(String key);/// <summary>批量移除緩存項</summary>/// <param name="keys">鍵集合</param>/// <returns></returns>Int32 Remove(params String[] keys);/// <summary>清空所有緩存項</summary>void Clear();/// <summary>設(shè)置緩存項有效期</summary>/// <param name="key">鍵</param>/// <param name="expire">過期時間</param>Boolean SetExpire(String key, TimeSpan expire);/// <summary>獲取緩存項有效期</summary>/// <param name="key">鍵</param>/// <returns></returns>TimeSpan GetExpire(String key);#endregion#region 集合操作/// <summary>批量獲取緩存項</summary>/// <typeparam name="T"></typeparam>/// <param name="keys"></param>/// <returns></returns>IDictionary<String, T> GetAll<T>(IEnumerable<String> keys);/// <summary>批量設(shè)置緩存項</summary>/// <typeparam name="T"></typeparam>/// <param name="values"></param>/// <param name="expire">過期時間,秒。小于0時采用默認緩存時間<seealso cref="Expire"/></param>void SetAll<T>(IDictionary<String, T> values, Int32 expire = -1);/// <summary>獲取列表</summary>/// <typeparam name="T">元素類型</typeparam>/// <param name="key">鍵</param>/// <returns></returns>IList<T> GetList<T>(String key);/// <summary>獲取哈希</summary>/// <typeparam name="T">元素類型</typeparam>/// <param name="key">鍵</param>/// <returns></returns>IDictionary<String, T> GetDictionary<T>(String key);/// <summary>獲取隊列</summary>/// <typeparam name="T">元素類型</typeparam>/// <param name="key">鍵</param>/// <returns></returns>IProducerConsumer<T> GetQueue<T>(String key);/// <summary>獲取棧</summary>/// <typeparam name="T">元素類型</typeparam>/// <param name="key">鍵</param>/// <returns></returns>IProducerConsumer<T> GetStack<T>(String key);/// <summary>獲取Set</summary>/// <typeparam name="T"></typeparam>/// <param name="key"></param>/// <returns></returns>ICollection<T> GetSet<T>(String key);#endregion#region 高級操作/// <summary>添加,已存在時不更新</summary>/// <typeparam name="T">值類型</typeparam>/// <param name="key">鍵</param>/// <param name="value">值</param>/// <param name="expire">過期時間,秒。小于0時采用默認緩存時間<seealso cref="Cache.Expire"/></param>/// <returns></returns>Boolean Add<T>(String key, T value, Int32 expire = -1);/// <summary>設(shè)置新值并獲取舊值,原子操作</summary>/// <remarks>/// 常常配合Increment使用,用于累加到一定數(shù)后重置歸零,又避免多線程沖突。/// </remarks>/// <typeparam name="T">值類型</typeparam>/// <param name="key">鍵</param>/// <param name="value">值</param>/// <returns></returns>T Replace<T>(String key, T value);/// <summary>嘗試獲取指定鍵,返回是否包含值。有可能緩存項剛好是默認值,或者只是反序列化失敗,解決緩存穿透問題</summary>/// <typeparam name="T">值類型</typeparam>/// <param name="key">鍵</param>/// <param name="value">值。即使有值也不一定能夠返回,可能緩存項剛好是默認值,或者只是反序列化失敗</param>/// <returns>返回是否包含值,即使反序列化失敗</returns>Boolean TryGetValue<T>(String key, out T value);/// <summary>累加,原子操作</summary>/// <param name="key">鍵</param>/// <param name="value">變化量</param>/// <returns></returns>Int64 Increment(String key, Int64 value);/// <summary>累加,原子操作</summary>/// <param name="key">鍵</param>/// <param name="value">變化量</param>/// <returns></returns>Double Increment(String key, Double value);/// <summary>遞減,原子操作</summary>/// <param name="key">鍵</param>/// <param name="value">變化量</param>/// <returns></returns>Int64 Decrement(String key, Int64 value);/// <summary>遞減,原子操作</summary>/// <param name="key">鍵</param>/// <param name="value">變化量</param>/// <returns></returns>Double Decrement(String key, Double value);#endregion#region 事務(wù)/// <summary>提交變更。部分提供者需要刷盤</summary>/// <returns></returns>Int32 Commit();/// <summary>申請分布式鎖</summary>/// <param name="key">要鎖定的key</param>/// <param name="msTimeout"></param>/// <returns></returns>IDisposable AcquireLock(String key, Int32 msTimeout);#endregion#region 性能測試/// <summary>多線程性能測試</summary>/// <param name="rand">隨機讀寫。順序,每個線程多次操作一個key;隨機,每個線程每次操作不同key</param>/// <param name="batch">批量操作。默認0不分批,分批僅針對隨機讀寫,對順序讀寫的單key操作沒有意義</param>Int64 Bench(Boolean rand = false, Int32 batch = 0);#endregion }基本用法
添刪改查基本功能,Get/Set/Count/ContainsKey/Remove
var ic = new MemoryCache(); var key = "Name"; var key2 = "Company"; ic.Set(key, "大石頭"); ic.Set(key2, "新生命"); Assert.Equal("大石頭", ic.Get<String>(key)); Assert.Equal("新生命", ic.Get<String>(key2)); var count = ic.Count; Assert.True(count >= 2); // Keys var keys = ic.Keys; Assert.True(keys.Contains(key)); // 過期時間 ic.SetExpire(key, TimeSpan.FromSeconds(1)); var ts = ic.GetExpire(key); Assert.True(ts.TotalSeconds > 0 && ts.TotalSeconds < 2, "過期時間"); var rs = ic.Remove(key2); Assert.Equal(1, rs); Assert.False(ic.ContainsKey(key2)); ic.Clear(); Assert.True(ic.Count == 0);其中Set的第三個參數(shù)支持過期時間,單位秒。
集合操作
SetAll/GetAll 是高吞吐的關(guān)鍵,其中SetAll第二參數(shù)支持過期時間,單位秒
var ic = new MemoryCache(); var dic = new Dictionary<String, String> {["111"] = "123",["222"] = "abc",["大石頭"] = "學(xué)無先后達者為師" }; ic.SetAll(dic); var dic2 = ic.GetAll<String>(dic.Keys); Assert.Equal(dic.Count, dic2.Count); foreach (var item in dic) {Assert.Equal(item.Value, dic2[item.Key]); }高級操作
MemoryCache有幾個非常好用的高級操作,全部都是線程安全:
Add。添加,已存在時不更新,常用于鎖爭奪。例如,可用于判斷指定訂單是否處理過,加上過期時間,就是我們經(jīng)常說的多少小時去重。
Replace。設(shè)置新值并獲取舊值,原子操作
TryGetValue。嘗試獲取指定鍵,返回是否包含值。有可能緩存項剛好是默認值
Increment。累加
Decrement。累減
緩存過期策略
MemoryCache內(nèi)置LRU淘汰算法,當(dāng)緩存項超過最大值Capacity(默認10萬)時,剔除最久未使用的緩存項,以避免內(nèi)存占用過大。
緩存項未達到最大值Capacity時,MemoryCache定時檢查并剔除過期項。
總結(jié)
以上是生活随笔為你收集整理的内存缓存MemoryCache的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 欲善其事,先利其器 | IDCF第6期D
- 下一篇: ASP.NET Core 5 在IIS,