請注意,這種特定的機制被稱為該高速緩存旁路模式,它通常用于讀重應用程序。

在這篇文章中,我們將詳細討論內存緩存

什么是ASP.NET Core中的內存緩存?

在ASP.NET Core中,內存緩存允許您將數據存儲在應用程序的內存中。這意味著數據被緩存在服務器的實例上,這通過減少重復獲取相同數據的需求來顯著提高應用程序的性能。內存緩存是在應用程序中實現緩存的最簡單、最有效的方法之一。

內存緩存的優點和缺點

 優點

  1. 更快:它避免了網絡通信,使其比其他形式的分布式緩存更快。
  2. 高度可靠:數據直接存儲在服務器內存中,確保快速訪問。
  3. 最適合中小型應用程序:它為較小的應用程序提供了有效的性能改進。

 缺點

  1. 資源消耗:如果配置不正確,它可能會消耗大量的服務器資源。
  2. 可伸縮性問題:隨著應用程序的擴展和緩存時間的延長,維護服務器的成本可能會變得很高。
  3. 云部署挑戰:在云部署中維護一致的緩存可能很困難。
  4. 不適用于具有多個副本的微服務:內存緩存可能不適用于運行多個副本的微服務架構,因為每個副本都有自己的緩存,可能導致緩存數據的不一致。

入門

在ASP.NETCore中設置緩存非常簡單。只需幾行代碼,就可以顯著提高應用程序的響應能力,通常提高50-75%或更多。

讓我們創建一個新的ASP.NET Core 8 Web API。我將使用Visual Studio 2022社區版進行此演示

要啟用內存中緩存,首先需要在應用程序的服務容器中注冊緩存服務。然后我們將看到如何使用依賴注入。

導航到Program.cs文件,并添加以下內容。

builder.Services.AddMemoryCache();

這將向應用程序的IServiceCollection添加一個非分布式的內存中實現。

就是這樣。您的應用程序現在支持內存緩存托管。現在,為了更好地理解緩存是如何工作的,我們將創建幾個CRUD端點。

我將使用EF核心代碼優先方法,并與本地計算機上托管的PostgreSQL數據庫進行交互。

讓我們先安裝所需的EF Core包。

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Npgsql.EntityFrameworkCore.PostgreSQL

如前所述,我們將構建一個ASP.NET Core 8 Web API,它具有端點,

  1. 獲取所有產品:在這里,產品列表將被緩存。
  2. 獲取單個產品:這也將被緩存。
  3. 添加產品:在此操作后,所有產品的緩存列表將被刪除/無效。

建立產品模型

像往常一樣,我們將有一個相當簡單的產品模型。

public class Product
{
public Guid Id { get; set; }
public string Name { get; set; } = default!;
public string Description { get; set; } = default!;
public decimal Price { get; set; }
// Parameterless constructor for EF Core
private Product() { }
public Product(string name, string description, decimal price)
{
Id = Guid.NewGuid();
Name = name;
Description = description;
Price = price;
}
}

我還添加了一個ProductCreationDTO,我們將在ProductService類中使用它。

public record ProductCreationDto(string Name, string Description, decimal Price);

我不打算一步一步地介紹如何設置DB Context類以及如何生成和應用遷移。您可以在本文末尾找到完整的源代碼。

但是,下面是我用來連接到本地PGSQL實例的連接字符串。

"ConnectionStrings": {"dotnetSeries": "Host=localhost;Database=dotnetSeries;Username=postgres;Password=admin;Include Error Detail=true"}

我還使用MockarooWeb應用程序為產品生成了1000條假記錄。我將此數據生成為SQL插入腳本,并在遷移的數據庫中運行它。因此,我們的“產品”表中有1,000條樣本記錄可用。SQL腳本包含在解決方案的SQL文件夾中,以防您也需要它。

產品服務-使用IMemoryCache

這是產品服務類,它為我們做了所有繁重的工作。讓我們一個方法一個方法地檢查代碼。

請注意,我已經將AppDbContextIMemoryCacheILogger<ProductService>實例注入到產品服務類的主構造函數中,以便我們可以在整個類中訪問它們。

public class ProductService(AppDbContext context, IMemoryCache cache, ILogger<ProductService> logger) : IProductService
{
public async Task Add(ProductCreationDto request)
{
var product = new Product(request.Name, request.Description, request.Price);
await context.Products.AddAsync(product);
await context.SaveChangesAsync();
// invalidate cache for products, as new product is added
var cacheKey = "products";
logger.LogInformation("invalidating cache for key: {CacheKey} from cache.", cacheKey);
cache.Remove(cacheKey);
}

public async Task<Product> Get(Guid id)
{
var cacheKey = $"product:{id}";
logger.LogInformation("fetching data for key: {CacheKey} from cache.", cacheKey);
if (!cache.TryGetValue(cacheKey, out Product? product))
{
logger.LogInformation("cache miss. fetching data for key: {CacheKey} from database.", cacheKey);
product = await context.Products.FindAsync(id);
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30))
.SetAbsoluteExpiration(TimeSpan.FromSeconds(300))
.SetPriority(CacheItemPriority.Normal);
logger.LogInformation("setting data for key: {CacheKey} to cache.", cacheKey);
cache.Set(cacheKey, product, cacheOptions);
}
else
{
logger.LogInformation("cache hit for key: {CacheKey}.", cacheKey);
}
return product;
}

public async Task<List<Product>> GetAll()
{
var cacheKey = "products";
logger.LogInformation("fetching data for key: {CacheKey} from cache.", cacheKey);
if (!cache.TryGetValue(cacheKey, out List<Product>? products))
{
logger.LogInformation("cache miss. fetching data for key: {CacheKey} from database.", cacheKey);
products = await context.Products.ToListAsync();
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(30))
.SetAbsoluteExpiration(TimeSpan.FromSeconds(300))
.SetPriority(CacheItemPriority.NeverRemove)
.SetSize(2048);
logger.LogInformation("setting data for key: {CacheKey} to cache.", cacheKey);
cache.Set(cacheKey, products, cacheOptions);
}
else
{
logger.LogInformation("cache hit for key: {CacheKey}.", cacheKey);
}
return products;
}
}

獲取所有產品

從第36行到第57行,我們有一個從數據庫/緩存返回所有產品列表的方法。

我們首先設置一個緩存鍵,在當前情況下是products。此鍵將用作標識符,用于將數據存儲在該高速緩存中。正如你所知道的,緩存在技術上是存儲在數據結構中的鍵值對,就像字典一樣。

接下來,在第40行中,我們首先嘗試檢查該高速緩存中是否有針對關鍵產品的可用數據。如果存在某些數據,則將其提取到List<Product>中。否則,控件進入if語句,我們將從數據庫中獲取數據。請注意,只有當緩存未命中時才會出現這種情況,或者換句話說,只有當數據不存在于我們的緩存存儲中時才會出現這種情況。

一旦我們從數據庫獲得響應,我們就根據該高速緩存鍵將該數據設置到我們的緩存存儲中,并將其返回給客戶機。

Get(Guid id)方法的實現也幾乎類似,我們在其中傳遞Product ID。然而,主要區別是,這一次,我們有一個更動態的緩存,即product:{productId},基于請求的Product ID。

ASP.NET Core中的緩存條目選項

配置該高速緩存選項允許您根據需要自定義緩存行為。

var cacheOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(20)).SetSlidingExpiration(TimeSpan.FromMinutes(2)).SetPriority(CacheItemPriority.NeverRemove);

MemoryCacheEntryOptions-這個類用于定義相關緩存技術的關鍵屬性。我們將創建這個類的一個實例,并將其傳遞給IMemoryCache實例。但在此之前,讓我們了解MemoryCacheEntryOptions的屬性。

在ASP.NET Core中使用緩存時,可以配置幾個設置來優化性能和資源管理。以下是一些關鍵設置及其用途:

優先級(SetPriority()

Size(SetSize()

SlidingList(SetSlidingList()

絕對值(SetAbsoluteArray()

關于ASP.NET Core中的內存緩存,在構建應用程序時,必須仔細考慮Priority、Size、Sliding Size和Absolute Size屬性。

添加產品-緩存失效

緩存并不像聽起來那么簡單。雖然它可以通過減少重復獲取數據的需要來顯著提高應用程序性能,但有效管理該高速緩存涉及確保緩存的數據保持準確和最新。這就是緩存失效發揮作用的地方。

緩存失效是至關重要的,因為它可以確保陳舊或過期的數據不會在該高速緩存中持久存在,從而導致應用程序中的不一致和潛在的錯誤行為。

這里有一個場景。如果用戶創建了一個新產品,然后試圖獲取所有產品的列表,該怎么辦?我們的應用程序很可能會提供產品創建之前的陳舊數據。當基礎數據發生更改時,必須更新該高速緩存以反映這些更改。

緩存失效策略

 基于時間的失效

基于依賴性的失效

 手動失效

對于我們的演示,我們將使用手動緩存無效。每當添加新產品時,我們調用cache.Remove(cacheKey);確保產品列表從該高速緩存中刪除。

您可以通過更新該高速緩存來進一步增強此機制,而不是完全清除它。例如,當創建新產品時,您可以從該高速緩存中獲取產品列表,將新創建的產品追加到列表中,并再次將更新后的列表重新添加到該高速緩存內存中。這可能會增加往返于該高速緩存內存的次數,但根據您的系統設計,這也可能是高效的。

您還可以將整個緩存失效過程作為后臺任務運行,這樣應用程序就不必等待該高速緩存調用完成才發送響應

最小API端點

現在我們已經實現了所需的服務方法,讓我們將產品服務連接到.NET 8 Web API中的實際API端點。打開Program.cs,并添加以下最小API端點。

app.MapGet("/products", async (IProductService service) =>
{
var products = await service.GetAll();
return Results.Ok(products);
});
app.MapGet("/products/{id:guid}", async (Guid id, IProductService service) =>
{
var product = await service.Get(id);
return Results.Ok(product);
});
app.MapPost("/products", async (ProductCreationDto product, IProductService service) =>
{
await service.Add(product);
return Results.Created();
});


您可能還必須將IProductService服務注冊到具有Transient作用域的依賴注入(DI)容器中。

builder.Services.AddTransient<IProductService, ProductService>();

在ASP.NET Core中測試緩存

讓我們構建并運行應用程序。我將使用Postman來測試我們的實現。

首先,讓我們訪問GetAll端點。由于該高速緩存中沒有數據,因此我們希望從數據庫中獲取數據。


如您所見,返回1000個產品的列表的響應時間接近800 ms。理想情況下,產品列表應該被緩存,如果我們再次訪問端點,我們希望從該高速緩存中獲取它。

我們再來一次。

現在,響應時間大大降低,降至40毫秒以下!

日志和我們預期的一樣。

要記住的要點

下面是實現緩存時需要考慮的一些要點。

  1. 您的應用程序永遠不應該依賴于緩存數據,因為它很可能在任何給定時間都不可用。傳統上,它應該取決于您的實際數據源。緩存只是一種增強功能,只有在可用/有效時才能使用。
  2. 嘗試限制內存中該高速緩存的增長。這是至關重要的,因為如果配置不正確,緩存可能會占用您的服務器資源。可以使用Size屬性限制用于條目的該高速緩存。
  3. 使用AbsoluteReader/SlidingReader使您的應用程序更快、更智能。它還有助于限制緩存內存的使用。

后臺緩存更新

此外,作為一項改進,您可以使用后臺作業定期更新該高速緩存。如果“絕對緩存”設置為5分鐘,則可以每6分鐘運行一次重復作業,以將該高速緩存條目更新為其最新版本。您可以使用Hangfire在ASP.NET核心應用程序中實現相同的功能。

讓我們在這里結束這篇文章。

在下一篇文章中,我們將討論更高級的緩存概念,如分布式緩存,設置Redis,Redis緩存,PostBack調用等等。

總結

在這篇詳細的文章中,我們探討了緩存的各個方面,包括基礎知識、內存中緩存以及如何在ASP.NET Core中實現內存中緩存。

文章轉自微信公眾號@科控物聯

上一篇:

使用Azure AI服務:使用C#和Python進行REST API調用

下一篇:

手把手教你Vue+ECharts+高德地圖API實現天氣預報數據可視化
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費