The asp.net core server-side caching component implemented based on ResourceFilter and ActionFilter;
基于ResourceFilter和ActionFilter实现的asp.net core服务端缓存组件
- 针对
Action的缓存实现,基于Filter,非中间件/AOP实现; - 命中缓存时直接将内容写入响应流,省略了序列化、反序列化等操作;
- 只会缓存响应内容和
ContentType,忽略了其它响应Header; - 支持基于
QueryKey、FormKey、Header、Claim、Model中单个或多个组合的缓存键生成; - 已实现基于
Memory和Redis(StackExchange.Redis)的缓存,可拓展; - 默认缓存Key生成器会包含请求路径为缓存Key;
- 默认缓存Key是大小写不敏感(强制转换为小写)的;
Asp.net Core版本要求 -6.0以上;Diagnostics支持;- 执行流程概览;
Install-Package Cuture.AspNetCore.ResponseCachingpublic void ConfigureServices(IServiceCollection services)
{
//....Other Settings
services.AddCaching(options =>
{
options.Enable = true; //是否启用响应缓存
options.DefaultCacheStoreLocation = CacheStoreLocation.Memory; //默认缓存数据存储位置 - Memory
options.DefaultExecutingLockMode = ExecutingLockMode.None; //默认执行锁定模式 - 不锁定
options.DefaultStrictMode = CacheKeyStrictMode.Ignore; //默认缓存Key的严格模式 - 忽略没有的找到的Key
options.LockedExecutionLocalResultCache = new MemoryCache(new MemoryCacheOptions()); //锁定执行时,响应的本地缓存
options.MaxCacheableResponseLength = 1024 * 1024; //默认最大可缓存的响应内容长度
options.MaxCacheKeyLength = 1024; //最大缓存Key长度
options.OnCannotExecutionThroughLock = (cacheKey, filterContext, next) => Task.CompletedTask; //无法使用锁执行请求时(Semaphore池用尽)的回调
options.OnExecutionLockTimeoutFallback = (cacheKey, filterContext, next) => Task.CompletedTask; //执行锁定超时后的处理委托
});
//以下为可选配置
// 锁定执行的相关配置
services.PostConfigure<ResponseCachingExecutingLockOptions>(options =>
{
options.MinimumSemaphoreRetained = 50; //信号池的最小保留大小
options.MaximumSemaphorePooled = 1000; //信号池的最大大小
options.MinimumExecutingLockRetained = 50; //执行锁池的最小保留大小
options.MaximumExecutingLockPooled = 1000; //执行锁池的最大大小
options.SemaphoreRecycleInterval = TimeSpan.FromMinutes(4); //信号池的回收间隔
options.ExecutingLockRecycleInterval = TimeSpan.FromMinutes(2); //执行锁池的回收间隔
});
}ResponseCachingMetadata为派生自IResponseCachingMetadata的Attribute,用于描述响应缓存的配置等细节;ResponseCacheable为内部实现的Attribute,用于使用Endpoint获取对应Action的ResponseCachingMetadata并动态构建Filter;
IResponseCachingMetadata接口及内置的ResponseCachingMetadata列表:
| 接口 | 描述内容 | 内置实现 |
|---|---|---|
IResponseClaimCachePatternMetadata |
创建缓存时依据的 Claim 类型 | ResponseCachingAttribute |
IResponseFormCachePatternMetadata |
创建缓存时依据的 Form 键 | ResponseCachingAttribute |
IResponseHeaderCachePatternMetadata |
创建缓存时依据的 Header 键 | ResponseCachingAttribute |
IResponseModelCachePatternMetadata |
创建缓存时依据的 Model 参数名 | ResponseCachingAttribute |
IResponseQueryCachePatternMetadata |
创建缓存时依据的 Query 键 | ResponseCachingAttribute |
ICacheKeyGeneratorMetadata |
用于生成缓存Key的ICacheKeyGenerator实现类型 |
CacheKeyGeneratorAttribute |
ICacheKeyStrictModeMetadata |
缓存键严格模式 | ResponseCachingAttribute |
ICacheModelKeyParserMetadata |
用于生成Model的缓存Key的IModelKeyParser实现类型 |
CacheModelKeyParserAttribute |
ICacheModeMetadata |
缓存模式 | ResponseCachingAttribute |
ICacheStoreLocationMetadata |
缓存数据存储位置 | ResponseCachingAttribute |
IDumpStreamInitialCapacityMetadata |
Dump响应的Stream初始容量 | ResponseDumpCapacityAttribute |
IExecutingLockMetadata |
Action的执行锁定方式 |
ExecutingLockAttribute |
IHotDataCacheMetadata |
热点数据缓存方式 | HotDataCacheAttribute |
IMaxCacheableResponseLengthMetadata |
最大可缓存响应长度 | ResponseCachingAttribute |
IResponseDurationMetadata |
缓存时长 | ResponseCachingAttribute |
使用[ResponseCaching]标记需要缓存响应的Action,或者使用简便标记[CacheByQuery]、[CacheByForm]、[CacheByHeader]、[CacheByClaim]、[CacheByModel]、[CacheByPath]、[CacheByFullUrl] (这些标记都是继承自ResponseCaching并进行了简单的预设置);
[ResponseCaching(
60, //缓存时长(秒)
Mode = CacheMode.Custom, //设置缓存模式 - 自定义缓存Key生成
VaryByClaims = new[] { "id" }, //依据Claim中的`id`进行构建缓存Key
VaryByHeaders = new[] { "version" }, //依据Header中的`version`进行构建缓存Key
VaryByQueryKeys = new[] { "page", "pageSize" }, //依据Query中的`page`和`pageSize`进行构建缓存Key
VaryByModels = new[] { "input" }, //依据Model中的`input`进行构建缓存Key
StoreLocation = CacheStoreLocation.Memory, //设置缓存数据存储位置 - Memory
StrictMode = CacheKeyStrictMode.Ignore, //缓存Key的严格模式 - 忽略没有的找到的Key
MaxCacheableResponseLength = 1024 * 1024 //最大可缓存的响应内容长度
)]
[CacheKeyGenerator(typeof(CustomCacheKeyGenerator), FilterType.Resource)] //使用 CustomCacheKeyGenerator 作为缓存key生成器
[CacheModelKeyParser(typeof(CustomModelKeyParser))] //使用 CustomModelKeyParser 作为model的key解析器
[ExecutingLock(ExecutingLockMode.CacheKeySingle)] //执行action时锁定执行过程,锁定粒度为每个缓存Key,不允许并行执行
[HotDataCache(50, HotDataCachePolicy.LRU)] //将热点数据缓存在内存中,淘汰算法为LRU
[ResponseDumpCapacity(1024 * 1024)] //指定dump响应的stream初始化容量,减少不必要的扩容
public IEnumerable<DataDto> Foo([FromQuery] int page, [FromQuery] int pageSize, [FromBody] RequestDto input)
{
//...action logic
}- 自定义
Attribue,继承继承ResponseCacheableAttribute(或继承IFilterFactory自行实现构建逻辑); - 自定义
Attribue,继承需要设置的IResponseCachingMetadata接口; - 使用上述自定义特性标记需要缓存的
Action方法;
- 可以使用多个
Attribute分别实现IResponseCachingMetadata,也可以将所有的功能在一个Attribute实现;
不配置时将默认使用MemoryCache进行缓存
Install-Package Cuture.AspNetCore.ResponseCaching.StackExchange.Redispublic void ConfigureServices(IServiceCollection services)
{
//....Other Settings
services.AddCaching()
.UseRedisResponseCache("redis:6379", //redis配置字符串
"ResponseCache_" //缓存前缀
);
}- 当前只有两个拦截器
- 缓存存储拦截器:
ICacheStoringInterceptor - 缓存写入拦截器:
IResponseWritingInterceptor
- 缓存存储拦截器:
- 拦截器可以有多个;
- 拦截器执行顺序为
全局Service拦截器->全局Instance拦截器->Attribute拦截器;
- 继承需要拦截的流程接口即可;
public class IgnoreHelloCacheStoringInterceptor
: Attribute //继承Attribute,以进行单个Action的设置
, ICacheStoringInterceptor //响应存储拦截器
{
public async Task<ResponseCacheEntry?> OnCacheStoringAsync(ActionContext actionContext, string key, ResponseCacheEntry entry, OnCacheStoringDelegate<ActionContext> next)
{
if (key == "hello")
{
return null; //key为hello时,不进行存储,且不进行后续的处理
}
return await next(actionContext, key, entry); //执行后续处理
}
}- 全局生效的拦截器
public void ConfigureServices(IServiceCollection services)
{
//....Other Settings
services.AddCaching().ConfigureInterceptor(options =>
{
//Instance拦截器
options.AddInterceptor(new CacheHitStampInterceptor("key", "value")); //添加拦截器 CacheHitStampInterceptor 的实例作为全局拦截器
//Service拦截器
options.AddServiceInterceptor<CustomInterceptor>(); //从DI中获取 CustomInterceptor 作为全局拦截器
});
}- 将拦截器
Attribute设置到对应的Action方法即可,此时拦截器只针对当前Action生效
- 此拦截器将会在命中缓存时向响应的
HttpHeader中添加指定内容 - 此设置可能因为前置拦截器短路而不执行
配置方法:
public void ConfigureServices(IServiceCollection services)
{
//....Other Settings
services.AddCaching().UseCacheHitStampHeader("cached", "1"); //当命中缓存时,响应的Header中将附加`cached: 1`
}使用query中的page和pageSize缓存
[HttpGet]
[CacheByQuery(60, "page", "pageSize")]
public ResultDto Foo(int page, int pageSize)
{
//...action logic
}使用form中的page和pageSize缓存
[HttpGet]
[CacheByForm(60, "page", "pageSize")]
public ResultDto Foo()
{
int page = int.Parse(Request.Form["page"]);
int pageSize = int.Parse(Request.Form["pageSize"]);
//...action logic
}使用action的参数进行缓存
[HttpPost]
[CacheByModel(60)]
public ResultDto Foo(RequestDto input)
{
//...action logic
}
public class RequestDto : ICacheKeyable
{
public int Page { get; set; }
public int PageSize { get; set; }
public string AsCacheKey() => $"{Page}_{PageSize}";
}使用Model缓存会使用以下方式的其中一种生成Key(优先级从上往下)
- 指定
ModelKeyParserType - Model类实现
ICacheKeyable接口 - Model类的
ToString方法
使用用户凭据中的id和query中的page与pageSize组合构建缓存Key
[ResponseCaching(60,
Mode = CacheMode.Custom,
VaryByClaims = new[] { "id" },
VaryByQueryKeys = new[] { "page", "pageSize" })]
public ResultDto Foo(int page, int pageSize)
{
//...action logic
}实现IMemoryResponseCache或IDistributedResponseCache接口;并将实现注入asp.net core的DI,替换掉默认实现;
- 部分功能已使用
Diagnostic,DiagnosticName为Cuture.AspNetCore.ResponseCaching;
事件列表如下:
| 事件名称 | 事件 |
|---|---|
| Cuture.AspNetCore.ResponseCaching.StartProcessingCache | 开始处理缓存 |
| Cuture.AspNetCore.ResponseCaching.EndProcessingCache | 处理缓存结束 |
| Cuture.AspNetCore.ResponseCaching.CacheKeyGenerated | 缓存key已生成 |
| Cuture.AspNetCore.ResponseCaching.ResponseFromCache | 从缓存响应请求 |
| Cuture.AspNetCore.ResponseCaching.ResponseFromActionResult | 使用IActionResult响应请求事件 |
| Cuture.AspNetCore.ResponseCaching.CacheKeyTooLong | 缓存键过长 |
| Cuture.AspNetCore.ResponseCaching.NoCachingFounded | 没有找到缓存 |
| Cuture.AspNetCore.ResponseCaching.CacheBodyTooLong | 缓存内容过大 |
- 已实现简单的
Diagnostic订阅并打印,直接启用即可输出日志; - 使用
Diagnostic会对性能有那么一点影响;
services.AddCaching()
.AddDiagnosticDebugLogger() //在Debug模式下使用Logger输出Diagnostic事件信息
.AddDiagnosticReleaseLogger(); //在Release模式下使用Logger输出Diagnostic事件信息app.EnableResponseCachingDiagnosticLogger();如上配置后,内部将订阅相关Diagnostic,并将事件信息使用ILogger输出。
services.AddCaching()
.EnableCacheKeyAccessor(); //启用 CacheKeyAccessor ,从DI中获取 ICacheKeyAccessor 以访问当前请求的 `cache key`