Demonstração do Hybrid Caching no .NET 10 — o melhor dos dois mundos
Projeto de demonstração que combina cache em memória (L1) com cache distribuído (L2) usando a nova API HybridCache do .NET 10. O objetivo é reduzir latência, aliviar o cache distribuído e simplificar o código de stampede prevention com uma única abstração.
O HybridCache no .NET 10 unifica IMemoryCache e IDistributedCache em uma única API coesa, oferecendo:
- ✅ Busca primeiro no cache local (in-process), depois no distribuído
- ✅ Prevenção nativa de cache stampede (sem código extra)
- ✅ TTLs independentes para L1 e L2
- ✅ Invalidação por chave ou por tags
- ✅ Suporte a serialização customizada
Request
│
├─► [L1] In-Memory (IMemoryCache)
│ └── HIT → retorna (~µs, sem serialização)
│ └── MISS ↓
│
├─► [L2] Distribuído (Redis)
│ └── HIT → preenche L1 e retorna
│ └── MISS ↓
│
└─► Factory (banco de dados / API externa)
└── Resultado armazenado em L1 + L2
| Camada | Tecnologia | TTL padrão | Escopo |
|---|---|---|---|
| L1 | IMemoryCache (in-process) |
30 segundos | Por instância |
| L2 | Redis 7+ | 5 minutos | Compartilhado entre pods |
- .NET 10 SDK (preview ou superior)
- Docker (para Redis local)
- Redis 7+ ou Azure Cache for Redis
- Visual Studio 2022 / JetBrains Rider / VS Code
docker run -d -p 6379:6379 --name redis-local redis:7-alpineOu usando o docker-compose.yml do projeto:
docker compose up -ddotnet restore
dotnet run --project src/Caching.WebApihttps://localhost:5001/swagger/index.html
dotnet-hybrid-cache/
├── src/
│ ├── Caching.WebApi/ # Endpoints, Program.cs e configuração
│ ├── Caching.Domain/ # Serviços de domínio e interfaces
│ └── Caching.Infra.Cache/ # Redis, repositórios e serialização
├── test/
│ └── Caching.UnitTest/ # Testes de integração e benchmark
├── docker-compose.yml
├── .editorconfig
└── README.md
{
"ConnectionStrings": {
"Redis": [
{
"Name": "Caching",
"ConnectionString": "127.0.0.1:6379,defaultDatabase=0"
}
]
}
}// Startup.cs
// L2: Redis distribuído
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = settings.ConnectionStrings.Redis.FirstOrDefault(c => c.Name.Equals("Caching"))?.ConnectionString;
});
// HybridCache — combina L1 + L2 automaticamente
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5), // TTL no L2 (Redis)
LocalCacheExpiration = TimeSpan.FromSeconds(30) // TTL no L1 (memória)
};
});public class ProductService(HybridCache cache, IProductRepository repo)
{
public async Task<Product?> GetByIdAsync(
int id, CancellationToken ct = default)
{
return await cache.GetOrCreateAsync(
key: $"product:{id}",
factory: async token =>
await repo.FindAsync(id, token),
cancellationToken: ct
);
}
}public async Task InvalidateAsync(int id)
=> await cache.RemoveAsync($"product:{id}");// Armazenar com tags
await cache.GetOrCreateAsync(
key: $"product:{id}",
factory: async token => await repo.FindAsync(id, token),
options: new HybridCacheEntryOptions { Tags = ["products"] },
cancellationToken: ct
);
// Invalidar todos os itens com a tag "products"
await cache.RemoveByTagAsync("products");dotnet test tests/HybridCacheDemo.TestsOs testes de integração usam TestContainers para provisionar um Redis real em Docker durante a execução.
| Cenário | Antes (manual) | Depois (HybridCache) |
|---|---|---|
| Código de cache | ~40 linhas por serviço | 5 linhas |
| Stampede prevention | Implementação manual | Nativo |
| Invalidação distribuída | Pub/Sub manual | RemoveAsync / RemoveByTagAsync |
| TTL independente L1/L2 | Não suportado | LocalCacheExpiration |
| Serialização | Configuração separada | Centralizada |
- What's new in .NET 10 — HybridCache
- Microsoft.Extensions.Caching.Hybrid NuGet
- ASP.NET Core Caching Overview
- StackExchange.Redis
Distribuído sob a licença MIT. Consulte o arquivo LICENSE para mais detalhes.