Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 22 de octubre de 2024
Imagen decorativa sugiriendo sistemas de almacenamiento en la nube

Una de las novedades más destacables de .NET 9 es, sin duda, el nuevo sistema de caché híbrida (Hybrid cache), una puesta al día del sistema de caché distribuida que nos acompaña desde las primeras versiones de .NET Core, y al que iban haciendo falta ya algunas mejoras.

Este nuevo sistema está construido encima de la infraestructura de caching existente (Microsoft.Extensions.Caching), añadiendo mejoras que hacen que su uso sea más sencillo y contemplando de serie funcionalidades que antes nos veíamos obligados a implementar manualmente.

Vamos a echarle un vistazo a sus principales características :)

Disclaimer: lo que vamos a ver a continuación está basado en .NET 9 RC2, por lo que todavía es posible que haya cambios en la versión final.

¿Qué aporta Hybrid Cache sobre IDistributedCache?

Empecemos por la que, para mi gusto, es la principal aportación de Hybrid Cache: la protección contra estampidas.

Una estampida es el efecto que puede ocurrir en sistemas que usan caché para soportar una carga muy alta de peticiones al mismo tiempo. En ellos, si la caché se invalida por algún motivo, todas las peticiones concurrentes que llegan mientras ésta no haya sido poblada de nuevo intentarán recargarla simultáneamente, dando lugar a un ataque masivo al servidor de origen que en el peor de los casos puede llevarlo al suelo.

Con la protección integrada contra estampidas, Hybrid Cache se encarga de gestionar las peticiones concurrentes, permitiendo que sólo una de ellas llegue al servidor de origen y el resto esperen a que la caché sea repoblada. Esto es una funcionalidad que antes debíamos implementar manualmente, y no siempre era trivial hacerlo bien.

Otra característica realmente interesante de Hybrid Cache es la implementación de serie de una caché multinivel, solucionando un escenario que es bastante frecuente en sistemas distribuidos: aunque usemos un mecanismo externo como Redis, Memcached u otros, es muy habitual que también tengamos una caché en memoria en el propio servidor, para evitar tener que ir a la caché externa en cada petición.

Hybrid Cache nos permite configurar fácilmente una caché de nivel 1 (L1) en memoria del servidor (usando la clase MemoryCache de siempre), y una caché de nivel 2 (L2) en un sistema externo (usando IDistributedCache), y gestionar la sincronización entre ambas de forma transparente. Es decir, desde nuestro código simplemente escribiremos o leeremos a través de Hybrid Cache, y éste se encargará de gestionar la comunicación con la caché en memoria y la externa.

Por otra parte, como recordaréis, IDisbributedCache es una interfaz muy simple que sólo nos permite almacenar y recuperar bytes, por lo que si queríamos almacenar contenidos más sofisticados como objetos o estructuras, teníamos que encargarnos de serializarlos y deserializarlos manualmente, dando lugar a mucho código repetitivo, propenso a errores, y muchas veces poco eficiente.

Hybrid Cache ha mejorado las APIs para interactuar con la caché, con el objetivo de simplificar su uso en los escenarios más frecuentes. Los métodos para almacenar y recuperar datos de la caché son más fáciles de usar, y permiten el uso directo de objetos complejos sin necesidad de serializarlos o deserializarlos manualmente.

Por defecto, Hybrid Cache serializará sin problema las secuencias de bytes (digamos que es el formato nativo), las cadenas de texto serán convertidas a bytes UTF-8, y el resto de tipos de datos se usará System.Text.Json para obtener su representación JSON, aunque si fuera necesario, podemos configurar serializadores personalizados para tipos de datos específicos. En todo caso, el payload puede ser comprimido para ahorrar espacio y minimizar los datos en circulación.

También es interesante destacar la introducción de la invalidación basada en tags, un mecanismo que permite agrupar elementos de la caché bajo una misma etiqueta, y luego invalidar todos los elementos que tengan esa etiqueta de forma sencilla. Hasta ahora, la única forma de conseguir algo parecido era implementar manualmente algún mecanismo de agrupación externo a la caché y eliminar las entradas una a una.

La reutilización de objetos permite mejorar el rendimiento en escenarios donde los objetos almacenados en la caché distribuida son grandes o se accede a ellos con mucha frecuencia. Cuando estos objetos sean inmutables y su estado no sea alterado desde el código, es posible que Hybrid Cache pueda reutilizar el objeto entre distintas peticiones concurrentes en lugar de deserializarlo de nuevo cada vez.

Uso de Hybrid Cache

La clase abstracta HybridCache, definida en el espacio de nombres Microsoft.Extensions.Caching.Distributed es la que nos dará acceso a las funcionalidades de la caché híbrida. Internamente existe una implementación concreta de esta clase llamada DefaultHybridCache, que es la que usaremos en la mayoría de los casos, aunque será transparente para nosotros porque normalmente trabajaremos con la clase abstracta.

De momento, estos nuevos tipos no vienen incluidos en los metapaquetes básicos de .NET 9, sino que se distribuye a través del paquete NuGet independiente Microsoft.Extensions.Caching.Hybrid, que es necesario instalar en los proyectos donde queramos usarlos. A día de hoy, todavía son paquetes en preview, pero en pocas semanas serán ya definitivos.

Una vez instalado, lo habitual será registrar el servicio en el contenedor de dependencias de la aplicación, para lo que usaremos el método AddHybridCache de la clase IServiceCollection. Luego, podemos reclamar instancias de HybridCache donde deseemos usar sus servicios.

Por ejemplo, la siguiente aplicación ASP.NET Core registra HybridCache y luego lo usa desde un endpoint para almacenar y recuperar la hora actual, pero actualizándose solo cada 5 segundos:

var builder = WebApplication.CreateBuilder(args);

// Agregamos los servicios de caché híbrida
builder.Services.AddHybridCache();

var app = builder.Build();

app.MapGet("/", async (HybridCache cache) =>
{
    DateTime cachedItem = await cache.GetOrCreateAsync(
        key: "currentTime",
        factory: _ => ValueTask.FromResult(DateTime.Now),
        options: new HybridCacheEntryOptions()
                {
                    LocalCacheExpiration = TimeSpan.FromSeconds(5),
                }
        );
    return cachedItem;
});

app.Run();

Pero fijaos que en el ejemplo anterior sólo estamos usando la caché de primer nivel, porque no hemos configurado la caché distribuida o de segundo nivel. Para ello, necesitamos registrar un servicio IDistributedCache en el contenedor de dependencias; HybridCache detectará automáticamente la presencia de este servicio y usará la caché de segundo nivel.

Por ejemplo, si queremos usar Redis como caché distribuida, podemos instalar el paquete NuGet Microsoft.Extensions.Caching.StackExchangeRedis y registrar el servicio de IDistributedCache en el contenedor de dependencias de forma similar a la siguiente:

var builder = WebApplication.CreateBuilder(args);

// Agregamos los servicios de caché híbrida
builder.Services.AddHybridCache();

// Agregamos los servicios de caché distribuida con Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("RedisConnectionString");
    options.InstanceName = "HybridCacheDemo";
});

var app = builder.Build();

app.MapGet("/", async (HybridCache cache) =>
{
    DateTime cachedItem = await cache.GetOrCreateAsync(
        key: "CurrentTime",
        factory: _ => ValueTask.FromResult(DateTime.Now),
        options: new HybridCacheEntryOptions()
                {
                    // Establecemos la expiración de caché local y distribuida:
                    LocalCacheExpiration = TimeSpan.FromSeconds(5),
                    Expiration = TimeSpan.FromSeconds(10)
                }
    );
    return cachedItem;
});

Obviamente, HybridCache es mucho más que lo que hemos visto aquí, pero creo que estos ejemplos son suficientes para hacernos una idea de cómo se configura y se usa. Si queréis profundizar más, os recomiendo que le echéis un vistazo a la documentación oficial de .NET 9.

¡Espero que os haya resultado interesante!

Publicado en Variable not found.

2 Comentarios:

Anónimo dijo...

¿Es factible utilizar el paquete NuGet de HybridCache desde Net 8?

José María Aguilar dijo...

Hola!

La página del paquete NuGet (https://www.nuget.org/packages/Microsoft.Extensions.Caching.Hybrid) indica que es compatible con .NET 8 e incluso NET Standard 2.0, lo que permitiría usarlo con versiones anteriores del framework 🙂

Eso sí, a día de hoy aún está en preview, dijeron que lo lanzarían algo después que la versión final de .NET 9.

Saludos!