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