目录
前言
IDistributedCache 接口是一个抽象接口,用于通过各种后端实现缓存数据,如内存、Redis或SQL Server。
但是,在使用 IDistributedCache 时需要注意一个问题,即缓存键的命名。
缓存键是标识缓存数据的唯一字符串,它们决定缓存数据的存储位置和检索方法。如果不同的缓存数据使用相同的缓存键,可能会导致数据被覆盖或混淆。
为了避免这些问题,一种常见的做法是使用前缀来区分不同来源的缓存数据。使用前缀,我们可以将缓存键组合成以下格式:
{Prefix}:{Key}
例如:
-
app1:user123 表示 App1 中 ID 为 123 的用户信息。
-
app2:user123 表示 App2 中 ID 为 123 的用户信息。
但是,使用前缀来命名缓存键并不是一件容易的事情。因为 IDistributedCache 接口本身并不提供任何支持前缀的方法或属性。我们只能手动拼接前缀和键值,然后传递给 IDistributedCache 接口。
这样做有以下几个问题:
-
代码冗余:我们需要在每次使用 IDistributedCache 接口时都手动拼接前缀和键值,这会增加代码量和出错概率。
-
代码分散:我们需要在应用程序中多处定义和使用前缀和键值,这会导致代码分散和难以维护。
-
代码耦合:我们需要直接依赖于 IDistributedCache 接口和具体的前缀和键值,这会导致代码耦合和难以测试。
那么,有没有更好的解决方案呢?
解决方案
为了解决这些问题,我们可以使用一种叫做装饰器模式(Decorator Pattern)的设计模式来扩展IDistributedCache接口。
装饰器模式是一种结构型设计模式,它可以在不修改原有对象的情况下,动态地给对象添加新的功能或行为。装饰器模式通常由以下几个角色组成:
-
抽象组件(Component):定义一个对象的接口,用于给对象动态地添加功能或行为。
-
具体组件(Concrete Component):实现抽象组件的接口,表示被装饰的原始对象。
-
抽象装饰器(Decorator):继承或实现抽象组件的接口,表示装饰器的基类,包含一个抽象组件的引用,用于调用原始对象的方法。
-
具体装饰器(Concrete Decorator):继承或实现抽象装饰器的接口,表示具体的装饰器,可以在调用原始对象的方法之前或之后添加新的功能或行为。
抽象组件
IDistributedCache 接口提供以下方法来处理分布式缓存实现中的项:
-
Get、GetAsync:如果在缓存中找到,则接受字符串键并以 byte[] 数组的形式检索缓存项。
-
Set、SetAsync:使用字符串键将项(作为 byte[] 数组)添加到缓存。
-
Refresh、RefreshAsync:根据键刷新缓存中的项,重置其可调到期超时(如果有)。
-
Remove、RemoveAsync:根据字符串键删除缓存项。
具体组件
这里使用的是分布式 Redis 缓存(RedisCache) 作为示例。
分布式 Redis 缓存是框架提供的 IDistributedCache 实现,位于 Microsoft.Extensions.Caching.StackExchangeRedis NuGet 包中。
抽象装饰器
实现代码如下:
public abstract class DistributedCacheDecorator : IDistributedCache
{
protected readonly IDistributedCache _innerCache;
protected DistributedCacheDecorator(IDistributedCache innerCache)
{
_innerCache = innerCache;
}
public virtual byte[] Get(string key)
{
return _innerCache.Get(key);
}
public virtual Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
return _innerCache.GetAsync(key, token);
}
public virtual void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
_innerCache.Set(key, value, options);
}
public virtual Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
return _innerCache.SetAsync(key, value, options, token);
}
public virtual void Refresh(string key)
{
_innerCache.Refresh(key);
}
public virtual Task RefreshAsync(string key, CancellationToken token = default)
{
return _innerCache.RefreshAsync(key, token);
}
public virtual void Remove(string key)
{
_innerCache.Remove(key);
}
public virtual Task RemoveAsync(string key, CancellationToken token = default)
{
return _innerCache.RemoveAsync(key, token);
}
}
具体装饰器
实现代码如下:
public class PrefixDistributedCache : DistributedCacheDecorator
{
private readonly string _prefix;
public PrefixDistributedCache(IDistributedCache innerCache, string prefix) : base(innerCache)
{
_prefix = prefix;
}
private string AddPrefix(string key)
{
return $"{_prefix}:{key}";
}
public override byte[] Get(string key)
{
return base.Get(AddPrefix(key));
}
public override Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
return base.GetAsync(AddPrefix(key), token);
}
public override void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
base.Set(AddPrefix(key), value, options);
}
public override Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
return base.SetAsync(AddPrefix(key), value, options, token);
}
public override void Refresh(string key)
{
base.Refresh(AddPrefix(key));
}
public override Task RefreshAsync(string key, CancellationToken token = default)
{
return base.RefreshAsync(AddPrefix(key), token);
}
public override void Remove(string key)
{
base.Remove(AddPrefix(key));
}
public override Task RemoveAsync(string key, CancellationToken token = default)
{
return base.RemoveAsync(AddPrefix(key), token);
}
}
使用
首先,创建一个新的扩展方法来注册我们的实现。代码如下:
public static class PrefixDistributedCacheExtentions
{
public static IServiceCollection AddPrefixDistributedCache(this IServiceCollection services, string prefix)
{
return services.AddSingleton<IDistributedCache>(x =>
{
var prefixDistributedCache = new PrefixDistributedCache(new MemoryDistributedCache(null), prefix);
return prefixDistributedCache;
});
}
}
然后,就可以在我们的项目中使用了:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPrefixDistributedCache("myio-demo");
var app = builder.Build();
app.MapGet("/cache/{key}/{value}", (IDistributedCache cache, string key,string value) => cache.SetString(key,value));
app.Run();
访问 API 后,让我们看一下redis数据库,它应该包含带有"myio-demo"前缀的缓存键:
总结
在本文中,我们学习了如何使用装饰器模式来扩展IDistributedCache接口,以便在不修改原有对象的情况下,动态地给对象添加新的功能或行为。