加速应用程序的一种常见方法是引入缓存。通常,首先想到的选项是使用 MemoryCache (RAM) 来保存一些数据,以加快检索速度。
此方法适用于单体式应用程序。但是,在微服务解决方案中,每个服务都可以独立扩展,使用本地缓存可能会破坏微服务架构模式的无状态规则。
因此,更好的解决方案是使用分布式缓存。在 .NET 中,与分布式缓存的常见交互是通过称为 的接口。
在 .NET 应用程序中工作时,可以选择要使用的实现。
.NET 8 中的可用实现包括:
最常用的实现之一是 Redis。在本文的后续部分中,我将使用 Redis 进行实现。
有关详细信息,请参阅官方文档:
使用分布式缓存时,需要了解两个重要参数:
有关详细信息,请参阅官方文档:
我们可以在应用程序中使用的一种方法是在需要时直接与分布式缓存进行交互。
例如,如果服务需要缓存,我们将请求 的实例。这种方法可行,但可能不是最佳方法,因为它可能导致重复操作。
更可取的方法是为缓存交互创建专用服务,并在整个应用程序中使用它。这样可以集中管理缓存,并有助于避免冗余并提高可维护性。
这里有一个示例项目,我准备了一个缓存服务的样本:
该项目具有以下结构:
以下是您将在 GitHub 上找到的 docker-compose 文件的内容:
version: '3'
services:
redis-monitoring:
image: redislabs/redisinsight:latest
pull_policy: always
ports:
- '8001:8001'
restart: unless-stopped
networks:
- default
redis:
image: redis:latest
pull_policy: always
ports:
- "6379:6379"
restart: unless-stopped
networks:
- default
networks:
default:
driver: bridge
如您所见,compose 文件配置了两个服务:
此 Docker Compose 设置仅用于开发或测试目的。请注意,Redis 已更改其许可策略。
您可以在这篇前篇文章中找到更多信息:
API 项目公开了一些与 交互的方法。
以下是启动应用程序时将显示的内容的示例:
以下是 API 背后的代码:
app.MapGet("/GetOrCreateAsync/{key}", async (string key,ICacheService cache) =>
{
return await cache.GetOrCreateAsync(key, () => Task.FromResult($"{nameof(cache.GetOrCreateAsync)} - Hello World"));
})
.WithName("GetOrCreateAsync")
.WithOpenApi();
app.MapGet("/GetOrDefault/{key}", async (string key, ICacheService cache) =>
{
return await cache.GetOrDefaultAsync(key, $"{nameof(cache.GetOrDefault)} - Hello World");
})
.WithName("GetOrDefault")
.WithOpenApi();
app.MapGet("/CreateAndSet/{key}", async (string key, ICacheService cache) =>
{
await cache.CreateAndSet(key, $"{nameof(cache.CreateAndSet)} - Hello World");
})
.WithName("CreateAndSet")
.WithOpenApi();
app.MapDelete("/RemoveAsync", (string key, ICacheService cache) =>
{
cache.RemoveAsync(key);
})
.WithName("RemoveAsync")
.WithOpenApi();
如果未提供 Redis 配置,则该服务配置为使用内存中实现。
在API项目中,只需要使用扩展即可。
builder.Services.AddServiceCache(builder.Configuration);
扩展的详细信息如下:
public static IServiceCollection AddServiceCache(
this IServiceCollection services,
IConfiguration configuration
)
{
services
.AddOptions<CacheOptions>()
.Bind(configuration.GetSection("Cache"))
.ValidateDataAnnotations();
if (!string.IsNullOrEmpty(configuration.GetSection("RedisCache:Configuration").Value))
{
services.AddStackExchangeRedisCache(options =>
{
configuration.Bind("RedisCache", options);
});
}
else
{
services.AddDistributedMemoryCache();
}
services.AddTransient<ICacheService, CacheService>();
return services;
}
如您所见,它在应用程序设置中搜索名为“Cache”的键,以尝试初始化对象。此选项包含全局 Sliding Expiration 值的值。
这是接口:CacheService
public interface ICacheService
{
Task CreateAndSet<T>(string key, T thing, int expirationMinutes = 0)
where T : class;
Task<T> CreateAndSetAsync<T>(string key, Func<Task<T>> createAsync, int expirationMinutes = 0);
Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> create, int expirationMinutes = 0);
Task<T> GetOrDefault<T>(string key);
Task<T> GetOrDefaultAsync<T>(string key, T defaultVal);
Task RemoveAsync(string key);
}
它提供了一些实用方法来与 进行交互。
特别是,它提供了不同的方法来检索数据并设置自动默认值或创建方法。
让我们看一下示例:
public async Task<T> GetOrCreateAsync<T>(
string key,
Func<Task<T>> create,
int expirationMinutes = 0
)
{
var bytesResult = await _cache.GetAsync(key);
if (bytesResult?.Length > 0)
{
using StreamReader reader = new(new MemoryStream(bytesResult));
using JsonTextReader jsonReader = new(reader);
JsonSerializer ser = new();
ser.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
ser.TypeNameHandling = TypeNameHandling.All;
ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;
var result = ser.Deserialize<T>(jsonReader);
if (result != null)
{
return result;
}
}
return await this.CreateAndSetAsync<T>(key, create, expirationMinutes);
}
GetOrDefault另一方面,不会尝试初始化分布式缓存。如果找不到键,它只会返回默认值。
public async Task<T> GetOrDefault<T>(string key)
{
var bytesResult = await _cache.GetAsync(key);
if (bytesResult?.Length > 0)
{
using StreamReader reader = new(new MemoryStream(bytesResult));
using JsonTextReader jsonReader = new(reader);
JsonSerializer ser = new();
ser.TypeNameHandling = TypeNameHandling.All;
ser.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;
var result = ser.Deserialize<T>(jsonReader);
if (result != null)
{
return result;
}
}
return default;
}
创建服务以管理分布式缓存的方法对于避免代码重复和具有抽象层非常有用。这可能适用于简单的方案,或者如果您希望完全控制代码库或限制外部影响。
对于更复杂的场景,您可以使用的一个很酷的库是 FusionCache,它本质上是类固醇上的服务缓存,提供高级弹性功能和可选的分布式二级缓存。
源代码获取:公众号回复消息【code:72610
】