ASP.NET Core 中的分散式快取

作者:Mohsin Nasirsmandia

Note

這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本

Warning

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 關於目前版本,請參閱 本文的 .NET 10 版本

分散式快取是由多個應用程式伺服器共享的快取。 快取通常作為外部服務維護,供存取快取的應用程式伺服器使用。 分散式快取能提升 ASP.NET Core 應用程式的效能與可擴展性,尤其當雲端服務或伺服器叢集承載該應用程式時。

分散式快取相比其他快取案例有幾項優點:快取資料會儲存在個別的應用程式伺服器上。

當快取資料被分發時,資料會如下:

  • 在多個伺服器的請求之間保持一致性
  • 即使在伺服器重新啟動和應用程式部署後,依然能夠保持存續。
  • 不使用本機記憶體。

依實作而定的分散式快取組態。 本文說明如何設定 SQL Server、Redis 或 Postgres 分散式快取。 也有非 Microsoft 的實作,如 NCacheGitHub 上的 NCache)、Azure Cosmos DB 和 Postgres。 無論選擇哪種實作,應用程式都會透過介面 IDistributedCache 與快取互動。

檢視或下載範例程式碼 \(英文\) (如何下載)

Warning

本文使用本機資料庫,其不需要使用者進行驗證。 實際執行應用程式應該使用可用的最安全驗證流程。 如需已部署測試與實際執行應用程式驗證的詳細資訊,請參閱安全驗證流程

Prerequisites

為使用的分散式快取提供者新增套件引用:

使用 IDistributedCache 介面

IDistributedCache 介面提供下列方法來操作分散式快取實作中的項目:

  • GetGetAsync:接受字串鍵值,如果在快取中找到,則擷取快取資料以byte[]陣列的形式。
  • SetSetAsync: 透過字串鍵將一個項目(以 byte[] 陣列形式)加入快取。
  • RefreshRefreshAsync:根據快取索引鍵重新整理快取中的項目,重設滑動期限 (若有) 的逾時設定。
  • RemoveRemoveAsync:移除快取項目(根據其字串鍵)。

建立分散式快取系統

IDistributedCache 檔案中註冊一個 的實現。 本文將介紹以下框架提供的實作:

分散式 Redis 快取

分散式 Redis 快取提供最佳效能,建議用於生產應用程式。 Redis 是一種開放原始碼的記憶體內部資料存放區,通常用來作為分散式快取。 您可以為 Azure 裝載的 ASP.NET Core 應用程式設定 Azure Cache for Redis,並可使用 Azure Cache for Redis 進行本機開發。 欲了解更多資訊,請參閱 「檢視快取建議」。

應用程式透過呼叫AddStackExchangeRedisCache方法來配置RedisCache快取實作。 對於 輸出快取,請使用 AddStackExchangeRedisOutputCache 方法。

  1. 建立 Azure Cache for Redis 的執行個體。

  2. 將主要連接字串(StackExchange.Redis)複製到 Configuration

    • 在本地開發環境中:使用Secret Manager儲存连接字串。

    • 用於 Azure:將連接字串存於安全儲存,例如 Azure Key Vault

下列程式碼會啟用 Azure Cache for Redis:

builder.Services.AddStackExchangeRedisCache(options =>
 {
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
     options.InstanceName = "SampleInstance";
 });

前述程式碼假設主要連接字串(StackExchange.Redis)在設定中以鍵名 MyRedisConStr 儲存。

更多資訊請參見 Azure Managed Redis

關於本地 Redis 快取替代方案的討論,請參見 GitHub /dotnet/aspnetcore issue #19542

分散式記憶體快取

分散式記憶體快取(AddDistributedMemoryCache)是一個由框架提供的 IDistributedCache 實作,將項目儲存在記憶體中。 然而,分散式記憶體快取並不是真正的分散式快取。 應用程式實例會將快取的項目儲存在執行該應用程式的伺服器上。

分散式記憶體快取是開發與測試情境中有用的實作。 它對於單一伺服器在生產環境中也很有用,因為記憶體消耗不是問題。 實作分散式記憶體快取能將快取資料儲存進行抽象化。 如果需要多個節點或容錯,它可在未來實作真正的分散式快取解決方案。

範例應用程式在Development檔案環境中執行時,會利用分散式記憶體快取。

builder.Services.AddDistributedMemoryCache();

分散式 SQL Server 快取

分散式SQL Server快取實作(AddDistributedSqlServerCache)允許分散式快取使用SQL Server資料庫作為其後備儲存。 若要在 SQL Server 執行個體中建立 SQL Server 快取項目資料表,您可以使用 sql-cache 工具。 此工具會使用您指定的名稱和架構來建立資料表。

執行 sql-cache create 命令以在 SQL Server 中建立資料表。 提供 SQL Server 執行個體 (Data Source)、資料庫 (Initial Catalog)、架構 (例如,dbo) 以及資料表名稱 (例如,TestCache):

dotnet sql-cache create "Data Source=(localdb)/MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

當工具成功時,會記錄一則訊息:

Table and index were created successfully.

sql-cache 工具所建立的資料表具有下列架構:

截圖,顯示用「sql-cache create」指令建立的SQL Server快取表結構。

Note

應用程式應該使用IDistributedCache實例來操作快取值,而不是使用SqlServerCache實例。

範例應用程式在非開發SqlServerCache()環境中實作該Development類別,並以 Program.cs 檔案呈現:

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Note

ConnectionString(以及可選的 SchemaNameTableName)這樣的屬性通常會儲存在版本控制之外。 例如, 秘密管理器appsettings.json或 appsettings。{Environment}.json 檔案可能會儲存屬性。 連接字串可能包含應避免進入原始碼控制系統的憑證。

更多資訊請參閱Azure<>上的SQL資料庫。

分散式 Postgres 快取

適用於PostgreSQL的 Azure 資料庫 可透過介面作為分散式快取備份存放區 IDistributedCache 。 適用於 PostgreSQL 的 Azure 資料庫 是一個完全託管、具備 AI 支援的資料庫即服務(DBaaS)產品,建立在開源的 PostgreSQL 引擎之上。 此設計以可預測的效能、強大的安全性、高可用性及無縫擴展性,支援關鍵任務工作負載。

安裝 Microsoft.Extensions.Caching.Postgres NuGet 套件之後,請設定分散式快取,如下所示:

  1. 註冊服務。

    using Microsoft.Extensions.DependencyInjection;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Register the Postgres distributed cache.
    builder.Services.AddDistributedPostgresCache(options => {
       options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
       options.SchemaName = builder.Configuration.GetValue<string>("PostgresCache:SchemaName", "public");
       options.TableName = builder.Configuration.GetValue<string>("PostgresCache:TableName", "cache");
       options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists", true);
       options.UseWAL = builder.Configuration.GetValue<bool>("PostgresCache:UseWAL", false);
    
       // Optional: Configure expiration settings.
    
       var expirationInterval = builder.Configuration.GetValue<string>("PostgresCache:ExpiredItemsDeletionInterval");
       if (!string.IsNullOrEmpty(expirationInterval) && TimeSpan.TryParse(expirationInterval, out var interval)) {
           options.ExpiredItemsDeletionInterval = interval;
       }
    
       var slidingExpiration = builder.Configuration.GetValue<string>("PostgresCache:DefaultSlidingExpiration");
       if (!string.IsNullOrEmpty(slidingExpiration) && TimeSpan.TryParse(slidingExpiration, out var sliding)) {
           options.DefaultSlidingExpiration = sliding;
       }
    });
    
    var app = builder.Build();
    
  2. 使用快取。

    public class MyService {
        private readonly IDistributedCache _cache; 
    
        public MyService(IDistributedCache cache) {
            _cache = cache;
        }
    
        public async Task<string> GetDataAsync(string key) {
            var cachedData = await _cache.GetStringAsync(key);
    
            if (cachedData == null) {
    
                // Fetch the data from source.
                var data = await FetchDataFromSource();
    
                // Cache the data with options.
                var options = new DistributedCacheEntryOptions {
                   AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
                   SlidingExpiration = TimeSpan.FromMinutes(5)
                };
    
                await _cache.SetStringAsync(key, data, options);
                return data;
            }
    
            return cachedData;
        }
    }
    

分散式 NCache 快取

NCache 是 .NET 原生開發的開放原始碼記憶體內部分散式快取。 NCache 可在本機運作,對於在 Azure 或其他裝載平臺上執行的 ASP.NET Core 應用程式,則設定為分散式快取叢集。

要在本地機器安裝和設定 NCache,請參閱 入門指南

若要設定 NCache:

  1. 安裝 NCache SDK NuGet 套件,該套件支援 NCache 開源版 .NET Framework 及 .NET Core 應用程式。

  2. 用戶端設定 中設定快取叢集( client.ncconf 檔案)。

  3. 將以下程式碼加入 Program.cs 檔案:

builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

分佈式 Azure Cosmos DB 快取

Azure Cosmos DB 可透過 IDistributedCache 介面在 ASP.NET Core 中設定為會話狀態提供者。 Azure Cosmos DB 是完全受控 NoSQL 和關聯式資料庫,適用於新式應用程式開發,可為任務關鍵性應用程式提供資料的高可用性、可擴縮性和低延遲存取。

安裝 Microsoft.Extensions.Caching.Cosmos NuGet 套件之後,配置一個 Azure Cosmos DB 分散式快取。 你可以使用現有的 Azure Cosmos DB 用戶端,或如以下章節所述建立新客戶端。

欲了解更多資訊,請參閱 使用 Azure Cosmos DB 的 Microsoft 快取擴充功能,以及 NuGet 套件的 GitHub 倉庫中的 README 檔案。

重複使用現有的用戶端

配置分散式快取最簡單的方法是重用現有的 Azure Cosmos DB 用戶端。 在這種情況下,當服務提供者被處置時,CosmosClient 實例並不會被處置。

services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
    cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
    cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
    cacheOptions.CosmosClient = existingCosmosClient;
    cacheOptions.CreateIfNotExists = true;
});

建立新用戶端

或者,具現化新的用戶端。 此情況下,當提供者被處置時,CosmosClient 實例也會被處置。

services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
    cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
    cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
    cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
    cacheOptions.CreateIfNotExists = true;
});

使用分散式快取

若要使用 IDistributedCache 介面,請在應用程式中要求 IDistributedCache 的執行個體。 執行個體是由依賴注入 (DI) 提供。

當範例應用程式啟動時,實 IDistributedCache 例會注入到 Program.cs 檔案中。 目前時間會透過 IHostApplicationLifetime 介面快取。 (更多資訊請參見 .NET 通用主機:IHostApplicationLifetime

app.Lifetime.ApplicationStarted.Register(() =>
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = System.Text.Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(20));
    app.Services.GetService<IDistributedCache>()
                              .Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

範例應用程式會將 IDistributedCache 實例注入 IndexModel 物件中,供索引頁面使用。

每次 Index 頁面載入時,會透過該 OnGetAsync 方法檢查快取時間。 如果快取時間沒有過期,時間就會顯示出來。 如果自上次存取快取時間(該頁面最後載入時)過了 20 秒,頁面會顯示「 快取時間已過期」的訊息。

立即選擇「 重設快取時間 」選項,將快取時間更新為目前時間。 此動作觸發 OnPostResetCachedTime 處理程序。

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string? CachedTimeUTC { get; set; }
    public string? ASP_Environment { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }

        ASP_Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (String.IsNullOrEmpty(ASP_Environment))
        {
            ASP_Environment = "Null, so Production";
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

Note

內建實作的IDistributedCache實例不需要使用單例或作用域生命周期。

你也可以在需要的地方建立 IDistributedCache 實例,而不是使用 DI。 然而,在程式碼中建立實例可能會讓你的程式碼更難測試,且違反 了明確相依原則

檢視快取建議

在決定哪種介面實作 IDistributedCache 最適合你的應用程式時,請考慮以下幾點:

  • 現有的基礎結構
  • 效能需求
  • Cost
  • 小組體驗

快取解決方案通常依賴記憶體內部儲存體來快速擷取快取的資料,但記憶體是有限的資源,而且成本高昂。 僅將常用的資料儲存在快取中。

對於大部分的應用程式,Redis 快取相比 SQL Server 快取,輸送量更高且延遲更低。 不過,還是建議使用效能評定來判斷快取策略的效能特性。

若 SQL Server 是分散式快取後備儲存,且快取與應用程式資料儲存/檢索共用同一資料庫,效能可能會下降。 建議做法是使用專用的 SQL Server 實例作為分散式快取後備儲存。

分散式快取是由多個應用程式伺服器共用的緩存,通常作為一個外部服務來維護,與存取它的應用程式伺服器分開。 分散式快取可以改善 ASP.NET Core 應用程式的效能和可擴縮性,尤其是當應用程式是由雲端服務或伺服器陣列裝載時。

分散式快取相比其他快取案例有幾項優點:快取資料會儲存在個別的應用程式伺服器上。

當快取資料散發時,資料具備如下特徵:

  • 在多個伺服器之間的請求中保持一致性
  • 在伺服器重新啟動和應用程式部署後仍然存續。
  • 不使用本機記憶體。

分散式快取配置依據實作而定。 本文說明如何設定 SQL Server、Redis 和 Postgres 分散式快取。 協力廠商實作也可供使用,例如:NCache (NCache on GitHub)。 無論選取哪一個實作,應用程式都會使用 IDistributedCache 介面與快取互動。

檢視或下載範例程式碼 \(英文\) (如何下載)

Prerequisites

為使用的分散式快取提供者新增套件引用:

IDistributedCache 介面

IDistributedCache 介面提供下列方法來操作分散式快取實作中的項目:

  • GetGetAsync:接受字串鍵值,如果在快取中找到,則擷取快取資料以byte[]陣列的形式。
  • SetSetAsync:使用字串索引鍵將項目 (作為 byte[] 陣列) 新增至快取。
  • RefreshRefreshAsync:根據快取索引鍵重新整理快取中的項目,重設滑動期限 (若有) 的逾時設定。
  • RemoveRemoveAsync:移除快取項目(根據其字串鍵)。

建立分散式快取系統

Program.cs 中註冊IDistributedCache實作。 本主題中所述了下列由架構提供的實作:

分散式 Redis 快取

我們建議生產應用程式使用分散式 Redis 快取,因為它是效能最高的應用程式。 如需詳細資訊,請參閱建議

Redis 是一種開放原始碼的記憶體內部資料存放區,通常用來作為分散式快取。 您可以為 Azure 裝載的 ASP.NET Core 應用程式設定 Azure Redis Cache,並可使用 Azure Redis Cache 進行本機開發。

應用程式會使用 RedisCache 執行個體 (AddStackExchangeRedisCache) 來設定快取實作。

  1. 建立 Azure Cache for Redis。
  2. 將主要連接字串 (StackExchange.Redis) 複製到 Configuration

下列程式碼會啟用 Azure Cache for Redis:

builder.Services.AddStackExchangeRedisCache(options =>
 {
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
     options.InstanceName = "SampleInstance";
 });

上述程式碼假設主要連接字串 (StackExchange.Redis) 已儲存在組態中,且金鑰名稱為 MyRedisConStr

如需詳細資訊,請參閱 Azure Cache for Redis

如需瞭解有關本機 Redis 快取替代方法的討論,請參閱此 GitHub 問題

分散式記憶體快取

分散式記憶體快取 (AddDistributedMemoryCache) 是架構所提供的 IDistributedCache 的實作,將項目儲存在記憶體中。 分散式記憶體快取並非實際的分散式快取。 快取項目由應用程式實例儲存於該應用程式運行的伺服器上。

分散式記憶體快取是實用的實作:

  • 在開發和測試情境中。
  • 當單一伺服器用於生產環境而且記憶體耗用量不是問題時。 實現分散式記憶體快取可抽象化快取資料的儲存方式。 如果需要多個節點或容錯,它可在未來實作真正的分散式快取解決方案。

範例應用程式在Development環境下執行時,在Program.cs 中會利用分散式記憶體快取:

builder.Services.AddDistributedMemoryCache();

分散式 SQL Server 快取

分散式 SQL Server 快取實作 (AddDistributedSqlServerCache) 可讓分散式快取使用 SQL Server 資料庫作為其備份存放區。 若要在 SQL Server 執行個體中建立 SQL Server 快取項目資料表,您可以使用 sql-cache 工具。 此工具會使用您指定的名稱和架構來建立資料表。

執行 sql-cache create 命令以在 SQL Server 中建立資料表。 提供 SQL Server 執行個體 (Data Source)、資料庫 (Initial Catalog)、架構 (例如,dbo) 以及資料表名稱 (例如,TestCache):

dotnet sql-cache create "Data Source=(localdb)/MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

系統將記錄訊息,指出工具成功:

Table and index were created successfully.

sql-cache 工具所建立的資料表具有下列架構:

SqlServer 快取資料表

Note

應用程式應該使用 IDistributedCache 的執行個體來操作快取值,而不是 SqlServerCache

範例應用程式在SqlServerCache非環境Development中實作:Program.cs

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Note

ConnectionString(選擇性地,SchemaNameTableName) 通常儲存在原始檔控制之外 (例如,由秘密管理員儲存,或儲存在 appsettings.json/appsettings.{Environment}.json 檔案中)。 連接字串可能包含應不保留在原始檔控制系統中的認證。

分散式 Postgres 快取

適用於PostgreSQL的 Azure 資料庫 可透過介面作為分散式快取備份存放區 IDistributedCache 。 適用於 PostgreSQL 的 Azure 資料庫是完全受控、AI 就緒的資料庫即服務 (DBaaS) 供應專案,建置在開放原始碼 PostgreSQL 引擎上,其設計目的是支援具有可預測效能、強固安全性、高可用性和無縫延展性的任務關鍵性工作負載。

安裝 Microsoft.Extensions.Caching.Postgres NuGet 套件之後,請設定分散式快取,如下所示:

  1. 註冊服務
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register Postgres distributed cache
builder.Services.AddDistributedPostgresCache(options => {
    options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
    options.SchemaName = builder.Configuration.GetValue<string>("PostgresCache:SchemaName", "public");
    options.TableName = builder.Configuration.GetValue<string>("PostgresCache:TableName", "cache");
    options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists", true);
    options.UseWAL = builder.Configuration.GetValue<bool>("PostgresCache:UseWAL", false);

    // Optional: Configure expiration settings

    var expirationInterval = builder.Configuration.GetValue<string>("PostgresCache:ExpiredItemsDeletionInterval");
    if (!string.IsNullOrEmpty(expirationInterval) && TimeSpan.TryParse(expirationInterval, out var interval)) {
        options.ExpiredItemsDeletionInterval = interval;
    }

    var slidingExpiration = builder.Configuration.GetValue<string>("PostgresCache:DefaultSlidingExpiration");
    if (!string.IsNullOrEmpty(slidingExpiration) && TimeSpan.TryParse(slidingExpiration, out var sliding)) {
        options.DefaultSlidingExpiration = sliding;
    }
});

var app = builder.Build();
  1. 使用快取
public class MyService {
    private readonly IDistributedCache _cache; 

    public MyService(IDistributedCache cache) {
        _cache = cache;
    }

    public async Task<string> GetDataAsync(string key) {
        var cachedData = await _cache.GetStringAsync(key);

        if (cachedData == null) {
            // Fetch data from source
            var data = await FetchDataFromSource();

            // Cache the data with options
            var options = new DistributedCacheEntryOptions {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
                SlidingExpiration = TimeSpan.FromMinutes(5)
            };

            await _cache.SetStringAsync(key, data, options);
            return data;
        }

        return cachedData;
    }
}

分散式 NCache 快取

NCache 是在 .NET 和 .NET Core 中原生開發的開放原始碼記憶體中分散式快取。 NCache 可在本機運作,對於在 Azure 或其他裝載平臺上執行的 ASP.NET Core 應用程式,則設定為分散式快取叢集。

若要在本機電腦上安裝和設定 NCache,請參閱 Windows 使用者入門指南 (.NET 和 .NET Core)

若要設定 NCache:

  1. 安裝 NCache 開放原始碼 NuGet
  2. client.ncconf 中設定快取叢集。
  3. 將下列程式碼新增至 Program.cs
builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

使用分散式快取

若要使用 IDistributedCache 介面,請在應用程式中要求 IDistributedCache 的執行個體。 執行個體是由依賴注入 (DI) 提供。

當範例應用程式啟動時,IDistributedCache 會插入至 Program.cs。 目前的時間會使用 IHostApplicationLifetime 快取 (如需詳細資訊,請參閱通用主機:IHostApplicationLifetime):

app.Lifetime.ApplicationStarted.Register(() =>
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = System.Text.Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(20));
    app.Services.GetService<IDistributedCache>()
                              .Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

範例應用程式會將 IDistributedCache 插入 IndexModel,以供 [索引] 頁面使用。

每次載入索引頁面時,都會檢查快取是否存在 OnGetAsync 中的快取時間。 如果快取的時間尚未過期,則會顯示時間。 如果自上次存取快取時間之後已經過 20 秒 (上次載入此頁面的時間),頁面會顯示 [快取時間過期]

選取 [重設快取時間] 按鈕可立即將快取的時間更新為目前時間。 此按鈕會觸發 OnPostResetCachedTime 事件處理方法。

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string? CachedTimeUTC { get; set; }
    public string? ASP_Environment { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }

        ASP_Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (String.IsNullOrEmpty(ASP_Environment))
        {
            ASP_Environment = "Null, so Production";
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

需要針對具有內建實作的 IDistributedCache 執行個體使用 Singleton 或 Scoped 存留期。

如果您可能需要 IDistributedCache 執行個體而不想使用 DI,那麼也可以建立一個,但透過程式碼建立執行個體可能會讓您的程式碼更加難以測試,而且這種做法也違反了明確相依性原則

Recommendations

決定哪一個 IDistributedCache 實作最適合您的應用程式時,請考慮下列事項:

  • 現有的基礎結構
  • 效能需求
  • Cost
  • 小組體驗

快取解決方案通常依賴記憶體內部儲存體來快速擷取快取的資料,但記憶體是有限的資源,而且成本高昂。 請只將常用的資料儲存在快取中。

對於大部分的應用程式,Redis 快取相比 SQL Server 快取,輸送量更高且延遲更低。 不過,還是建議使用效能評定來判斷快取策略的效能特性。

當 SQL Server 被用作分散式快取的備用存放區時,若快取與應用程式的普通資料存取共用相同的資料庫,可能會對兩者的效能造成負面影響。 我們建議針對分散式快取備份存放區使用專用 SQL Server 執行個體。

其他資源

分散式快取是由多個應用程式伺服器共用的快取,通常作為外部服務 (相對於對快取進行存取的應用程式伺服器) 進行維護。 分散式快取可以改善 ASP.NET Core 應用程式的效能和可擴縮性,尤其是當應用程式是由雲端服務或伺服器陣列裝載時。

分散式快取相比其他快取案例有幾項優點:快取資料會儲存在個別的應用程式伺服器上。

當快取資料被分發時,資料會如下:

  • 在多個伺服器的請求之間保持一致性
  • 即使在伺服器重新啟動和應用程式部署後,依然能夠保持存續。
  • 不使用本機記憶體。

依實作而定的分散式快取組態。 本文說明如何設定 SQL Server、Redis 和 Postgres 分散式快取。 協力廠商實作也可供使用,例如:NCache (NCache on GitHub)。 無論選取哪一個實作,應用程式都會使用 IDistributedCache 介面與快取互動。

檢視或下載範例程式碼 \(英文\) (如何下載)

Prerequisites

若要使用 SQL Server 分散式快取,請將套件參考新增至 Microsoft.Extensions.Caching.SqlServer 套件。

若要使用 Redis 分散式快取,請將套件參考新增至 Microsoft.Extensions.Caching.StackExchangeRedis 套件。

若要使用 Postgres 分散式快取,請將套件參考新增至 Microsoft.Extensions.Caching.Postgres 套件。

若要使用 NCache 分散式快取,請將套件參考新增至 NCache.Microsoft.Extensions.Caching.OpenSource 套件。

IDistributedCache 介面

IDistributedCache 介面提供下列方法來操作分散式快取實作中的項目:

  • GetGetAsync:接受字串鍵值,如果在快取中找到,則擷取快取資料以byte[]陣列的形式。
  • SetSetAsync:使用字串索引鍵將項目 (作為 byte[] 陣列) 新增至快取。
  • RefreshRefreshAsync:根據快取索引鍵重新整理快取中的項目,重設滑動期限 (若有) 的逾時設定。
  • RemoveRemoveAsync:移除快取項目(根據其字串鍵)。

建立分散式快取系統

Startup.ConfigureServices 中註冊IDistributedCache實作。 本主題中所述了下列由架構提供的實作:

分散式記憶體快取

分散式記憶體快取 (AddDistributedMemoryCache) 是架構所提供的 IDistributedCache 的實作,將項目儲存在記憶體中。 分散式記憶體快取並非實際的分散式快取。 快取項目由應用程式實例儲存於該應用程式運行的伺服器上。

分散式記憶體快取是實用的實作:

  • 在開發和測試情境中。
  • 當單一伺服器用於生產環境而且記憶體耗用量不是問題時。 實現分散式記憶體快取可抽象化快取資料的儲存方式。 如果需要多個節點或容錯,它可在未來實作真正的分散式快取解決方案。

範例應用程式在Development環境下執行時,在Startup.ConfigureServices 中會利用分散式記憶體快取:

services.AddDistributedMemoryCache();

分散式 SQL Server 快取

分散式 SQL Server 快取實作 (AddDistributedSqlServerCache) 可讓分散式快取使用 SQL Server 資料庫作為其備份存放區。 若要在 SQL Server 執行個體中建立 SQL Server 快取項目資料表,您可以使用 sql-cache 工具。 此工具會使用您指定的名稱和架構來建立資料表。

執行 sql-cache create 命令以在 SQL Server 中建立資料表。 提供 SQL Server 執行個體 (Data Source)、資料庫 (Initial Catalog)、架構 (例如,dbo) 以及資料表名稱 (例如,TestCache):

dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

系統將記錄訊息,指出工具成功:

Table and index were created successfully.

sql-cache 工具所建立的資料表具有下列架構:

SqlServer 快取資料表

Note

應用程式應該使用 IDistributedCache 的執行個體來操作快取值,而不是 SqlServerCache

範例應用程式在SqlServerCache非環境Development中實作:Startup.ConfigureServices

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Note

ConnectionString(選擇性地,SchemaNameTableName) 通常儲存在原始檔控制之外 (例如,由秘密管理員儲存,或儲存在 appsettings.json/appsettings.{Environment}.json 檔案中)。 連接字串可能包含應不保留在原始檔控制系統中的認證。

分散式 Redis 快取

Redis 是一種開放原始碼的記憶體內部資料存放區,通常用來作為分散式快取。 您可以為 Azure 裝載的 ASP.NET Core 應用程式設定 Azure Redis Cache,並可使用 Azure Redis Cache 進行本機開發。

應用程式會使用 RedisCache 執行個體 (AddStackExchangeRedisCache) 來設定快取實作。

  1. 建立 Azure Cache for Redis。
  2. 將主要連接字串 (StackExchange.Redis) 複製到 Configuration

下列程式碼會啟用 Azure Cache for Redis:

public void ConfigureServices(IServiceCollection services)
{
    if (_hostContext.IsDevelopment())
    {
        services.AddDistributedMemoryCache();
    }
    else
    {
        services.AddStackExchangeRedisCache(options =>
        {
            options.Configuration = _config["MyRedisConStr"];
            options.InstanceName = "SampleInstance";
        });
    }

    services.AddRazorPages();
}

上述程式碼假設主要連接字串 (StackExchange.Redis) 已儲存在組態中,且金鑰名稱為 MyRedisConStr

如需詳細資訊,請參閱 Azure Cache for Redis

如需瞭解有關本機 Redis 快取替代方法的討論,請參閱此 GitHub 問題

分散式 Postgres 快取

適用於PostgreSQL的 Azure 資料庫 可透過介面作為分散式快取備份存放區 IDistributedCache 。 適用於 PostgreSQL 的 Azure 資料庫是完全受控、AI 就緒的資料庫即服務 (DBaaS) 供應專案,建置在開放原始碼 PostgreSQL 引擎上,其設計目的是支援具有可預測效能、強固安全性、高可用性和無縫延展性的任務關鍵性工作負載。

安裝 Microsoft.Extensions.Caching.Postgres NuGet 套件之後,請設定分散式快取,如下所示:

  1. 註冊服務
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register Postgres distributed cache
builder.Services.AddDistributedPostgresCache(options => {
    options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
    options.SchemaName = builder.Configuration.GetValue<string>("PostgresCache:SchemaName", "public");
    options.TableName = builder.Configuration.GetValue<string>("PostgresCache:TableName", "cache");
    options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists", true);
    options.UseWAL = builder.Configuration.GetValue<bool>("PostgresCache:UseWAL", false);

    // Optional: Configure expiration settings

    var expirationInterval = builder.Configuration.GetValue<string>("PostgresCache:ExpiredItemsDeletionInterval");
    if (!string.IsNullOrEmpty(expirationInterval) && TimeSpan.TryParse(expirationInterval, out var interval)) {
        options.ExpiredItemsDeletionInterval = interval;
    }

    var slidingExpiration = builder.Configuration.GetValue<string>("PostgresCache:DefaultSlidingExpiration");
    if (!string.IsNullOrEmpty(slidingExpiration) && TimeSpan.TryParse(slidingExpiration, out var sliding)) {
        options.DefaultSlidingExpiration = sliding;
    }
});

var app = builder.Build();
  1. 使用快取
public class MyService {
    private readonly IDistributedCache _cache; 

    public MyService(IDistributedCache cache) {
        _cache = cache;
    }

    public async Task<string> GetDataAsync(string key) {
        var cachedData = await _cache.GetStringAsync(key);

        if (cachedData == null) {
            // Fetch data from source
            var data = await FetchDataFromSource();

            // Cache the data with options
            var options = new DistributedCacheEntryOptions {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
                SlidingExpiration = TimeSpan.FromMinutes(5)
            };

            await _cache.SetStringAsync(key, data, options);
            return data;
        }

        return cachedData;
    }
}

分散式 NCache 快取

NCache 是在 .NET 和 .NET Core 中原生開發的開放原始碼記憶體中分散式快取。 NCache 可在本機運作,對於在 Azure 或其他裝載平臺上執行的 ASP.NET Core 應用程式,則設定為分散式快取叢集。

若要在本機電腦上安裝和設定 NCache,請參閱 Windows 使用者入門指南 (.NET 和 .NET Core)

若要設定 NCache:

  1. 安裝 NCache 開放原始碼 NuGet

  2. client.ncconf 中設定快取叢集。

  3. 將下列程式碼新增至 Startup.ConfigureServices

    services.AddNCacheDistributedCache(configuration =>    
    {        
        configuration.CacheName = "demoClusteredCache";
        configuration.EnableLogs = true;
        configuration.ExceptionsEnabled = true;
    });
    

使用分散式快取

若要使用 IDistributedCache 介面,請從應用程式中的任何建構函式請求 IDistributedCache 執行個體。 執行個體是由依賴注入 (DI) 提供。

當範例應用程式啟動時,IDistributedCache 會插入至 Startup.Configure。 目前的時間會使用 IHostApplicationLifetime 快取 (如需詳細資訊,請參閱通用主機:IHostApplicationLifetime):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
    IHostApplicationLifetime lifetime, IDistributedCache cache)
{
    lifetime.ApplicationStarted.Register(() =>
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

範例應用程式會將 IDistributedCache 插入 IndexModel,以供 [索引] 頁面使用。

每次載入索引頁面時,都會檢查快取是否存在 OnGetAsync 中的快取時間。 如果快取的時間尚未過期,則會顯示時間。 如果自上次存取快取時間之後已經過 20 秒 (上次載入此頁面的時間),頁面會顯示 [快取時間過期]

選取 [重設快取時間] 按鈕可立即將快取的時間更新為目前時間。 此按鈕會觸發 OnPostResetCachedTime 事件處理方法。

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string CachedTimeUTC { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

Note

不需要針對IDistributedCache實例使用 Singleton 或 Scoped 的生命周期 (至少針對內建實作)。

如果您可能需要 IDistributedCache 執行個體而不想使用 DI,那麼也可以建立一個,但透過程式碼建立執行個體可能會讓您的程式碼更加難以測試,而且這種做法也違反了明確相依性原則

Recommendations

決定哪一個 IDistributedCache 實作最適合您的應用程式時,請考慮下列事項:

  • 現有的基礎結構
  • 效能需求
  • Cost
  • 小組體驗

快取解決方案通常依賴記憶體內部儲存體來快速擷取快取的資料,但記憶體是有限的資源,而且成本高昂。 請只將常用的資料儲存在快取中。

一般而言,Redis 快取相比 SQL Server 快取,輸送量更高且延遲更低。 不過,通常還是需要使用效能評定來判斷快取策略的效能特性。

當 SQL Server 被用作分散式快取的備用存放區時,若快取與應用程式的普通資料存取共用相同的資料庫,可能會對兩者的效能造成負面影響。 我們建議針對分散式快取備份存放區使用專用 SQL Server 執行個體。

其他資源