ABP Framework-Cache源码解析
目录
https://abp.io/docs/6.0/Caching
Version
6.0.3
Package
Volo.Abp.Caching
基于Redis的缓存包
Volo.Abp.Caching.StackExchangeRedis
Abp的缓存包作为基础模块,在Abp模板中被很多模块所依赖,因此开发中,一般不用手动加入AbpCachingModule,但对于Redis的module,需要手动加入模块依赖中。
缓存实现
ABP的缓存其本质上还是借助于Asp.Net Core的缓存接口与实现,包括IMemoryCache和IDistributedCache,从Abp.Caching的模块中可见
[DependsOn(
typeof(AbpThreadingModule),
typeof(AbpSerializationModule),
typeof(AbpUnitOfWorkModule),
typeof(AbpMultiTenancyModule),
typeof(AbpJsonModule))]
public class AbpCachingModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMemoryCache();
context.Services.AddDistributedMemoryCache();
//...
}
}
了解Asp.Net Core中的缓存
在该缓存上,ABP设计了IDistributedCache<,>, 提供了一些额外的方法扩展使用场景,如下简要列出几个方法。
public interface IDistributedCache<TCacheItem, TCacheKey>
where TCacheItem : class
{
Task<TCacheItem?> GetAsync();
Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetManyAsync();
Task<TCacheItem?> GetOrAddAsync();
Task<KeyValuePair<TCacheKey, TCacheItem?>[]> GetOrAddManyAsync();
Task SetAsync();
Task SetManyAsync();
Task RefreshAsync();
Task RefreshManyAsync();
Task RemoveAsync();
Task RemoveManyAsync();
}
该部分类图如下
IDistributedCache实则是指定了Key类型为String的IDistributedCache<TCacheItem,TCacheKey>。
public interface IDistributedCache<TCacheItem> : IDistributedCache<TCacheItem, string>
where TCacheItem : class
{
IDistributedCache<TCacheItem, string> InternalCache { get; }
}
DistributedCache内部注入DistributedCache<TCacheItem,TCacheKey>
public class DistributedCache<TCacheItem> :
IDistributedCache<TCacheItem>
where TCacheItem : class
{
public IDistributedCache<TCacheItem, string> InternalCache { get; }
public DistributedCache(IDistributedCache<TCacheItem, string> internalCache)
{
InternalCache = internalCache;
}
}
如此依赖,DistributedCache内部的所有接口最终都是通过InternalCache进入到DistributedCache<,>的方法中。如下所示
public class DistributedCache<TCacheItem> :
IDistributedCache<TCacheItem>
where TCacheItem : class
{
public IDistributedCache<TCacheItem, string> InternalCache { get; }
public TCacheItem? Get(string key, bool? hideErrors = null, bool considerUow = false)
{
return InternalCache.Get(key, hideErrors, considerUow);
}
}
所以只用关注DistributedCache<TCacheItem,TCacheKey>了
- IDistributedCache
关键部分是Microsoft.IDistributedCache,在模块服务注册中,默认加上了分布式缓存,对应实现默认为内存分布式缓存(Redis缓存一节中提及Redis分布式缓存)。以如下方法为例,执行一些处理,最终是调用Cache.Get或者Set方法;
public virtual async Task<TCacheItem?> GetOrAddAsync(
TCacheKey key,
Func<Task<TCacheItem>> factory,
Func<DistributedCacheEntryOptions>? optionsFactory = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
{
token = CancellationTokenProvider.FallbackToProvider(token);
var value = await GetAsync(key, hideErrors, considerUow, token);
if (value != null)
{
return value;
}
using (await SyncSemaphore.LockAsync(token))
{
value = await GetAsync(key, hideErrors, considerUow, token);
if (value != null)
{
return value;
}
value = await factory();
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
if (uowCache.TryGetValue(key, out var item))
{
item.SetValue(value);
}
else
{
uowCache.Add(key, new UnitOfWorkCacheItem<TCacheItem>(value));
}
}
await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token);
}
return value;
}
- IDistributedCacheKeyNormalizer
DistributedCache<TCacheItem,TCacheKey>,对于TCacheKey中的名字,默认格式如下代码中,如想要自己指定格式,可以override该方法。
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
{
var normalizedKey = $"c:{args.CacheName},k:{DistributedCacheOptions.KeyPrefix}{args.Key}";
if (!args.IgnoreMultiTenancy && CurrentTenant.Id.HasValue)
{
normalizedKey = $"t:{CurrentTenant.Id.Value},{normalizedKey}";
}
return normalizedKey;
}
在DistributedCache<TCacheItem,TCacheKey>本身的方法中,也可以重写该方法,因此想要按照TCacheKey转换期望的名字,可以有多种方式。
protected virtual string NormalizeKey(TCacheKey key)
{
return KeyNormalizer.NormalizeKey(
new DistributedCacheKeyNormalizeArgs(
key.ToString()!,
CacheName,
IgnoreMultiTenancy
)
);
}
- IDistributedCacheSerializer
提供对TCacheItem的序列化,其实现中调用IJsonSerializer,最终调用Json序列化包。如果没有特别需求,也无需扩展了,当然此处可以参照Utf8JsonDistributedCacheSerializer扩展自己的缓存序列化服务。
ICacheSupportsMultipleItems
该接口中扩展了一些批量操作的方法,简要查看如下,提供了批量查询,写入,刷新和删除等。
public interface ICacheSupportsMultipleItems
{
Task<byte[]?[]> GetManyAsync(IEnumerable<string> keys,CancellationToken token = default);
Task SetManyAsync(IEnumerable<KeyValuePair<string, byte[]>> items,DistributedCacheEntryOptions options,CancellationToken token = default);
Task RefreshManyAsync(IEnumerable<string> keys,CancellationToken token = default);
Task RemoveManyAsync(IEnumerable<string> keys,CancellationToken token = default);
}
该接口在DistributedCache<TCacheItem,TCacheKey>中,先进行判断是否有继承该类型,没有的情况下,走SetManyFallbackAsync方法其内部循环调用Cache.Set方法。有则调用cacheSupportsMultipleItems中的方法。该接口在AbpRedisCache中有使用到。
public virtual async Task SetManyAsync(
IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
DistributedCacheEntryOptions? options = null,
bool? hideErrors = null,
bool considerUow = false,
CancellationToken token = default)
{
var itemsArray = items.ToArray();
var cacheSupportsMultipleItems = Cache as ICacheSupportsMultipleItems;
if (cacheSupportsMultipleItems == null)
{
await SetManyFallbackAsync(
itemsArray,
options,
hideErrors,
considerUow,
token
);
return;
}
async Task SetRealCache()
{
hideErrors = hideErrors ?? _distributedCacheOption.HideErrors;
try
{
await cacheSupportsMultipleItems.SetManyAsync(
ToRawCacheItems(itemsArray),
options ?? DefaultCacheOptions,
CancellationTokenProvider.FallbackToProvider(token)
);
}
catch (Exception ex)
{
if (hideErrors == true)
{
await HandleExceptionAsync(ex);
return;
}
throw;
}
}
if (ShouldConsiderUow(considerUow))
{
var uowCache = GetUnitOfWorkCache();
foreach (var pair in itemsArray)
{
if (uowCache.TryGetValue(pair.Key, out _))
{
uowCache[pair.Key].SetValue(pair.Value);
}
else
{
uowCache.Add(pair.Key, new UnitOfWorkCacheItem<TCacheItem>(pair.Value));
}
}
UnitOfWorkManager.Current?.OnCompleted(SetRealCache);
}
else
{
await SetRealCache();
}
}
Redis缓存
在Abp.Redis包中,并没有对IDistributedCache<TCacheItem,TCacheKey>做扩展,实则是在Microsoft.IDistributedCache对应的RedisCache下做的实现。
public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{
}
该模块中服务注册时,会调用AddStackExchangRedisCache配置Redis参数,默认取参数格式为,取值格式固定,没有提供自定义扩展。
{
"Redis":{
"IsEnabled":false,
"Configuration":""
}
}
在AbpRedisCache中,实现了ICacheSupportsMultipleItems中的批量方法,而对于基础的单个方法如GetAsync,SetAsync,则由StackExchangeRedis.RedisCache中提供。
因此,这个模块存在的意义并不是很大,如果想要使用Redis缓存,可以直接调用AddStackExchangRedisCache配置即可,因为从IDistributedCache<TCacheItem,TCacheKey>调用Microsoft.IDistributedCache对应的实现则是RedisCache,再使用其中的GetAsync,SetAsync即可。该模块更多的是提供了批量操作。
再次说明Volo.Abp.Caching.StackExchangeRedis包并不是直接扩展IDistributedCache<TCacheItem,TCacheKey>,而是对其内部依赖的IDistributedCache的实现RedisCache进行扩展从而由AbpRedisCache,这个扩展方式不同于其他扩展组件。
扩展
为了指定key名或是替换默认key名实现,可有多种方式,
一种是override在IDistributedCacheKeyNormalizer中的方法,或者写一个同等服务替换默认实现。
还可以override在DistributedCache<TCacheItem,TCacheKey>中的NormalizeKey,直接写期望的格式。
如果想要按照项目中Redis参数配置的格式配置StackExchangeRedis,那么可以直接服务注册中加入AddStackExchangeRedisCache,不需要使用到Abp.Redis包。
2024-09-16,望技术有成后能回来看见自己的脚步。