ABP - 緩存模塊(1)

来源:https://www.cnblogs.com/wewant/archive/2023/06/02/17132954.html
-Advertisement-
Play Games

# 1. 與 .NET Core 緩存的關係和差異 ABP 框架中的緩存系統核心包是 [Volo.Abp.Caching](https://www.nuget.org/packages/Volo.Abp.Caching) ,而對於分散式緩存的支持,abp 官方提供了基於 Redis 的方案,需要安裝 ...


1. 與 .NET Core 緩存的關係和差異

ABP 框架中的緩存系統核心包是 Volo.Abp.Caching ,而對於分散式緩存的支持,abp 官方提供了基於 Redis 的方案,需要安裝 Volo.Abp.Caching.StackExchangeRedis 集成包。預設的情況下,在我們使用 ABP CLI 創建 ABP 框架模板項目的時候已經集成了這個包,我們不需要手動進行安裝。

ABP 框架中的緩存系統在 ASP.NET Core的分散式緩存系統上進行擴展,從而為了使分散式緩存使用起來更加方便。理所當然的,ABP 框架的緩存系統相容 ASP.NET Core 原生的分散式緩存使用方式,關於 ASP.NET Core 分散式緩存的使用,可以參考我之前的文章 ASP.NET Core - 緩存之分散式緩存,也可以看下官方文檔 ASP.NET Core 中的分散式緩存

那麼 ABP 框架中的緩存系統擴展了什麼呢?ASP.NET Core 的分散式緩存又有哪些不便之處呢 ?ASP.NET Core 通過 IDistributedCache 抽象了分散式緩存的存取操作,但是有這個兩個問題:

  • 它對緩存值的存取是基於 byte 數組的,而不是對象。

    這使得我們在使用分散式緩存的時候需要先將實例對象進行序列化/反序列化,之後再轉碼為 byte 數組。如果每個使用分散式緩存的地方都這麼做將會有很多的重覆冗餘的代碼,這是需要抽取封裝的。

  • 為了能實現多個應用公用緩存,達成分散式緩存的左右,它將所有的緩存項存放在同一個 Key 池之中。

    這就可能會有問題,我們要特別註意緩存鍵的設置,如果開發人員不註意,很可能將一些緩存相互覆蓋了,特別是共用緩存的應用比較多,或者多租戶的情況下,這造成的問題可能很嚴重,而且很難排查。

ABP 框架的緩存系統擴展了通用的泛型介面 IDistributedCache<TCacheItem> ,泛型類型就是緩存值的類型,用於解決上面提到的問題:

  • 該介面內部實現了對緩存對象 序列化/反序列化 的邏輯。 預設使用 JSON 序列化, 我們如果有需要可以替換 依賴註入 系統中 IDistributedCacheSerializer 服務的實現來覆蓋預設的方式。

  • 該介面會根據緩存中對象類型自動向緩存key添加 緩存名稱 首碼。 預設緩存名是緩存對象的全類名(如果類名以CacheItem 結尾, 那麼CacheItem 會被忽略,不應用到緩存名稱上),開發人員也可以在緩存類上使用 CacheName 特性 設置緩存的名稱.

  • 如果是多租戶應用的話,它會自動將當前的租戶id添加到緩存鍵中, 以區分不同租戶的緩存項 。 如果租戶之間需要共用緩存對象, 我們可以在緩存類打上 IgnoreMultiTenancy 特性,聲明當前緩存不區分組合。

  • 允許為每個應用程式定義 全局緩存鍵首碼 , 不同的應用程式可以在共用的分散式緩存中擁有自己的隔離池.

  • 它提供了 錯誤容忍 機制,對分散式緩存存取過程中的異常進行了處理,例如分散式緩存服務連接失敗等,避免因緩存問題導致應用出錯。
    因為緩存本身就只是一種用於提升應用性能的策略,如果因為分散式緩存的問題導致應用出錯,業務邏輯無法繼續執行,那就得不償失了。緩存系統應該是,就算將其從應用中摘除,也不影響應用業務邏輯正常執行的東西,只是性能可能差了點。

  • 它額外提供了 GetManyAsync 和 SetManyAsync 等方法, 支持對緩存的批量操作,可以顯著提高批處理的性能。

2. Abp 緩存的使用

這裡還是以控制台應用作為演示,Web 應用中使用比控制台更簡單,因為 Web 應用中已經集成了緩存模塊,不需要再自行引入。首先,通過以下命令生成一個 ABP 的控制台啟動模板:

abp new AbpCacheSample -t console

之後,如果是使用基於記憶體的分散式緩存的話,只需要安裝 Volo.Abp.Caching 包即可。

Install-package  Volo.Abp.Caching

之後,在項目的模塊文件中添加模塊依賴:

image

這裡不需要再進行依賴關係的配置,因為在 AbpCacheModule模塊的初始化之中已經配置好了相關的內容,通過源碼可以看到:

image

當然,你也可以更高效地在項目所在的文件夾使用以下命令,省掉對模塊依賴的配置。

abp add-package  Volo.Abp.Caching

如果是使用 Redis 分散式緩存的話,需要安裝 Volo.Abp.Caching.StackExchangeRedis 集成包,這個包對 Microsoft.Extensions.Caching.StackExchangeRedis 進行了擴展,它簡化了 Redis 緩存的配置,也是前面提到的 GetManyAsync 和SetManyAsync 等更加性能的方法的實現所在。

install-package Volo.Abp.Caching.StackExchangeRedis

同時,模塊依賴改成以下這樣:

image

同樣的,模塊初始化的時候也已經配置好 Redis 緩存使用的內容:

image

通過源碼我們也可以看到,Redis 相應的配置是從配置文件中的 "Redis" 節點讀取的,我們可以在appsettings.json 中添加以下內容啟用 Redis 緩存:

{
  "Redis": {
	"IsEnabed": true, // 控制 Redis 分散式緩存是否啟用
	"Configuration": "xxx.xxx.xxx.xxx:6379,password=123456" // Redis 連接字元串
  }
}

這裡其實就是通過配置信息中的 "Redis:Configuration" 節點配置 RedisCacheOpions 選項,這個選項是微軟標準的 Redi s緩存支持包中的,如果有需要,我們可以在代碼中通過以下方式對該選項進行配置:

[DependsOn(
	typeof(AbpAutofacModule),
	typeof(AbpCachingStackExchangeRedisModule)
)]
public class AbpCacheSampleModule : AbpModule
{
	public override void ConfigureServices(ServiceConfigurationContext context)
	{
		Configure<RedisCacheOptions>(option =>
		{
			// ...
		});
	}
}

2.1 常規使用

首先我們定義緩存類:

/// <summary>
/// 緩存中,會以緩存類的全類名作為建,如果類名以 CacheItem 結尾,CacheItem 會被忽略
/// </summary>
// [CacheName("DateTime")] 也可以通過 CacheName 特性設置緩存鍵
public class DateTimeCacheItem
{
	public DateTime Now { get; set; }

	public string Name { get; set; }
}

之後,只需要在要用到的服務中註入 IDistributedCache 泛型介面即可:

public class HelloWorldService : ITransientDependency
{
	public const string CacheKey = nameof(HelloWorldService);
	private readonly IDistributedCache<DateTimeCacheItem> _distributedCache;

	public HelloWorldService(IDistributedCache<DateTimeCacheItem> distributedCache)
	{
		_distributedCache = distributedCache;
	}

	public async Task SayHelloAsync()
	{
		// 常規的 IDisctributedCache 的同名方法,不過 ABP 框架中進行了擴展
		// 使其支持泛型,可以直接存取對象
		//var cacheValue = await _distributedCache.GetAsync(CacheKey);
		//if (cacheValue == null)
		//{
		//    cacheValue = new DateTimeCacheItem
		//    {
		//        Name = CacheKey,
		//        Now = DateTime.Now,
		//    };
		//    await _distributedCache.SetAsync(
		//        CacheKey, // 緩存鍵,最終的鍵會是 緩存名稱(緩存類全類名去除CacheItem,或CacheName特性設置的名稱) + 這裡的鍵
		//        cacheValue, // 直接存取對象,而不用自己序列化/反序列化 以及轉碼 
		//        new DistributedCacheEntryOptions // 一樣可以通過選項設置緩存策略
		//    {
		//        SlidingExpiration = TimeSpan.FromMinutes(1)
		//    });
		//}

		// ABP 框架新增的方法
		var cacheValue = await _distributedCache.GetOrAddAsync(
			CacheKey,
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem()
				{
					Name = CacheKey,
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions
			{
				SlidingExpiration= TimeSpan.FromMinutes(1)
			});

		Console.WriteLine(JsonSerializer.Serialize(cacheValue));
		Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
	}
}

image

2.2 非字元串類型的 Key

除了可以使用 string 類型作為緩存鍵之外,我們還可以通過 IDistributedCache<CacheKey, CacheItem> 介面使用其他類型,甚至複雜類型作為緩存鍵,使用複雜類型作為緩存鍵的時候需要重寫 ToString() 方法。

public class MyCacheKey
	{
		public int Id { get; set; }

		public string Name { get; set; }

		public override string ToString()
		{
			return Id + Name;
		}
	}

public class HelloWorldService : ITransientDependency
{
	private readonly IDistributedCache<DateTimeCacheItem, int> _distributedCacheKeyInt;
	private readonly IDistributedCache<DateTimeCacheItem, MyCacheKey> _distiributedCacheKey;

	public HelloWorldService(
		IDistributedCache<DateTimeCacheItem, int> distributedCacheKeyInt,
		IDistributedCache<DateTimeCacheItem, MyCacheKey> distiributedCacheKey)
	{
		_distributedCache = distributedCache;
	}

	public async Task SayHelloAsync()
	{
		_ = await _distributedCacheKeyInt.GetOrAddAsync(
			1,
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem
				{
					Name = "1",
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) }
			);

		_ = await _distiributedCacheKey.GetOrAddAsync(
			new MyCacheKey { Id = 1, Name = "MyKey" },
			async () =>
			{
				return await Task.FromResult(new DateTimeCacheItem
				{
					Name = "1",
					Now = DateTime.Now
				});
			},
			() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) }
			);
	}
}

2.3 批量操作

批量進行緩存存取操作的方式如下:

_ = await _distributedCache.GetOrAddManyAsync(new List<string> { "Key1", "Key2" }, async keys =>
{
	return await Task.FromResult(
		keys.Select(k => new KeyValuePair<string, DateTimeCacheItem>(k, new DateTimeCacheItem { Name = k, Now = DateTime.Now }))
		.ToList()
		);
},
() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) });

ABP的分散式緩存介面定義了以下批量操作方法,當你需要在一個方法中調用多次緩存操作時,這些方法可以提高性能

  • SetManyAsync 和 SetMany 方法可以用來向緩存中設置多個值.
  • GetManyAsync 和 GetMany 方法可以用來從緩存中獲取多個值.
  • GetOrAddManyAsync 和 GetOrAddMany 方法可以用來從緩存中獲取並添加缺少的值.
  • RefreshManyAsync 和 RefreshMany 方法可以來用重置多個值的滾動過期時間.
  • RemoveManyAsync 和 RemoveMany 方法可以用來從緩存中刪除多個值.

這些不是標準的ASP.NET Core緩存方法, 所以其他的分散式緩存方案可能不支持。而 ABP 通過 Volo.Abp.Caching.StackExchangeRedis 實現了 Redis 分散式緩存下的批量操作。如果採用了其他方案實現分散式緩存,而提供程式不支持的情況下,會回退到迴圈調用 SetAsync 和 GetAsync 方法。

以上就是 ABP 框架中緩存系統的基本使用。除此之外,ABP 框架提供了 AbpDistributedCacheOptions 選項用於配置一些緩存策略,可用屬性:

  • HideErrors (bool, 預設: true): 啟用/禁用隱藏從緩存伺服器寫入/讀取值時的錯誤.
  • KeyPrefix (string, 預設: null): 如果你的緩存伺服器由多個應用程式共同使用, 則可以為應用程式的緩存鍵設置一個首碼. 在這種情況下, 不同的應用程式不能覆蓋彼此的緩存內容.
  • GlobalCacheEntryOptions (DistributedCacheEntryOptions): 用於設置保存緩內容卻沒有指定選項時, 預設的分散式緩存選項 (例如 AbsoluteExpiration 和 SlidingExpiration). SlidingExpiration的預設值設置為20分鐘.

3. 額外功能

另外,還有ABP 框架下的緩存系統還有以下一些功能:

  • 錯誤處理

    當為你的對象設計緩存時, 通常會首先嘗試從緩存中獲取值. 如果在緩存中找不到該值, 則從來源查詢對象. 它可能在資料庫中, 或者可能需要通過HTTP調用遠程伺服器.

    在大多數情況下, 你希望容忍緩存錯誤; 如果緩存伺服器出現錯誤, 也不希望取消該操作. 相反, 你可以默默地隱藏(並記錄)錯誤並從來源查詢. 這就是ABP框架預設的功能.

    ABP的分散式緩存異常處理, 預設記錄並隱藏錯誤,有一個全局修改該功能的選項.所有的IDistributedCache (和 IDistributedCache<TCacheItem, TCacheKey>)方法都有一個可選的參數hideErrors, 預設值為null. 如果此參數設置為null, 則全局生效, 否則你可以選擇單個方法調用時隱藏或者拋出異常.

  • 工作單元級別的緩存

    分散式緩存服務提供了一個有趣的功能. 假設你已經更新了資料庫中某本書的價格, 然後將新價格設置到緩存中, 以便以後使用緩存的值. 如果設置緩存後出現異常, 並且更新圖書價格的事務被回滾了, 該怎麼辦?在這種情況下, 緩存值是錯誤的.

    IDistributedCache<..>方法提供一個可選參數, considerUow, 預設為false. 如果將其設置為true, 則你對緩存所做的更改不會應用於真正的緩存存儲, 而是與當前的工作單元關聯. 你將獲得在同一工作單元中設置的緩存值, 但僅當前工作單元成功時更改才會生效.

  • 可替換的鍵、值處理方式

    • IDistributedCacheSerializer

      IDistributedCacheSerializer 服務用於序列化和反序列化緩存內容. 預設實現是 Utf8JsonDistributedCacheSerializer 類, 它使用 IJsonSerializer 服務將對象轉換為 JSON, 反之亦然. 然後, 它使用 UTC8 編碼將 JSON 字元串轉換為分散式緩存接受的位元組數組.

      如果你想實現自己的序列化邏輯, 可以自己實現並替換此服務.

    • IDistributedCacheKeyNormalizer

      預設情況下, IDistributedCacheKeyNormalizer是由DistributedCacheKeyNormalizer類實現的. 它將緩存名稱、應用程式緩存首碼和當前租戶id添加到緩存鍵中. 如果需要更高級的鍵規範化, 可以自己實現並替換此服務.



參考文章:
ABP 官方文檔 - 緩存



ABP 系列總結:
目錄:ABP 系列總結
上一篇:ABP - 依賴註入(2)


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

-Advertisement-
Play Games
更多相關文章
  • 基於java的線上商城設計與實現,線上購物平臺,校園購物商城,商品銷售平臺,基於Java的電商平臺;電商平臺,買家和賣家可以在此平臺上進行銷售和交易,節約了大量的線下時間成本,購物車的功能,校園交易平臺等等; ...
  • 昨天遇到參數key大小寫不一致導致校驗簽名失敗的問題,查了很長時間才找到原因。看了一下FastJson源碼,發現JSON.toObject中轉換成對象的時候會忽略大小寫。 所以,當使用了JSON.toObject將json轉成Java對象後,再用JSON.toObject轉成json,key值就變了 ...
  • # 前言 在上一篇文章中,給大家講解了泛型的概念、作用、使用場景,以及泛型集合、泛型介面和泛型類的用法,但受限於篇幅,並沒有把泛型的內容講解完畢。所以今天我們會繼續學習泛型方法、泛型擦除,以及通配符等的內容,希望大家繼續做好學習的準備哦。 *** 全文大約【**4600】** 字,不說廢話,只講可以 ...
  • [toc] 這篇文章是我之前總結的一篇文章,因為整理博客的原因,原有博客已經註銷,但這篇文章對一些讀者很有用,所以現在新瓶裝舊酒重新整理回來分享給大家。 最近一段時間生產環境頻繁出問題,每次都會生成一個hs_err_pid*.log文件,因為工作內容的原因,在此之前並沒有瞭解過相關內容,趁此機會學習 ...
  • 隨著技術的發展,ASP.NET Core MVC也推出了好長時間,經過不斷的版本更新迭代,已經越來越完善,本系列文章主要講解ASP.NET Core MVC開發B/S系統過程中所涉及到的相關內容,適用於初學者,在校畢業生,或其他想從事ASP.NET Core MVC 系統開發的人員。 經過前幾篇文章... ...
  • 以前在隨筆《Winform開發框架之客戶關係管理系統(CRM)的開發總結系列1-界面功能展示 》的幾篇隨筆中介紹過基於WInform開發框架開發的CRM系統,系統的功能主要也是圍繞著客戶相關信息來進行管理的。本篇隨筆介紹在最新的《SqlSugar開發框架》中整合CRM系統模塊的功能。 ...
  • 1. 擴展方法 擴展方法使你能夠向現有類型“添加”方法,而無需創建新的派生類型、重新編譯或以其他方式修改原始類型。 擴展方法是一種靜態方法,但可以像擴展類型上的實例方法一樣進行調用。 對於用 C#、F# 和 Visual Basic 編寫的客戶端代碼,調用擴展方法與調用在類型中定義的方法沒有明顯區別 ...
  • 最近ET做熱更重載dll的時候,返回登陸會重新檢測新的dll,首次登錄之前已經Assembly.Load()過一次dll,第二次返回登陸再次load dll到記憶體中,Invoke執行方法的時候,異常了,有些方法執行了,有些未執行,於是查資料,看到些老資料說Assembly.Load重覆載入同名dll ...
一周排行
    -Advertisement-
    Play Games
  • 最近做項目過程中,使用到了海康相機,官方只提供了C/C++的SDK,沒有搜尋到一個合適的封裝了的C#庫,故自己動手,簡單的封裝了一下,方便大家也方便自己使用和二次開發 ...
  • 前言 MediatR 是 .NET 下的一個實現消息傳遞的庫,輕量級、簡潔高效,用於實現進程內的消息傳遞機制。它基於中介者設計模式,支持請求/響應、命令、查詢、通知和事件等多種消息傳遞模式。通過泛型支持,MediatR 可以智能地調度不同類型的消息,非常適合用於領域事件處理。 在本文中,將通過一個簡 ...
  • 前言 今天給大家推薦一個超實用的開源項目《.NET 7 + Vue 許可權管理系統 小白快速上手》,DncZeus的願景就是做一個.NET 領域小白也能上手的簡易、通用的後臺許可權管理模板系統基礎框架。 不管你是技術小白還是技術大佬或者是不懂前端Vue 的新手,這個項目可以快速上手讓我們從0到1,搭建自 ...
  • 第1章:WPF概述 本章目標 瞭解Windows圖形演化 瞭解WPF高級API 瞭解解析度無關性概念 瞭解WPF體繫結構 瞭解WPF 4.5 WPF概述 ​ 歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢 ...
  • 在日常開發中,並不是所有的功能都是用戶可見的,還在一些背後默默支持的程式,這些程式通常以服務的形式出現,統稱為輔助角色服務。今天以一個簡單的小例子,簡述基於.NET開發輔助角色服務的相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 第3章:佈局 本章目標 理解佈局的原則 理解佈局的過程 理解佈局的容器 掌握各類佈局容器的運用 理解 WPF 中的佈局 WPF 佈局原則 ​ WPF 視窗只能包含單個元素。為在WPF 視窗中放置多個元素並創建更貼近實用的用戶男面,需要在視窗上放置一個容器,然後在這個容器中添加其他元素。造成這一限制的 ...
  • 前言 在平時項目開發中,定時任務調度是一項重要的功能,廣泛應用於後臺作業、計劃任務和自動化腳本等模塊。 FreeScheduler 是一款輕量級且功能強大的定時任務調度庫,它支持臨時的延時任務和重覆迴圈任務(可持久化),能夠按秒、每天/每周/每月固定時間或自定義間隔執行(CRON 表達式)。 此外 ...
  • 目錄Blazor 組件基礎路由導航參數組件參數路由參數生命周期事件狀態更改組件事件 Blazor 組件 基礎 新建一個項目命名為 MyComponents ,項目模板的交互類型選 Auto ,其它保持預設選項: 客戶端組件 (Auto/WebAssembly): 最終解決方案裡面會有兩個項目:伺服器 ...
  • 先看一下效果吧: isChecked = false 的時候的效果 isChecked = true 的時候的效果 然後我們來實現一下這個效果吧 第一步:創建一個空的wpf項目; 第二步:在項目裡面添加一個checkbox <Grid> <CheckBox HorizontalAlignment=" ...
  • 在編寫上位機軟體時,需要經常處理命令拼接與其他設備進行通信,通常對不同的命令封裝成不同的方法,擴展稍許麻煩。 本次擬以特性方式實現,以兼顧維護性與擴展性。 思想: 一種命令對應一個類,其類中的各個屬性對應各個命令段,通過特性的方式,實現其在這包數據命令中的位置、大端或小端及其轉換為對應的目標類型; ...