天天看點

C# .NET 8 — 建立具有分布式緩存的緩存服務

C# .NET 8 — 建立具有分布式緩存的緩存服務

介紹

加速應用程式的一種常見方法是引入緩存。通常,首先想到的選項是使用 MemoryCache (RAM) 來儲存一些資料,以加快檢索速度。

此方法适用于單體式應用程式。但是,在微服務解決方案中,每個服務都可以獨立擴充,使用本地緩存可能會破壞微服務架構模式的無狀态規則。

是以,更好的解決方案是使用分布式緩存。在 .NET 中,與分布式緩存的常見互動是通過稱為 的接口。

分布式緩存

在 .NET 應用程式中工作時,可以選擇要使用的實作。

.NET 8 中的可用實作包括:

  • Memory Cache
  • Redis
  • SQL Server
  • NCache
  • Azure CosmosDB

最常用的實作之一是 Redis。在本文的後續部分中,我将使用 Redis 進行實作。

有關詳細資訊,請參閱官方文檔:

ASP.NET Core 中的分布式緩存

了解如何使用 ASP.NET Core 分布式緩存來提高應用性能和可擴充性,尤其是在雲或...

learn.microsoft.com

重要參數

使用分布式緩存時,需要了解兩個重要參數:

  • 滑動到期: 在從緩存中逐出緩存條目之前必須通路緩存條目的時間跨度。
  • 絕對到期時間: 逐出緩存條目的時間點。預設情況下,緩存中保留的條目不會過期。

有關詳細資訊,請參閱官方文檔:

CacheItemPolicy.AbsoluteExpiration 屬性 (System.Runtime.Caching)

擷取或設定一個值,該值訓示是否應在指定時間點逐出緩存項。

learn.microsoft.com

CacheItemPolicy.SlidingExpiration 屬性 (System.Runtime.Caching)

擷取或設定一個值,該值訓示如果在給定範圍内未通路過緩存條目,是否應将其逐出...

learn.microsoft.com

緩存服務

我們可以在應用程式中使用的一種方法是在需要時直接與分布式緩存進行互動。

例如,如果服務需要緩存,我們将請求 的執行個體。這種方法可行,但可能不是最佳方法,因為它可能導緻重複操作。

更可取的方法是為緩存互動建立專用服務,并在整個應用程式中使用它。這樣可以集中管理緩存,并有助于避免備援并提高可維護性。

示例項目

這裡有一個示例項目,我準備了一個緩存服務的樣本:文末

項目結構

該項目具有以下結構:

  • docker:包含一個 Docker Compose 檔案,其中包含已配置的 Redis 容器。
  • src:包含項目源代碼。
  • test:包含項目測試。

Docker Compose

以下是您将在 GitHub 上找到的 docker-compose 檔案的内容:

version: '3'

services:

 redis-monitoring:
 image: redislabs/redisinsight:latest
 pull_policy: always
 ports:
 - '8001:8001'
 restart: unless-stopped
 networks:
 - default

 redis: 
 image: redis:latest
 pull_policy: always
 ports:
 - "6379:6379"
 restart: unless-stopped
 networks:
 - default

networks:
 default:
 driver: bridge
           

如您所見,compose 檔案配置了兩個服務:

  • redis: Redis 的執行個體。
  • RedisInsight: 一個幫助你與 Redis 互動的容器。此容器可用于調試目的。

此 Docker Compose 設定僅用于開發或測試目的。請注意,Redis 已更改其許可政策。

您可以在這篇前篇文章中找到更多資訊:

API項目概覽

API 項目公開了一些與 互動的方法。

以下是啟動應用程式時将顯示的内容的示例:

C# .NET 8 — 建立具有分布式緩存的緩存服務

以下是 API 背後的代碼:

app.MapGet("/GetOrCreateAsync/{key}", async (string key,ICacheService cache) => 
{ 
return await cache.GetOrCreateAsync(key, () => Task.FromResult($"{nameof(cache.GetOrCreateAsync)} - Hello World")); 
}) 
.WithName("GetOrCreateAsync") 
.WithOpenApi(); 


app.MapGet("/GetOrDefault/{key}", async (string key, ICacheService cache) => 
{ 
return await cache.GetOrDefaultAsync(key, $"{nameof(cache.GetOrDefault)} - Hello World"); 
}) 
.WithName("GetOrDefault") 
.WithOpenApi(); 


app.MapGet("/CreateAndSet/{key}", async (string key, ICacheService cache) => 
{ 
await cache.CreateAndSet(key, $"{nameof(cache.CreateAndSet)} - Hello World"); 
}) 
.WithName("CreateAndSet") 
.WithOpenApi(); 


app.MapDelete("/RemoveAsync", (string key, ICacheService cache) => 
{ 
 cache.RemoveAsync(key); 
}) 
.WithName("RemoveAsync") 
.WithOpenApi();
           

服務注冊

如果未提供 Redis 配置,則該服務配置為使用記憶體中實作。

在API項目中,隻需要使用擴充即可。

builder.Services.AddServiceCache(builder.Configuration);
           

擴充的詳細資訊如下:

public static IServiceCollection AddServiceCache(
this IServiceCollection services,
IConfiguration configuration
 )
 {
 services
 .AddOptions<CacheOptions>()
 .Bind(configuration.GetSection("Cache"))
 .ValidateDataAnnotations();

if (!string.IsOrEmpty(configuration.GetSection("RedisCache:Configuration").Value))
 {
 services.AddStackExchangeRedisCache(options =>
 {
 configuration.Bind("RedisCache", options);
 });
 }
else
 {
 services.AddDistributedMemoryCache();
 }

 services.AddTransient<ICacheService, CacheService>();
return services;
 }
           

如您所見,它在應用程式設定中搜尋名為“Cache”的鍵,以嘗試初始化對象。此選項包含全局 Sliding Expiration 值的值。

ServiceCache 服務緩存

這是接口:CacheService

public interface ICacheService
{
Task CreateAndSet<T>(string key, T thing, int expirationMinutes = 0)
where T : class;
Task<T> CreateAndSetAsync<T>(string key, Func<Task<T>> createAsync, int expirationMinutes = 0);
Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> create, int expirationMinutes = 0);
Task<T> GetOrDefault<T>(string key);
Task<T> GetOrDefaultAsync<T>(string key, T defaultVal);
Task RemoveAsync(string key);
}
           

它提供了一些實用方法來與 進行互動。

特别是,它提供了不同的方法來檢索資料并設定自動預設值或建立方法。

讓我們看一下示例:

public async Task<T> GetOrCreateAsync<T>(
string key,
Func<Task<T>> create,
int expirationMinutes = 0
 )
 {
var bytesResult = await _cache.GetAsync(key);

if (bytesResult?.Length > 0)
 {
using StreamReader reader = new(new MemoryStream(bytesResult));
using JsonTextReader jsonReader = new(reader);
JsonSerializer ser = new();
 ser.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
 ser.TypeNameHandling = TypeNameHandling.All;
 ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

var result = ser.Deserialize<T>(jsonReader);
if (result != )
 {
return result;
 }
 }

return await this.CreateAndSetAsync<T>(key, create, expirationMinutes);
 }
           

GetOrDefault另一方面,不會嘗試初始化分布式緩存。如果找不到鍵,它隻會傳回預設值。

public async Task<T> GetOrDefault<T>(string key)
 {
var bytesResult = await _cache.GetAsync(key);

if (bytesResult?.Length > 0)
 {
using StreamReader reader = new(new MemoryStream(bytesResult));
using JsonTextReader jsonReader = new(reader);
JsonSerializer ser = new();
 ser.TypeNameHandling = TypeNameHandling.All;
 ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

var result = ser.Deserialize<T>(jsonReader);
if (result != )
 {
return result;
 }
 }

return default;
 }
           

FusionCache

建立服務以管理分布式緩存的方法對于避免代碼重複和具有抽象層非常有用。這可能适用于簡單的方案,或者如果您希望完全控制代碼庫或限制外部影響。

對于更複雜的場景,您可以使用的一個很酷的庫是 FusionCache,它本質上是類固醇上的服務緩存,提供進階彈性功能和可選的分布式二級緩存。

源代碼擷取:公衆号回複消息【

code:72610

如果你喜歡我的文章,請給我一個贊!謝謝

繼續閱讀