.NET 9 的混合式快取 HybridCache

在現代 Web 應用程式開發中,快取是提升應用程式效能不可或缺的技術。在過去,我們會使用 IMemoryCache 做記憶體快取,或者使用 IDistributedCache 做分散式快取。

.NET 9 引入了全新的 HybridCache,它結合了記憶體快取(L1)和分散式快取(L2)的優勢,讓我們能夠在同一個 API 中享受兩層快取的效能提升,同時還提供了快取雪崩保護和標籤管理等進階功能。

開發環境

  • Windows 11 Pro
  • ASP.NET Core 9.0
  • Microsoft.Extensions.Caching.Hybrid
  • Microsoft.Extensions.Caching.StackExchangeRedis
  • Rider 2025.2

HybridCache 是什麼?

HybridCache 是 .NET 9 中新推出的快取程式庫,透過 Microsoft.Extensions.Caching.Hybrid 套件提供。它被稱為「混合快取」,因為能夠同時利用記憶體內快取(L1)和分散式快取(L2,如 Redis)來最佳化資料儲存和檢索效能。
 

  • 雙層快取架構(L1/L2)

    • L1 快取:運行在應用程式記憶體中,提供最快的存取速度
    • L2 快取:可以是 Redis、SQL Server 或任何其他分散式快取
    • 彈性配置:可以只使用 L1 快取,無需分散式快取
  • 避免快取雪崩(Stampede)
    • HybridCache 確保對於相同的鍵值,只有一個並發呼叫者會執行工廠方法,其他呼叫者等待該結果,防止快取雪崩問題。
  • 支援標籤式快取管理
  • 提供自訂序列化器
    • 預設使用 System.Text.Json 處理字串和 byte[]
    • 支援透過 WithSerializer 和 WithSerializerFactory 方法自訂序列化器
    • 可配置為使用 protobuf 或 XML 等其他序列化器

 

架構圖:

graph TB
    A[應用程式請求] --> B[HybridCache]
    B --> C[L1 記憶體快取]
    C --> D{命中?}
    D -->|是| E[直接回傳]
    D -->|否| F[L2 分散式快取]
    F --> G{命中?}
    G -->|是| H[回傳並存入L1]
    G -->|否| I[執行資料來源]
    I --> J[存入L1與L2]
    J --> K[回傳結果]

如何安裝?

dotnet add package Microsoft.Extensions.Caching.Hybrid

 

如果你要用 Redis 當 L2 快取:

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

 


基本設定

只使用 L1 快取:

builder.Services.AddHybridCache();

 

使用 Redis 作為 L2 快取:

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(1)
    };
});

 


快取資料的方式

在應用程式端,挖一個洞讓物件依賴 Microsoft.Extensions.Caching.Hybrid.HybridCache

var weather = await _cache.GetOrCreateAsync(
    key: $"weather-{city}",
    factory: async token =>
    {
        await Task.Delay(1000, token); // 模擬外部 API
        return new WeatherInfo(city, 28, "晴朗", DateTime.UtcNow);
    },
    options: new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    },
    tags: [$"weather-{city}"]);

這段程式碼會先查 L1,再查 L2,最後才執行 factory 方法。這樣可以避免重複查詢外部資源。

NOTE:L1 快取時間應比 L2 短

 

我的範例則是在 Minimal Api 方法注入 Microsoft.Extensions.Caching.Hybrid.HybridCache

        app.MapGet("/weatherforecast/hybrid-cache", 
                async (Microsoft.Extensions.Caching.Hybrid.HybridCache hybridCache) =>
            {
                // 使用 HybridCache 快取天氣預報資料
                var forecast = await hybridCache.GetOrCreateAsync(
                    key: "weather-forecast",
                    factory: async cancellationToken =>
                    {
                        // 模擬從資料來源獲取資料(這裡會有延遲以便觀察快取效果)
                        await Task.Delay(TimeSpan.FromMicroseconds(2), cancellationToken); // 模擬 2 秒的資料庫查詢

                        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 從資料來源生成天氣預報資料");

                        return Enumerable.Range(1, 5).Select(index =>
                                new WeatherForecast
                                {
                                    Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                                    TemperatureC = Random.Shared.Next(-20, 55),
                                    Summary = summaries[Random.Shared.Next(summaries.Length)]
                                })
                            .ToArray();
                    },
                    tags: ["weather-forecast"]
                );

                return Results.Ok(new
                {
                    Data = forecast,
                    CachedAt = DateTime.Now,
                    Message = "資料來自 HybridCache (L1: 記憶體 + L2: Redis)"
                });
            })
            .WithName("GetWeatherForecastWithHybridCache")
            .WithSummary("使用 HybridCache 的天氣預報")
            .WithDescription("展示 HybridCache L1(記憶體)+ L2(Redis)雙層快取功能");

 


標籤式快取管理

HybridCache 支援快取標籤,方便批次清除:

await _cache.RemoveByTagAsync("product-123");
await _cache.RemoveByTagAsync("category-electronics");

 

這在產品更新、類別變動時非常實用。


自訂序列化器

預設使用 System.Text.Json,但你可以改用 protobuf:

options.WithSerializerFactory(type =>
{
    if (type == typeof(Product))
        return new ProtobufSerializer<Product>();
    return null;
});

 


心得

HybridCache 是 .NET 9 中一個非常實用的快取工具,它讓我們不再需要在記憶體快取與分散式快取之間做選擇。透過簡單的 API、標籤管理、自訂序列化器等功能,讓快取管理變得更簡單、更彈性。

  • L1 快取時間應比 L2 短
  • 所有快取都加上標籤,方便日後清除

參考資料


範例位置

sample.dotblog/Cache/Lab.HybridCache at master · yaochangyu/sample.dotblog

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo