.NET中的緩存實現

来源:https://www.cnblogs.com/zhao123/archive/2019/06/21/11060084.html
-Advertisement-
Play Games

軟體開發中最常用的模式之一是緩存,這是一個簡單但非常有效的概念,想法是重用操作結果,執行繁重的操作時,我們會將結果保存在緩存容器中,下次我們需要該結果時,我們將從緩存容器中取出它,而不是再次執行繁重的操作。 例如,要獲得某人的頭像,您可能需要前往資料庫。我們不會每次都執行那次查詢,而是將結果保存在緩 ...


軟體開發中最常用的模式之一是緩存,這是一個簡單但非常有效的概念,想法是重用操作結果,執行繁重的操作時,我們會將結果保存在緩存容器中,下次我們需要該結果時,我們將從緩存容器中取出它,而不是再次執行繁重的操作。

例如,要獲得某人的頭像,您可能需要前往資料庫。我們不會每次都執行那次查詢,而是將結果保存在緩存中,每次需要時都將其從記憶體中刪除。

緩存非常適合不經常更改的數據,甚至永遠不會改變。不斷變化的數據不適合緩存,如當前機器的時間不應緩存,否則您將得到錯誤的結果。

進程內緩存,持久化緩存和分散式緩存

  • 進程內緩存用於在單個進程中實現緩存時,當進程終止時,緩存會隨之消失。如果您在多個伺服器上運行相同的進程,則每個伺服器都有一個單獨的緩存。
  • 持久化緩存是指在進程記憶體之外備份緩存,它可能位於文件中,也可能位於資料庫中。這實現比較困難,但如果重新啟動進程,緩存不會丟失。
  • 分散式緩存是指您為多台電腦提供共用緩存,通常它將是幾個伺服器,使用分散式緩存,它存儲在外部服務中。這意味著如果一臺伺服器保存了緩存項,其他伺服器也可以使用它。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,運行 的數據丟失了

這就是為什麼我們應該從Cache中刪除項目:

  1. 緩存可能占用大量記憶體,最終導致記憶體不足異常和崩潰。
  2. 高記憶體消耗可導致GC壓力(又稱記憶體壓力)。在這種狀態下,垃圾收集器的工作量超出預期,會影響性能。
  3. 如果數據發生更改,可能需要刷新緩存,我們的緩存基礎架構應該支持這種能力。

為了處理這些問題,緩存框架具有驅逐策略(即刪除策略),這些是根據某些邏輯從緩存中刪除項目的規則,常見的驅逐政策是:

  • 絕對過期策略將在一段固定的時間後從緩存中刪除一個項目。
  • 如果未在固定的時間內訪問項目,則滑動過期策略將從緩存中刪除項目因此,如果我將到期時間設置為1分鐘,只要我每隔30秒使用一次,該項目就會保持在緩存中,一旦我不使用它超過一分鐘,該項目被驅逐。
  • 大小限制策略將限制高速緩存大小。

現在我們知道了我們需要什麼,讓我們繼續尋找更好的解決方案。

改善方案

令我非常沮喪的是,作為博主,微軟已經創建了一個很棒的緩存實現,這剝奪了我自己創建類似實現的樂趣,但至少我寫這篇博文的工作較少。

我將向您展示Microsoft的解決方案,如何有效地使用它,以及如何在某些情況下改進它。

System.Runtime.Caching / MemoryCache與Microsoft.Extensions.Caching.Memory

微軟有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,所以改變了什麼?嗯,首先,這是一個線程安全的實現。您可以安全地從多個線程一次調用它。

帶有逐出政策的IMemoryCache:

/// <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;
    }
}

讓我們分析一下新增內容:

  1. SizeLimit加入了MemoryCacheOptions,這會將基於大小的策略添加到緩存容器中。相反,我們需要在每個緩存條目上設置大小,在這種情況下,我們每次設置為1 SetSize(1),這意味著緩存限製為1024個項目。
  2. 當我們達到大小限制時,應該刪除哪個緩存項?您實際上可以設置優先順序.SetPriority(CacheItemPriority.High)級別為Low,Normal,HighNeverRemove
  3. SetSlidingExpiration(TimeSpan.FromSeconds(2))添加了,將滑動到期時間設置為2秒,這意味著如果超過2秒內未訪問某個項目,它將被刪除。
  4. SetAbsoluteExpiration(TimeSpan.FromSeconds(10))添加了,它將絕對到期時間設置為10秒,這意味著如果物品尚未在10秒內被驅逐。

除了示例中的選項之外,您還可以設置一個RegisterPostEvictionCallback委托,當項目被驅逐時將調用委托。

這是一個非常全面的功能集。它讓你想知道是否還有其他東西要添加,實際上有幾件事。

問題和缺失的功能

這個實現中有幾個重要的缺失部分。

  1. 雖然您可以設置大小限制,但緩存實際上並不監視gc壓力。如果我們確實對其進行監控,我們可以在壓力較大時收緊政策,併在壓力較低時放鬆政策。
  2. 當同時請求具有多個線程的相同項時,請求不等待第一個完成,該項目將被多次創建。例如,假設我們正在緩存阿凡達,從資料庫中獲取頭像需要10秒鐘,如果我們在第一次請求後2秒請求頭像,它將檢查頭像是否被緩存(它還沒有),並開始另一次訪問資料庫。

英文原文中有說明,但是覺得不太好,再次沒有翻譯。

英文原文地址:

https://michaelscodingspot.com/cache-implementations-in-csharp-net/?utm_source=csharpdigest&utm_medium=web&utm_campaign=featured

代碼與所寫有所修改,但是大致意思一樣,如果感興趣,可以看看英文。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 迴圈結構 :for 迴圈四要素: 1.初始化條件 2.迴圈條件 3.迴圈體 4.迭代條件 格式: for(初始化條件;迴圈條件;迭代條件){ 迴圈體; } 執行順序 :1 -> 2 -> 3 -> 4 ->2 -> 3 -> 4 ...... 2 說明: 1.初始化條件只執行一次 2.迴圈條件的結果 ...
  • 迴圈結構 :do-while 迴圈四要素: 1.初始化條件 2.迴圈條件 3.迴圈體 4.迭代條件 格式: 1.初始化條件 do{ 3.迴圈體 4.迭代條件 }while(2.迴圈條件); do-while和while的區別? ...
  • ``` Date: 表示特定的瞬間,精確到毫秒,通過方法設定自己所表示的時間,可以表示任意的時間 System.currentTimeMillis() :返回的當前系統時間, 1970-1-1 至今的毫秒數 SimpleDateFormat sdf = new SimpleDateFormat("y... ...
  • 1.遍歷key值 同理,可把下列代碼的d改為d.keys() notice:python2除了上述兩種方法外,還可以寫為d.iterkeys() 2.遍歷value值 同理,可把下列代碼的d改為d.values() notice:python2除了上述兩種方法外,還可以寫為d.itervalues( ...
  • 容器可設置佈局管理器,管理容器中組件的佈局: container.setLayout(new XxxLayout()); Java有6種佈局管理器,AWT提供了5種: FlowLayout BorderLayout GridLayout GridBagLayout CradLayout Swing還 ...
  • Github https://github.com/gongluck/SDL2 study/tree/master/Csdl2 Csdl2.h Csdl2.cpp 測試 C++ include "Csdl2.h" include include include define TESTCHECKRET ...
  • 讓我們考慮一個簡單的編程挑戰:對大數組中的所有元素求和。現在可以通過使用並行性來輕鬆優化這一點,特別是對於具有數千或數百萬個元素的巨大陣列,還有理由認為,並行處理時間應該與常規時間除以CPU核心數一樣多。事實證明,這一壯舉並不容易實現。我將向您展示幾種並行執行此操作的方法,它們如何改善或降低性能以及 ...
  • 在前面兩篇隨筆《ABP開發框架前後端開發系列---(7)系統審計日誌和登錄日誌的管理》和《ABP開發框架前後端開發系列---(8)ABP框架之Winform界面的開發過程》開始介紹了許可權管理的內容,其中只是列出了內部的許可權系統的審計和登陸信息,以及對Winform界面的整合,本篇隨筆繼續介紹ABP開... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...