Util应用框架基础(七) - 缓存
本節(jié)介紹Util應(yīng)用框架如何操作緩存.
概述
緩存是提升性能的關(guān)鍵手段之一.
除了提升性能,緩存對(duì)系統(tǒng)健壯性和安全性也有影響.
不同類型的系統(tǒng)對(duì)緩存的依賴程度不同.
對(duì)于后臺(tái)管理系統(tǒng),由于是給管理人員使用的,用戶有限,而且操作基本都需要身份認(rèn)證和授權(quán),甚至可能部署在局域網(wǎng)內(nèi),一般僅對(duì)耗時(shí)操作使用緩存即可.
但是商城,門(mén)戶網(wǎng)站這類系統(tǒng), 它們部署在互聯(lián)網(wǎng)上,并且允許匿名用戶訪問(wèn),僅緩存耗時(shí)操作是不夠的.
除了訪問(wèn)量可能比較大,另外需要防范網(wǎng)絡(luò)流氓的惡意攻擊,他們會(huì)發(fā)送大量請(qǐng)求來(lái)試探你的系統(tǒng).
如果某個(gè)讀取操作直接到達(dá)數(shù)據(jù)庫(kù),哪怕僅執(zhí)行非常簡(jiǎn)單的SQL,由于請(qǐng)求非常密集,服務(wù)器的CPU將很快到達(dá)100%從而拒絕服務(wù).
對(duì)于這類系統(tǒng),需要對(duì)暴露到互聯(lián)網(wǎng)上的所有頁(yè)面和讀取數(shù)據(jù)的API進(jìn)行緩存.
當(dāng)然也可以使用更多的只讀數(shù)據(jù)庫(kù)和其它高性能數(shù)據(jù)庫(kù)分?jǐn)傋x取壓力,本文介紹基于內(nèi)存和Redis的緩存操作.
緩存框架
要緩存數(shù)據(jù),需要選擇一種緩存框架.
.Net 緩存
-
本地緩存 IMemoryCache
.Net 提供了 Microsoft.Extensions.Caching.Memory.IMemoryCache 進(jìn)行本地緩存操作.
IMemoryCache 可以將數(shù)據(jù)對(duì)象緩存到Web服務(wù)器進(jìn)程的內(nèi)存中.
本地緩存的主要優(yōu)勢(shì)是性能非常高,而且不需要序列化對(duì)象.
本地緩存的主要問(wèn)題是內(nèi)存容量受限和更新同步困難.
本地緩存可使用的內(nèi)存容量受Web服務(wù)器內(nèi)存的限制.
可以在單體項(xiàng)目中使用本地緩存.
如果單體項(xiàng)目?jī)H部署一個(gè)Web服務(wù)器實(shí)例,緩存只有一個(gè)副本,不存在更新同步的問(wèn)題.
但是如果將單體項(xiàng)目部署到Web集群,由于Web服務(wù)器實(shí)例不止一個(gè),每個(gè)Web服務(wù)器都會(huì)產(chǎn)生一個(gè)緩存副本.
想要同時(shí)更新多個(gè)Web服務(wù)器的本地緩存非常困難,這可能導(dǎo)致緩存的數(shù)據(jù)不一致.
可以使用負(fù)載均衡器的會(huì)話粘滯特性將用戶每次請(qǐng)求都定位到同一臺(tái)Web服務(wù)器,從而避免多次請(qǐng)求看到不一致的數(shù)據(jù).
微服務(wù)項(xiàng)目情況則更為復(fù)雜,由于包含多個(gè)Web Api項(xiàng)目,每個(gè)Web Api項(xiàng)目都會(huì)部署到一個(gè)或多個(gè)Web服務(wù)器.
不同 Web Api 項(xiàng)目可能需要使用相同的緩存數(shù)據(jù),無(wú)法使用負(fù)載均衡器的會(huì)話粘滯特性解決該問(wèn)題.
我們需要使用分布式緩存來(lái)解決內(nèi)存容量和更新同步的問(wèn)題.
-
分布式緩存 IDistributedCache
.Net 提供了 Microsoft.Extensions.Caching.Distributed.IDistributedCache 進(jìn)行分布式緩存操作.
IDistributedCache 可以將數(shù)據(jù)對(duì)象序列化后保存到 Redis 等緩存服務(wù)器中.
相比基于內(nèi)存的本地緩存, 分布式緩存的性能要低得多, 不僅要序列化對(duì)象,還需要跨進(jìn)程網(wǎng)絡(luò)調(diào)用.
但是由于不使用 Web 服務(wù)器的內(nèi)存,所以可以輕松的增加緩存容量.
把緩存抽出來(lái)放到專門(mén)的服務(wù)器后,多個(gè)Web Api項(xiàng)目就可以共享緩存.
由于緩存只有一份,也就不存在同步更新.
-
直接使用.Net 緩存的問(wèn)題
毫無(wú)疑問(wèn),你可以直接使用 IMemoryCache 和 IDistributedCache 接口進(jìn)行緩存操作.
但會(huì)面臨以下問(wèn)題:
-
Api生硬且不統(tǒng)一.
IDistributedCache 直接操作 byte[] ,如果不進(jìn)一步封裝很難使用.
你需要明確指定是本地緩存還是分布式緩存,無(wú)法使用統(tǒng)一的API,不能通過(guò)配置進(jìn)行切換.
-
需要自行處理緩存過(guò)期引起的性能問(wèn)題.
如果同一時(shí)間,緩存正好大面積過(guò)期,大量請(qǐng)求到達(dá)數(shù)據(jù)庫(kù),從而導(dǎo)致系統(tǒng)可能崩潰,這稱為緩存雪崩.
簡(jiǎn)單的處理辦法是給每個(gè)緩存項(xiàng)設(shè)置不同的緩存時(shí)間,如果統(tǒng)一配置緩存時(shí)間,則添加一個(gè)隨機(jī)間隔,讓緩存過(guò)期的時(shí)間錯(cuò)開(kāi)即可.
另一個(gè)棘手的問(wèn)題,如果很多請(qǐng)求并發(fā)訪問(wèn)某個(gè)熱點(diǎn)緩存項(xiàng),當(dāng)緩存過(guò)期,這些并發(fā)請(qǐng)求將到達(dá)數(shù)據(jù)庫(kù),這稱為緩存擊穿.
雖然只有一個(gè)緩存項(xiàng)過(guò)期,但還是會(huì)損害系統(tǒng)性能.
可以對(duì)并發(fā)請(qǐng)求加鎖,只允許第一個(gè)進(jìn)入的請(qǐng)求到達(dá)數(shù)據(jù)庫(kù)并更新緩存,后續(xù)請(qǐng)求將從更新的緩存讀取.
為進(jìn)一步提升性能,可以在緩存過(guò)期前的某個(gè)時(shí)間更新緩存,從而避免鎖定請(qǐng)求造成的等待.
-
缺失前綴移除等關(guān)鍵特性.
有些緩存項(xiàng)具有相關(guān)性,比如為當(dāng)前用戶設(shè)置權(quán)限,菜單,個(gè)人偏好等緩存項(xiàng),當(dāng)他退出登錄時(shí),需要清除跟他相關(guān)的所有緩存項(xiàng).
你可以一個(gè)個(gè)的移除,但相當(dāng)費(fèi)力.
可以為具有相關(guān)性的緩存項(xiàng)設(shè)置相同的緩存前綴,并通過(guò)緩存前綴找出所有相關(guān)緩存項(xiàng),從而一次性移除它們.
遺憾的是, .Net 緩存并不支持這些特性,需要自行實(shí)現(xiàn).
-
緩存框架 EasyCaching
EasyCaching 是一個(gè)專業(yè)而易用的緩存框架,提供統(tǒng)一的API接口,并解決了上述問(wèn)題.
EasyCaching 支持多種緩存提供程序,可以將緩存寫(xiě)入內(nèi)存,Redis,Memcached等.
EasyCaching 支持多種序列化方式,可在使用分布式緩存時(shí)指定.
EasyCaching 支持前綴移除,模式移除等高級(jí)用法.
除此之外,EasyCaching 還支持2級(jí)緩存.
2級(jí)緩存可以讓你的項(xiàng)目從本地緩存中獲取數(shù)據(jù),這樣可以獲得很高的讀取性能.
當(dāng)本地緩存過(guò)期,本地緩存會(huì)請(qǐng)求Redis分布式緩存,Redis緩存從數(shù)據(jù)庫(kù)讀取最新數(shù)據(jù),并更新本地緩存.
Redis還充當(dāng)事件總線的角色,每當(dāng)數(shù)據(jù)更新,通過(guò)Redis總線發(fā)布事件,同步更新所有本地緩存副本,解決了本地緩存更新困難的難題.
與 IMemoryCache 相比, EasyCaching 的本地緩存性能稍低,畢竟實(shí)現(xiàn)了更多功能.
Util應(yīng)用框架使用 EasyCaching 緩存框架,并進(jìn)行簡(jiǎn)單包裝.
Util 僅引入了 EasyCaching 的本地緩存和Redis緩存兩種提供程序, 以及 SystemTextJson 序列化方式.
如果需要使用其它提供程序和序列化方式,請(qǐng)自行引入相關(guān) Nuget 包.
基礎(chǔ)用法
配置緩存
配置本地緩存
-
引用Nuget包
Nuget包名: Util.Caching.EasyCaching
-
AddMemoryCache
使用 AddMemoryCache 擴(kuò)展方法啟用本地緩存.
-
默認(rèn)配置不帶參數(shù),設(shè)置以下默認(rèn)值:
-
MaxRdSecond 設(shè)置為 1200秒.
-
CacheNulls 設(shè)置為 true.
var builder = WebApplication.CreateBuilder( args ); builder.AsBuild().AddMemoryCache(); -
-
使用 IConfiguration 進(jìn)行配置.
可以使用 appsettings.json 文件進(jìn)行配置.
var builder = WebApplication.CreateBuilder( args ); builder.AsBuild().AddMemoryCache( builder.Configuration );默認(rèn)配置節(jié): EasyCaching:Memory
appsettings.json 配置文件示例.
{ "EasyCaching": { "Memory": { "MaxRdSecond": 1200, "CacheNulls": true } } } -
使用委托進(jìn)行配置.
var builder = WebApplication.CreateBuilder( args ); builder.AsBuild().AddMemoryCache( options => { options.MaxRdSecond = 1200; options.CacheNulls = true; } );
-
配置Redis緩存
-
引用Nuget包
Nuget包名: Util.Caching.EasyCaching
-
AddRedisCache
使用 AddRedisCache 擴(kuò)展方法啟用Redis緩存.
-
最簡(jiǎn)單的配置方法只需傳入Redis服務(wù)地址,并設(shè)置以下默認(rèn)值.
-
MaxRdSecond 設(shè)置為 1200秒.
-
CacheNulls 設(shè)置為 true.
-
AllowAdmin 設(shè)置為 true.
-
端口設(shè)置為 6379.
-
SerializerName 設(shè)置為 "SystemTextJson".
范例:
var builder = WebApplication.CreateBuilder( args ); builder.AsBuild().AddRedisCache( "127.0.0.1" );如果要修改端口為 6666,如下所示.
builder.AsBuild().AddRedisCache( "127.0.0.1",6666 );還可以統(tǒng)一設(shè)置緩存鍵前綴,下面的示例將緩存鍵前綴設(shè)置為 "test:".
builder.AsBuild().AddRedisCache( "127.0.0.1",6666,"test:" ); -
-
使用 IConfiguration 進(jìn)行配置.
可以使用 appsettings.json 文件進(jìn)行配置.
builder.AsBuild().AddRedisCache( builder.Configuration );默認(rèn)配置節(jié): EasyCaching:Redis
appsettings.json 配置文件示例.
{ "EasyCaching": { "Redis": { "MaxRdSecond": 1200, "CacheNulls": true, "DbConfig": { "AllowAdmin": true, "Endpoints": [ { "Host": "localhost", "Port": 6739 } ], "Database": 0 } } } } -
使用委托進(jìn)行配置.
builder.AsBuild().AddRedisCache( options => { options.MaxRdSecond = 1200; options.CacheNulls = true; options.DBConfig.AllowAdmin = true; options.DBConfig.KeyPrefix = "test:"; options.DBConfig.Endpoints.Add( new ServerEndPoint( "127.0.0.1", 6379 ) ); } );
-
配置二級(jí)緩存
-
引用Nuget包
Nuget包名: Util.Caching.EasyCaching
-
AddHybridCache
使用 AddHybridCache 擴(kuò)展方法啟用2級(jí)緩存.
-
最簡(jiǎn)單的配置方法不帶參數(shù),設(shè)置以下默認(rèn)值.
-
TopicName 設(shè)置為 EasyCachingHybridCache.
TopicName 是Redis總線發(fā)布事件的主題名稱.
啟用2級(jí)緩存之前,應(yīng)先配置本地緩存和Redis緩存.
范例:
var builder = WebApplication.CreateBuilder( args ); builder.AsBuild() .AddMemoryCache() .AddRedisCache( "127.0.0.1" ) .AddHybridCache();如果要修改 TopicName,傳入主題參數(shù),如下所示.
builder.AsBuild() .AddMemoryCache() .AddRedisCache( "127.0.0.1" ) .AddHybridCache( "topic" ); -
-
使用 IConfiguration 進(jìn)行配置.
可以使用 appsettings.json 文件進(jìn)行配置.
builder.AsBuild() .AddMemoryCache() .AddRedisCache( "127.0.0.1" ) .AddHybridCache( builder.Configuration );除了需要配置2級(jí)緩存提供程序,還需要配置 Redis 總線.
默認(rèn)配置節(jié)名稱:
-
2級(jí)緩存默認(rèn)配置節(jié)名稱: EasyCaching:Hybrid
-
Redis總線配置節(jié)名稱: EasyCaching:RedisBus
appsettings.json 配置文件示例.
{ "EasyCaching": { "Hybrid": { "LocalCacheProviderName": "DefaultInMemory", "DistributedCacheProviderName": "DefaultRedis", "TopicName": "EasyCachingHybridCache" }, "RedisBus": { "Endpoints": [ { "Host": "localhost", "Port": 6739 } ], "SerializerName": "SystemTextJson" } } } -
-
使用委托進(jìn)行配置.
builder.AsBuild() .AddMemoryCache() .AddRedisCache( "127.0.0.1" ) .AddHybridCache( hybridOptions => { hybridOptions.LocalCacheProviderName = "DefaultInMemory"; hybridOptions.DistributedCacheProviderName = "DefaultRedis"; hybridOptions.TopicName = "topic"; }, redisBusOptions => { redisBusOptions.Endpoints.Add( new ServerEndPoint( "127.0.0.1", 6379 ) ); redisBusOptions.SerializerName = "SystemTextJson"; } )
-
-
配置參數(shù)
EasyCaching 緩存提供了多個(gè)配置參數(shù),具體請(qǐng)參考 EasyCaching 文檔.
下面介紹幾個(gè)比較重要的參數(shù).
-
MaxRdSecond
MaxRdSecond 是額外添加的緩存間隔最大隨機(jī)秒數(shù).
MaxRdSecond 用于防止緩存雪崩,在緩存時(shí)間基礎(chǔ)上增加隨機(jī)秒數(shù),以防止同一時(shí)間所有緩存項(xiàng)失效.
MaxRdSecond 的默認(rèn)值為 120, 增加的隨機(jī)間隔是120秒以內(nèi)的某個(gè)隨機(jī)值.
你可以增大 MaxRdSecond ,以更大的范圍錯(cuò)開(kāi)各緩存項(xiàng)的失效時(shí)間.
對(duì)于集成測(cè)試,你如果要測(cè)試緩存失效時(shí)間,需要將該值設(shè)置為 0.
-
CacheNulls
CacheNulls 用于解決緩存穿透問(wèn)題.
當(dāng)使用 Get( key, ()=> value ) 方法獲取緩存時(shí),如果返回的value為null,是否應(yīng)該創(chuàng)建緩存項(xiàng).
CacheNulls 的值為 true 時(shí),創(chuàng)建緩存項(xiàng).
如果返回值為null不創(chuàng)建緩存項(xiàng),使用相同緩存鍵的每次請(qǐng)求都會(huì)到達(dá)數(shù)據(jù)庫(kù).
CacheNulls設(shè)置為 true 可防范正常業(yè)務(wù)的緩存穿透.
但惡意攻擊每次傳遞的參數(shù)可能不同,請(qǐng)求依然會(huì)到達(dá)數(shù)據(jù)庫(kù),且浪費(fèi)緩存空間.
可以通過(guò)緩存全部有效參數(shù)的方式精確判斷輸入?yún)?shù)是否在有效業(yè)務(wù)范圍,不過(guò)會(huì)占用過(guò)多內(nèi)存.
要減少內(nèi)存占用,可使用布隆過(guò)濾器.
EasyCaching尚未內(nèi)置布隆過(guò)濾器,請(qǐng)自行實(shí)現(xiàn).
-
緩存鍵
每個(gè)緩存項(xiàng)有一個(gè)唯一標(biāo)識(shí)的鍵名,通過(guò)緩存鍵來(lái)獲取緩存項(xiàng).
緩存鍵通常是一個(gè)字符串.
可以以任意方式構(gòu)造緩存鍵,只要保證唯一即可.
但是根據(jù)緩存項(xiàng)的功能進(jìn)行構(gòu)造更容易識(shí)別緩存項(xiàng)的用途.
范例1:
是否管理員緩存鍵
IsAdmin-1
IsAdmin 代表是否管理員, 1是用戶的Id,需要把用戶Id的參數(shù)拼接到緩存鍵,以識(shí)別特定的緩存項(xiàng)
范例2:
菜單緩存鍵.
Menu-1
Menu 代表菜單, 1是用戶的Id.
緩存鍵前綴
如果用戶退出了,我們需要清除他的全部緩存項(xiàng).
EasyCaching支持通過(guò)緩存鍵前綴批量移除緩存項(xiàng).
修改前面的范例.
是否管理員緩存鍵: User-1-IsAdmin
菜單緩存鍵: User-1-Menu
User代表用戶,1是用戶Id, User-1 前綴可以標(biāo)識(shí)Id為1的用戶.
使用 User-1 前綴就可以移除用戶1的所有緩存項(xiàng).
CacheKey
你可以直接創(chuàng)建緩存鍵字符串,不過(guò)有些緩存鍵可能比較復(fù)雜,由很多參數(shù)構(gòu)成.
另外可能需要在多個(gè)地方使用同一個(gè)緩存鍵進(jìn)行操作.
用一個(gè)對(duì)象來(lái)封裝緩存鍵的構(gòu)造,不僅可以降低緩存鍵的復(fù)雜性,而且也方便多處使用.
Util應(yīng)用框架提供了一個(gè)緩存鍵對(duì)象 Util.Caching.CacheKey.
CacheKey 包含兩個(gè)屬性, Prefix 和 Key.
Prefix 是緩存鍵前綴,Key是緩存鍵.
通常不直接使用 CacheKey,而是從它派生具體的緩存鍵,這樣可以更清晰的表示緩存項(xiàng)的用途,以及更好的接收參數(shù).
范例:
-
定義 AclCacheKey 緩存鍵.
AclCacheKey 表示訪問(wèn)控制緩存鍵,接收用戶Id和資源Id參數(shù).
public class AclCacheKey : CacheKey { public AclCacheKey( string userId, string resourceId ) { Prefix = $"User-{userId}:"; Key = $"Acl-{resourceId}"; } } -
使用 AclCacheKey 緩存鍵.
實(shí)例化 AclCacheKey ,傳入?yún)?shù), 通過(guò) Key 屬性獲取緩存鍵.
var cacheKey = new AclCacheKey("1","2"); var key = cacheKey.Key;Key 屬性返回 Prefix 與 Key 連接后的結(jié)果: User-1:Acl-2
也可以使用 ToString 方法獲取緩存鍵.
var cacheKey = new AclCacheKey("1","2"); var key = cacheKey.ToString();
緩存操作
Util應(yīng)用框架緩存操作提供了三個(gè)接口: ICache, ILocalCache, IRedisCache.
Util.Caching.ICache 是緩存操作的主要接口.
根據(jù)緩存配置,ICache可以在本地緩存,Redis緩存,2級(jí)緩存切換.
-
如果僅配置本地緩存, ICache實(shí)例為本地緩存操作.
-
如果僅配置 Redis 緩存,ICache實(shí)例為Redis緩存操作.
-
如果同時(shí)配置本地緩存和 Redis 緩存,ICache 實(shí)例為后配置的緩存操作.
-
如果配置了2級(jí)緩存,ICache 實(shí)例為2級(jí)緩存操作.
注意事項(xiàng)
如果使用2級(jí)緩存,有些操作不可用,調(diào)用會(huì)拋出異常.
示例上下文
-
通過(guò)依賴注入獲取 ICache 實(shí)例.
public class Service : IService { private ICache _cache; private IUserResourceRepository _repository; public Service( ICache cache,IUserResourceRepository repository ) { _cache = cache; _repository = repository; } } -
用戶資源示例
public class UserResource { public string UserId { get; set; } public string UserName { get; set; } public string ResourceId { get; set; } public string ResourceName { get; set; } } -
用戶資源緩存鍵示例
public class UserResourceCacheKey : CacheKey { public UserResourceCacheKey( string userId,string resourceId ) { Prefix = $"User-{userId}:"; Key = $"Resource-{resourceId}"; } } -
用戶資源倉(cāng)儲(chǔ)示例
public interface IUserResourceRepository { UserResource GetUserResource( string userId, string resourceId ); Task<UserResource> GetUserResourceAsync( string userId, string resourceId ); }
API
-
Exists
功能: 判斷緩存是否存在
-
bool Exists( CacheKey key )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); bool exists = _cache.Exists( cacheKey ); -
bool Exists( string key )
范例:
bool exists = _cache.Exists( "User-1:Resource-2" );
-
-
ExistsAsync
功能: 判斷緩存是否存在
-
Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); bool exists = await _cache.ExistsAsync( cacheKey ); -
Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default )
范例:
bool exists = await _cache.ExistsAsync( "User-1:Resource-2" );
-
-
Get
功能: 從緩存中獲取數(shù)據(jù)
-
T Get<T>( CacheKey key )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = _cache.Get<UserResource>( cacheKey ); -
T Get<T>( string key )
范例:
var result = _cache.Get<UserResource>( "User-1:Resource-2" ); -
List<T> Get<T>( IEnumerable<CacheKey> keys )
通過(guò)緩存鍵集合獲取結(jié)果集合.
范例:
var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) }; var result = _cache.Get<UserResource>( keys ); -
List<T> Get<T>( IEnumerable<string> keys )
通過(guò)緩存鍵集合獲取結(jié)果集合.
范例:
var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" }; var result = _cache.Get<UserResource>( keys ); -
T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null )
從緩存中獲取數(shù)據(jù),如果數(shù)據(jù)不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中.
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = _cache.Get( cacheKey,()=> _repository.GetUserResource( "1", "2" ) );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = _cache.Get( cacheKey, () => _repository.GetUserResource( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
T Get<T>( string key, Func<T> action, CacheOptions options = null )
從緩存中獲取數(shù)據(jù),如果數(shù)據(jù)不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中.
范例:
var result = _cache.Get( "User-1:Resource-2",()=> _repository.GetUserResource( "1", "2" ) );
-
-
GetAsync
功能: 從緩存中獲取數(shù)據(jù)
-
Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default )
無(wú)法傳入泛型返回類型參數(shù)可使用該重載方法.
范例:
object result = await _cache.GetAsync( "User-1:Resource-2", typeof( UserResource ) ); -
Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = await _cache.GetAsync<UserResource>( cacheKey ); -
Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default )
范例:
var result = await _cache.GetAsync<UserResource>( "User-1:Resource-2" ); -
Task<List
> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default ) 通過(guò)緩存鍵集合獲取結(jié)果集合.
范例:
var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) }; var result = await _cache.GetAsync<UserResource>( keys ); -
Task<List
> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default ) 通過(guò)緩存鍵集合獲取結(jié)果集合.
范例:
var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" }; var result = await _cache.GetAsync<UserResource>( keys ); -
Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default )
從緩存中獲取數(shù)據(jù),如果數(shù)據(jù)不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中.
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = await _cache.GetAsync( cacheKey,async ()=> await _repository.GetUserResourceAsync( "1", "2" ) );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = await _cache.GetAsync( cacheKey,async ()=> await _repository.GetUserResourceAsync( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default )
從緩存中獲取數(shù)據(jù),如果數(shù)據(jù)不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中.
范例:
var result = await _cache.GetAsync( "User-1:Resource-2",async ()=> await _repository.GetUserResourceAsync( "1", "2" ) );
-
-
GetByPrefix
功能: 通過(guò)緩存鍵前綴獲取數(shù)據(jù)
-
List<T> GetByPrefix<T>( string prefix )
范例:
var result = _cache.GetByPrefix<UserResource>( "User-1" );
-
-
GetByPrefixAsync
功能: 通過(guò)緩存鍵前綴獲取數(shù)據(jù)
-
Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default )
范例:
var result = await _cache.GetByPrefixAsync<UserResource>( "User-1" );
-
-
TrySet
功能: 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
-
bool TrySet<T>( CacheKey key, T value, CacheOptions options = null )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = _repository.GetUserResource( "1", "2" ); var result = _cache.TrySet( cacheKey, value );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = _repository.GetUserResource( "1", "2" ); var result = _cache.TrySet( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
bool TrySet<T>( string key, T value, CacheOptions options = null )
范例:
var value = _repository.GetUserResource( "1", "2" ); var result = _cache.TrySet( "User-1:Resource-2", value );
-
-
TrySetAsync
功能: 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
-
Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = await _repository.GetUserResourceAsync( "1", "2" ); var result = await _cache.TrySetAsync( cacheKey, value );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = await _repository.GetUserResourceAsync( "1", "2" ); var result = await _cache.TrySetAsync( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var value = await _repository.GetUserResourceAsync( "1", "2" ); var result = await _cache.TrySetAsync( "User-1:Resource-2", value );
-
-
Set
功能: 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
-
void Set<T>( CacheKey key, T value, CacheOptions options = null )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = _repository.GetUserResource( "1", "2" ); _cache.Set( cacheKey, value );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = _repository.GetUserResource( "1", "2" ); _cache.Set( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
void Set<T>( string key, T value, CacheOptions options = null )
范例:
var value = _repository.GetUserResource( "1", "2" ); _cache.Set( "User-1:Resource-2", value ); -
void Set<T>( IDictionary<CacheKey,T> items, CacheOptions options = null )
范例:
var items = new Dictionary<CacheKey, UserResource> { { new UserResourceCacheKey( "1", "2" ), _repository.GetUserResource( "1", "2" ) }, { new UserResourceCacheKey( "3", "4" ), _repository.GetUserResource( "3", "4" ) } }; _cache.Set( items ); -
void Set<T>( IDictionary<string,T> items, CacheOptions options = null )
范例:
var items = new Dictionary<string, UserResource> { { "User-1:Resource-2", _repository.GetUserResource( "1", "2" ) }, { "User-3:Resource-4", _repository.GetUserResource( "3", "4" ) } }; _cache.Set( items );
-
-
SetAsync
功能: 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
-
Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = await _repository.GetUserResourceAsync( "1", "2" ); await _cache.SetAsync( cacheKey, value );CacheOptions 配置包含 Expiration 屬性,用于設(shè)置緩存過(guò)期時(shí)間間隔,默認(rèn)值: 8小時(shí).
設(shè)置1小時(shí)過(guò)期,如下所示.
var cacheKey = new UserResourceCacheKey( "1", "2" ); var value = await _repository.GetUserResourceAsync( "1", "2" ); await _cache.SetAsync( cacheKey, value, new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var value = await _repository.GetUserResourceAsync( "1", "2" ); await _cache.SetAsync( "User-1:Resource-2", value ); -
Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var items = new Dictionary<CacheKey, UserResource> { { new UserResourceCacheKey( "1", "2" ), await _repository.GetUserResourceAsync( "1", "2" ) }, { new UserResourceCacheKey( "3", "4" ), await _repository.GetUserResourceAsync( "3", "4" ) } }; await _cache.SetAsync( items ); -
Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default )
范例:
var items = new Dictionary<string, UserResource> { { "User-1:Resource-2", await _repository.GetUserResourceAsync( "1", "2" ) }, { "User-3:Resource-4", await _repository.GetUserResourceAsync( "3", "4" ) } }; await _cache.SetAsync( items );
-
-
Remove
功能: 移除緩存
-
void Remove( CacheKey key )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); _cache.Remove( cacheKey ); -
void Remove( string key )
范例:
_cache.Remove( "User-1:Resource-2" ); -
void Remove( IEnumerable<CacheKey> keys )
范例:
var keys = new List<UserResourceCacheKey> { new ( "1", "2" ), new( "3", "4" ) }; _cache.Remove( keys ); -
void Remove( IEnumerable<string> keys )
范例:
var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" }; _cache.Remove( keys );
-
-
RemoveAsync
功能: 移除緩存
-
Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default )
范例:
var cacheKey = new UserResourceCacheKey( "1", "2" ); await _cache.RemoveAsync( cacheKey ); -
Task RemoveAsync( string key, CancellationToken cancellationToken = default )
范例:
await _cache.RemoveAsync( "User-1:Resource-2" ); -
Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default )
范例:
var keys = new List<UserResourceCacheKey> { new( "1", "2" ), new( "3", "4" ) }; await _cache.RemoveAsync( keys ); -
Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default )
范例:
var keys = new List<string> { "User-1:Resource-2", "User-3:Resource-4" }; await _cache.RemoveAsync( keys );
-
-
RemoveByPrefix
功能: 通過(guò)緩存鍵前綴移除緩存
-
void RemoveByPrefix( string prefix )
范例:
_cache.RemoveByPrefix( "User-1" ); -
Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default )
范例:
await _cache.RemoveByPrefixAsync( "User-1" );
-
-
RemoveByPattern
功能: 通過(guò)模式移除緩存
-
void RemoveByPattern( string pattern )
范例:
移除 User 開(kāi)頭的緩存.
_cache.RemoveByPattern( "User*" );
-
-
RemoveByPatternAsync
功能: 通過(guò)模式移除緩存
-
Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default )
范例:
移除 User 開(kāi)頭的緩存.
await _cache.RemoveByPatternAsync( "User*" );
-
-
Clear
功能: 清空緩存
-
void Clear()
范例:
_cache.Clear();
-
-
ClearAsync
功能: 清空緩存
-
Task ClearAsync( CancellationToken cancellationToken = default )
范例:
await _cache.ClearAsync();
-
ILocalCache
Util.Caching.ILocalCache 從 ICache 派生,表示本地緩存.
當(dāng)同時(shí)配置本地緩存和Redis緩存, 如果你想明確使用本地緩存, 請(qǐng)使用 ILocalCache.
Api 參考 ICache.
IRedisCache
Util.Caching.IRedisCache 從 ICache 派生,表示 Redis 分布式緩存.
當(dāng)同時(shí)配置本地緩存和Redis緩存, 如果你想明確使用 Redis 緩存, 請(qǐng)使用 IRedisCache.
IRedisCache 除了繼承基礎(chǔ)緩存操作外,還將添加 Redis 專用緩存操作.
目前 IRedisCache 尚未添加 Redis 專用操作,后續(xù)根據(jù)需要進(jìn)行添加.
Api 參考 ICache.
更新緩存
-
設(shè)置緩存到期時(shí)間
創(chuàng)建緩存項(xiàng)時(shí)可以設(shè)置一個(gè)過(guò)期時(shí)間間隔,超過(guò)到期時(shí)間,緩存將失效.
EasyCaching 目前尚不支持滑動(dòng)過(guò)期.
下面的示例設(shè)置1小時(shí)的過(guò)期時(shí)間間隔,當(dāng)超過(guò)1小時(shí),緩存過(guò)期后,將重新加載最新數(shù)據(jù).
var cacheKey = new UserResourceCacheKey( "1", "2" ); var result = _cache.Get( cacheKey, () => _repository.GetUserResource( "1", "2" ), new CacheOptions { Expiration = TimeSpan.FromHours( 1 ) } ); -
通過(guò)本地事件總線更新緩存
基于過(guò)期時(shí)間被動(dòng)更新,適合實(shí)時(shí)性要求低的場(chǎng)景.
當(dāng)數(shù)據(jù)庫(kù)的值已更新,從緩存中讀取舊值,對(duì)業(yè)務(wù)基本沒(méi)有影響或影響很小.
但有些數(shù)據(jù)具有更高的實(shí)時(shí)性,在數(shù)據(jù)庫(kù)更新時(shí),需要同步更新緩存中的副本.
可以通過(guò)發(fā)布訂閱本地事件總線實(shí)時(shí)更新特定緩存.
緩存攔截器
-
CacheAttribute 緩存攔截器
[Cache] 是一個(gè)緩存攔截器,使用 ICache 接口操作緩存.
它會(huì)根據(jù)參數(shù)自動(dòng)創(chuàng)建緩存鍵,并調(diào)用攔截的方法獲取數(shù)據(jù)并緩存起來(lái).
如果你不關(guān)心緩存鍵的長(zhǎng)相,可以使用 [Cache] 攔截器快速添加緩存.
范例:
public interface ITestService { [Cache] UserResource Get( string userId, string resourceId ); }-
設(shè)置緩存鍵前綴 Prefix.
緩存鍵前綴支持占位符, {0} 代表第一個(gè)參數(shù).
范例:
public interface ITestService { [Cache( Prefix = "User-{0}" )] UserResource Get( string userId, string resourceId ); }下面的示例調(diào)用 ITestService 的 Get 方法,傳入?yún)?shù) userId = "1" , resourceId = "2" .
創(chuàng)建的緩存鍵為: "User-1:1:2".
緩存鍵前綴 User-{0} 中的 {0} 替換為第一個(gè)參數(shù) userId ,即 User-1.
使用 : 按順序連接所有參數(shù)值.
var result = _service.Get( "1", "2" ); -
設(shè)置緩存過(guò)期間隔 Expiration ,單位: 秒,默認(rèn)值: 36000
范例:
設(shè)置 120 秒過(guò)期.
public interface ITestService { [Cache( Expiration = 120 )] UserResource Get( string userId, string resourceId ); }
-
-
LocalCacheAttribute 本地緩存攔截器
[LocalCache] 與 [Cache] 類似,但它使用 ILocalCache 接口操作緩存.
如果你的某個(gè)操作需要使用本地緩存,可以用 [LocalCache].
具體操作請(qǐng)參考 [Cache].
-
RedisCacheAttribute Redis緩存攔截器
[RedisCache] 與 [Cache] 類似,但它使用 IRedisCache 接口操作緩存.
如果你的某個(gè)操作需要使用Redis緩存,可以用 [RedisCache].
具體操作請(qǐng)參考 [Cache].
緩存內(nèi)存釋放
當(dāng)緩存占據(jù)大量?jī)?nèi)存空間,調(diào)用 Clear 清理緩存并不會(huì)釋放內(nèi)存,等待一段時(shí)間仍然不會(huì)釋放.
對(duì)于 IMemoryCache 同樣如此.
某些測(cè)試環(huán)境,你可以調(diào)用 GC.Collect() 強(qiáng)制回收內(nèi)存空間.
生產(chǎn)環(huán)境,不應(yīng)手工回收.
源碼解析
ICache 緩存操作
Util.Caching.ICache 是緩存操作接口.
CacheManager 將緩存操作委托給 EasyCaching 的 IEasyCachingProvider 接口.
IEasyCachingProvider 根據(jù)配置的提供程序切換為本地緩存或Redis緩存.
當(dāng)配置了2級(jí)緩存, 緩存操作委托給 IHybridCachingProvider 2級(jí)緩存提供程序接口.
/// <summary>
/// 緩存
/// </summary>
public interface ICache {
/// <summary>
/// 緩存是否已存在
/// </summary>
/// <param name="key">緩存鍵</param>
bool Exists( CacheKey key );
/// <summary>
/// 緩存是否已存在
/// </summary>
/// <param name="key">緩存鍵</param>
bool Exists( string key );
/// <summary>
/// 緩存是否已存在
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default );
/// <summary>
/// 緩存是否已存在
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
T Get<T>( CacheKey key );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
T Get<T>( string key );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="keys">緩存鍵集合</param>
List<T> Get<T>( IEnumerable<CacheKey> keys );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="keys">緩存鍵集合</param>
List<T> Get<T>( IEnumerable<string> keys );
/// <summary>
/// 從緩存中獲取數(shù)據(jù),如果不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="action">獲取數(shù)據(jù)操作</param>
/// <param name="options">緩存配置</param>
T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null );
/// <summary>
/// 從緩存中獲取數(shù)據(jù),如果不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="action">獲取數(shù)據(jù)操作</param>
/// <param name="options">緩存配置</param>
T Get<T>( string key, Func<T> action, CacheOptions options = null );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="type">緩存數(shù)據(jù)類型</param>
/// <param name="cancellationToken">取消令牌</param>
Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="keys">緩存鍵集合</param>
/// <param name="cancellationToken">取消令牌</param>
Task<List<T>> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="keys">緩存鍵集合</param>
/// <param name="cancellationToken">取消令牌</param>
Task<List<T>> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù),如果不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="action">獲取數(shù)據(jù)操作</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 從緩存中獲取數(shù)據(jù),如果不存在,則執(zhí)行獲取數(shù)據(jù)操作并添加到緩存中
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="action">獲取數(shù)據(jù)操作</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 通過(guò)緩存鍵前綴獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="prefix">緩存鍵前綴</param>
List<T> GetByPrefix<T>( string prefix );
/// <summary>
/// 通過(guò)緩存鍵前綴獲取數(shù)據(jù)
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="prefix">緩存鍵前綴</param>
/// <param name="cancellationToken">取消令牌</param>
Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
bool TrySet<T>( CacheKey key, T value, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
bool TrySet<T>( string key, T value, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則忽略,設(shè)置成功返回true
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
void Set<T>( CacheKey key, T value, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
void Set<T>( string key, T value, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存集合
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="items">緩存項(xiàng)集合</param>
/// <param name="options">緩存配置</param>
void Set<T>( IDictionary<CacheKey,T> items, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存集合
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="items">緩存項(xiàng)集合</param>
/// <param name="options">緩存配置</param>
void Set<T>( IDictionary<string, T> items, CacheOptions options = null );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存,當(dāng)緩存已存在則覆蓋
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="key">緩存鍵</param>
/// <param name="value">值</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存集合
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="items">緩存項(xiàng)集合</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 設(shè)置緩存集合
/// </summary>
/// <typeparam name="T">緩存數(shù)據(jù)類型</typeparam>
/// <param name="items">緩存項(xiàng)集合</param>
/// <param name="options">緩存配置</param>
/// <param name="cancellationToken">取消令牌</param>
Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default );
/// <summary>
/// 移除緩存
/// </summary>
/// <param name="key">緩存鍵</param>
void Remove( CacheKey key );
/// <summary>
/// 移除緩存
/// </summary>
/// <param name="key">緩存鍵</param>
void Remove( string key );
/// <summary>
/// 移除緩存集合
/// </summary>
/// <param name="keys">緩存鍵集合</param>
void Remove( IEnumerable<CacheKey> keys );
/// <summary>
/// 移除緩存集合
/// </summary>
/// <param name="keys">緩存鍵集合</param>
void Remove( IEnumerable<string> keys );
/// <summary>
/// 移除緩存
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default );
/// <summary>
/// 移除緩存
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveAsync( string key, CancellationToken cancellationToken = default );
/// <summary>
/// 移除緩存集合
/// </summary>
/// <param name="keys">緩存鍵集合</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default );
/// <summary>
/// 移除緩存集合
/// </summary>
/// <param name="keys">緩存鍵集合</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default );
/// <summary>
/// 通過(guò)緩存鍵前綴移除緩存
/// </summary>
/// <param name="prefix">緩存鍵前綴</param>
void RemoveByPrefix( string prefix );
/// <summary>
/// 通過(guò)緩存鍵前綴移除緩存
/// </summary>
/// <param name="prefix">緩存鍵前綴</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default );
/// <summary>
/// 通過(guò)緩存鍵模式移除緩存
/// </summary>
/// <param name="pattern">緩存鍵模式,范例: test*</param>
void RemoveByPattern( string pattern );
/// <summary>
/// 通過(guò)緩存鍵模式移除緩存
/// </summary>
/// <param name="pattern">緩存鍵模式,范例: test*</param>
/// <param name="cancellationToken">取消令牌</param>
Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default );
/// <summary>
/// 清空緩存
/// </summary>
void Clear();
/// <summary>
/// 清空緩存
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
Task ClearAsync( CancellationToken cancellationToken = default );
}
/// <summary>
/// EasyCaching緩存服務(wù)
/// </summary>
public class CacheManager : ICache {
#region 字段
/// <summary>
/// 緩存提供器
/// </summary>
private readonly IEasyCachingProviderBase _provider;
/// <summary>
/// 緩存提供器
/// </summary>
private readonly IEasyCachingProvider _cachingProvider;
#endregion
#region 構(gòu)造方法
/// <summary>
/// 初始化EasyCaching緩存服務(wù)
/// </summary>
/// <param name="provider">EasyCaching緩存提供器</param>
/// <param name="hybridProvider">EasyCaching 2級(jí)緩存提供器</param>
public CacheManager( IEasyCachingProvider provider, IHybridCachingProvider hybridProvider = null ) {
CachingOptions.Clear();
if ( provider != null ) {
_provider = provider;
_cachingProvider = provider;
}
if( hybridProvider != null )
_provider = hybridProvider;
_provider.CheckNull( nameof( provider ) );
}
#endregion
#region Exists
/// <inheritdoc />
public bool Exists( CacheKey key ) {
key.Validate();
return Exists( key.Key );
}
/// <inheritdoc />
public bool Exists( string key ) {
return _provider.Exists( key );
}
#endregion
#region ExistsAsync
/// <inheritdoc />
public async Task<bool> ExistsAsync( CacheKey key, CancellationToken cancellationToken = default ) {
key.Validate();
return await ExistsAsync( key.Key, cancellationToken );
}
/// <inheritdoc />
public async Task<bool> ExistsAsync( string key, CancellationToken cancellationToken = default ) {
return await _provider.ExistsAsync( key, cancellationToken );
}
#endregion
#region Get
/// <inheritdoc />
public T Get<T>( CacheKey key ) {
key.Validate();
return Get<T>( key.Key );
}
/// <inheritdoc />
public T Get<T>( string key ) {
var result = _provider.Get<T>( key );
return result.Value;
}
/// <inheritdoc />
public List<T> Get<T>( IEnumerable<CacheKey> keys ) {
return Get<T>( ToKeys( keys ) );
}
/// <summary>
/// 轉(zhuǎn)換為緩存鍵字符串集合
/// </summary>
private IEnumerable<string> ToKeys( IEnumerable<CacheKey> keys ) {
keys.CheckNull( nameof( keys ) );
var cacheKeys = keys.ToList();
cacheKeys.ForEach( t => t.Validate() );
return cacheKeys.Select( t => t.Key );
}
/// <inheritdoc />
public List<T> Get<T>( IEnumerable<string> keys ) {
Validate();
var result = _cachingProvider.GetAll<T>( keys );
return result.Values.Select( t => t.Value ).ToList();
}
/// <summary>
/// 驗(yàn)證
/// </summary>
private void Validate() {
if ( _cachingProvider == null )
throw new NotSupportedException( "2級(jí)緩存不支持該操作" );
}
/// <inheritdoc />
public T Get<T>( CacheKey key, Func<T> action, CacheOptions options = null ) {
key.Validate();
return Get( key.Key, action, options );
}
/// <inheritdoc />
public T Get<T>( string key, Func<T> action, CacheOptions options = null ) {
var result = _provider.Get( key, action, GetExpiration( options ) );
return result.Value;
}
/// <summary>
/// 獲取過(guò)期時(shí)間間隔
/// </summary>
private TimeSpan GetExpiration( CacheOptions options ) {
var result = options?.Expiration;
result ??= TimeSpan.FromHours( 8 );
return result.SafeValue();
}
#endregion
#region GetAsync
/// <inheritdoc />
public async Task<object> GetAsync( string key, Type type, CancellationToken cancellationToken = default ) {
return await _provider.GetAsync( key, type, cancellationToken );
}
/// <inheritdoc />
public async Task<T> GetAsync<T>( CacheKey key, CancellationToken cancellationToken = default ) {
key.Validate();
return await GetAsync<T>( key.Key, cancellationToken );
}
/// <inheritdoc />
public async Task<T> GetAsync<T>( string key, CancellationToken cancellationToken = default ) {
var result = await _provider.GetAsync<T>( key, cancellationToken );
return result.Value;
}
/// <inheritdoc />
public async Task<List<T>> GetAsync<T>( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default ) {
return await GetAsync<T>( ToKeys( keys ), cancellationToken );
}
/// <inheritdoc />
public async Task<List<T>> GetAsync<T>( IEnumerable<string> keys, CancellationToken cancellationToken = default ) {
Validate();
var result = await _cachingProvider.GetAllAsync<T>( keys, cancellationToken );
return result.Values.Select( t => t.Value ).ToList();
}
/// <inheritdoc />
public async Task<T> GetAsync<T>( CacheKey key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default ) {
key.Validate();
return await GetAsync( key.Key, action, options, cancellationToken );
}
/// <inheritdoc />
public async Task<T> GetAsync<T>( string key, Func<Task<T>> action, CacheOptions options = null, CancellationToken cancellationToken = default ) {
var result = await _provider.GetAsync( key, action, GetExpiration( options ), cancellationToken );
return result.Value;
}
#endregion
#region GetByPrefix
/// <inheritdoc />
public List<T> GetByPrefix<T>( string prefix ) {
if( prefix.IsEmpty() )
return new List<T>();
Validate();
return _cachingProvider.GetByPrefix<T>( prefix ).Where( t => t.Value.HasValue ).Select( t => t.Value.Value ).ToList();
}
#endregion
#region GetByPrefixAsync
/// <inheritdoc />
public async Task<List<T>> GetByPrefixAsync<T>( string prefix, CancellationToken cancellationToken = default ) {
if( prefix.IsEmpty() )
return new List<T>();
Validate();
var result = await _cachingProvider.GetByPrefixAsync<T>( prefix, cancellationToken );
return result.Where( t => t.Value.HasValue ).Select( t => t.Value.Value ).ToList();
}
#endregion
#region TrySet
/// <inheritdoc />
public bool TrySet<T>( CacheKey key, T value, CacheOptions options = null ) {
key.Validate();
return TrySet( key.Key, value, options );
}
/// <inheritdoc />
public bool TrySet<T>( string key, T value, CacheOptions options = null ) {
return _provider.TrySet( key, value, GetExpiration( options ) );
}
#endregion
#region TrySetAsync
/// <inheritdoc />
public async Task<bool> TrySetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
key.Validate();
return await TrySetAsync( key.Key, value, options, cancellationToken );
}
/// <inheritdoc />
public async Task<bool> TrySetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
return await _provider.TrySetAsync( key, value, GetExpiration( options ), cancellationToken );
}
#endregion
#region Set
/// <inheritdoc />
public void Set<T>( CacheKey key, T value, CacheOptions options = null ) {
key.Validate();
Set( key.Key, value, options );
}
/// <inheritdoc />
public void Set<T>( string key, T value, CacheOptions options = null ) {
_provider.Set( key, value, GetExpiration( options ) );
}
/// <inheritdoc />
public void Set<T>( IDictionary<CacheKey, T> items, CacheOptions options = null ) {
Set( ToItems( items ), options );
}
/// <summary>
/// 轉(zhuǎn)換為緩存項(xiàng)集合
/// </summary>
private IDictionary<string, T> ToItems<T>( IDictionary<CacheKey, T> items ) {
items.CheckNull( nameof( items ) );
return items.Select( item => {
item.Key.Validate();
return new KeyValuePair<string, T>( item.Key.Key, item.Value );
} ).ToDictionary( t => t.Key, t => t.Value );
}
/// <inheritdoc />
public void Set<T>( IDictionary<string, T> items, CacheOptions options = null ) {
_provider.SetAll( items, GetExpiration( options ) );
}
#endregion
#region SetAsync
/// <inheritdoc />
public async Task SetAsync<T>( CacheKey key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
key.Validate();
await SetAsync( key.Key, value, options, cancellationToken );
}
/// <inheritdoc />
public async Task SetAsync<T>( string key, T value, CacheOptions options = null, CancellationToken cancellationToken = default ) {
await _provider.SetAsync( key, value, GetExpiration( options ), cancellationToken );
}
/// <inheritdoc />
public async Task SetAsync<T>( IDictionary<CacheKey, T> items, CacheOptions options = null, CancellationToken cancellationToken = default ) {
await SetAsync( ToItems( items ), options, cancellationToken );
}
/// <inheritdoc />
public async Task SetAsync<T>( IDictionary<string, T> items, CacheOptions options = null, CancellationToken cancellationToken = default ) {
await _provider.SetAllAsync( items, GetExpiration( options ), cancellationToken );
}
#endregion
#region Remove
/// <inheritdoc />
public void Remove( CacheKey key ) {
key.Validate();
Remove( key.Key );
}
/// <inheritdoc />
public void Remove( string key ) {
_provider.Remove( key );
}
/// <inheritdoc />
public void Remove( IEnumerable<CacheKey> keys ) {
Remove( ToKeys( keys ) );
}
/// <inheritdoc />
public void Remove( IEnumerable<string> keys ) {
_provider.RemoveAll( keys );
}
#endregion
#region RemoveAsync
/// <inheritdoc />
public async Task RemoveAsync( CacheKey key, CancellationToken cancellationToken = default ) {
key.Validate();
await RemoveAsync( key.Key, cancellationToken );
}
/// <inheritdoc />
public async Task RemoveAsync( string key, CancellationToken cancellationToken = default ) {
await _provider.RemoveAsync( key, cancellationToken );
}
/// <inheritdoc />
public async Task RemoveAsync( IEnumerable<CacheKey> keys, CancellationToken cancellationToken = default ) {
await RemoveAsync( ToKeys( keys ), cancellationToken );
}
/// <inheritdoc />
public async Task RemoveAsync( IEnumerable<string> keys, CancellationToken cancellationToken = default ) {
await _provider.RemoveAllAsync( keys, cancellationToken );
}
#endregion
#region RemoveByPrefix
/// <summary>
/// 通過(guò)緩存鍵前綴移除緩存
/// </summary>
/// <param name="prefix">緩存鍵前綴</param>
public void RemoveByPrefix( string prefix ) {
if( prefix.IsEmpty() )
return;
_provider.RemoveByPrefix( prefix );
}
#endregion
#region RemoveByPrefixAsync
/// <inheritdoc />
public async Task RemoveByPrefixAsync( string prefix, CancellationToken cancellationToken = default ) {
if( prefix.IsEmpty() )
return;
await _provider.RemoveByPrefixAsync( prefix, cancellationToken );
}
#endregion
#region RemoveByPattern
/// <inheritdoc />
public void RemoveByPattern( string pattern ) {
if( pattern.IsEmpty() )
return;
_provider.RemoveByPattern( pattern );
}
#endregion
#region RemoveByPatternAsync
/// <inheritdoc />
public async Task RemoveByPatternAsync( string pattern, CancellationToken cancellationToken = default ) {
if( pattern.IsEmpty() )
return;
await _provider.RemoveByPatternAsync( pattern, cancellationToken );
}
#endregion
#region Clear
/// <inheritdoc />
public void Clear() {
Validate();
_cachingProvider.Flush();
}
#endregion
#region ClearAsync
/// <inheritdoc />
public async Task ClearAsync( CancellationToken cancellationToken = default ) {
Validate();
await _cachingProvider.FlushAsync( cancellationToken );
}
#endregion
}
CacheKey 緩存鍵
通過(guò)繼承 CacheKey 創(chuàng)建自定義緩存鍵對(duì)象,可以封裝緩存鍵的構(gòu)造細(xì)節(jié).
/// <summary>
/// 緩存鍵
/// </summary>
public class CacheKey {
/// <summary>
/// 緩存鍵
/// </summary>
private string _key;
/// <summary>
/// 初始化緩存鍵
/// </summary>
public CacheKey() {
}
/// <summary>
/// 初始化緩存鍵
/// </summary>
/// <param name="key">緩存鍵</param>
/// <param name="parameters">緩存鍵參數(shù)</param>
public CacheKey( string key,params object[] parameters) {
_key = string.Format( key, parameters );
}
/// <summary>
/// 緩存鍵
/// </summary>
public string Key {
get => ToString();
set => _key = value;
}
/// <summary>
/// 緩存鍵前綴
/// </summary>
public string Prefix { get; set; }
/// <summary>
/// 獲取緩存鍵
/// </summary>
public override string ToString() {
return $"{Prefix}{_key}";
}
}
CacheAttribute 緩存攔截器
[Cache] 緩存攔截器提供了緩存操作的快捷方式.
/// <summary>
/// 緩存攔截器
/// </summary>
public class CacheAttribute : InterceptorBase {
/// <summary>
/// 緩存鍵前綴,可使用占位符, {0} 表示第一個(gè)參數(shù)值,范例: User-{0}
/// </summary>
public string Prefix { get; set; }
/// <summary>
/// 緩存過(guò)期間隔,單位:秒,默認(rèn)值:36000
/// </summary>
public int Expiration { get; set; } = 36000;
/// <summary>
/// 執(zhí)行
/// </summary>
public override async Task Invoke( AspectContext context, AspectDelegate next ) {
var cache = GetCache( context );
var returnType = GetReturnType( context );
var key = CreateCacheKey( context );
var value = await GetCacheValue( cache, returnType, key );
if( value != null ) {
SetReturnValue( context, returnType, value );
return;
}
await next( context );
await SetCache( context, cache, key );
}
/// <summary>
/// 獲取緩存服務(wù)
/// </summary>
protected virtual ICache GetCache( AspectContext context ) {
return context.ServiceProvider.GetService<ICache>();
}
/// <summary>
/// 獲取返回類型
/// </summary>
private Type GetReturnType( AspectContext context ) {
return context.IsAsync() ? context.ServiceMethod.ReturnType.GetGenericArguments().First() : context.ServiceMethod.ReturnType;
}
/// <summary>
/// 創(chuàng)建緩存鍵
/// </summary>
private string CreateCacheKey( AspectContext context ) {
var keyGenerator = context.ServiceProvider.GetService<ICacheKeyGenerator>();
return keyGenerator.CreateCacheKey( context.ServiceMethod, context.Parameters, GetPrefix( context ) );
}
/// <summary>
/// 獲取緩存鍵前綴
/// </summary>
private string GetPrefix( AspectContext context ) {
try {
return string.Format( Prefix, context.Parameters.ToArray() );
}
catch {
return Prefix;
}
}
/// <summary>
/// 獲取緩存值
/// </summary>
private async Task<object> GetCacheValue( ICache cache, Type returnType, string key ) {
return await cache.GetAsync( key, returnType );
}
/// <summary>
/// 設(shè)置返回值
/// </summary>
private void SetReturnValue( AspectContext context, Type returnType, object value ) {
if( context.IsAsync() ) {
context.ReturnValue = typeof( Task ).GetMethods()
.First( p => p.Name == "FromResult" && p.ContainsGenericParameters )
.MakeGenericMethod( returnType ).Invoke( null, new[] { value } );
return;
}
context.ReturnValue = value;
}
/// <summary>
/// 設(shè)置緩存
/// </summary>
private async Task SetCache( AspectContext context, ICache cache, string key ) {
var options = new CacheOptions { Expiration = TimeSpan.FromSeconds( Expiration ) };
var returnValue = context.IsAsync() ? await context.UnwrapAsyncReturnValue() : context.ReturnValue;
await cache.SetAsync( key, returnValue, options );
}
}
LocalCacheAttribute 本地緩存攔截器
/// <summary>
/// 本地緩存攔截器
/// </summary>
public class LocalCacheAttribute : CacheAttribute {
/// <summary>
/// 獲取緩存服務(wù)
/// </summary>
protected override ICache GetCache( AspectContext context ) {
return context.ServiceProvider.GetService<ILocalCache>();
}
}
RedisCacheAttribute Redis緩存攔截器
/// <summary>
/// Redis緩存攔截器
/// </summary>
public class RedisCacheAttribute : CacheAttribute {
/// <summary>
/// 獲取緩存服務(wù)
/// </summary>
protected override ICache GetCache( AspectContext context ) {
return context.ServiceProvider.GetService<IRedisCache>();
}
}
總結(jié)
以上是生活随笔為你收集整理的Util应用框架基础(七) - 缓存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 南京鼓楼医院可以做第三代试管婴儿吗?价格
- 下一篇: 释放5G红利,必须找到更多“杀手级”应用