Compartir a través de


Biblioteca HybridCache en ASP.NET Core

En este artículo se explica cómo configurar y usar la biblioteca HybridCache en una aplicación ASP.NET Core. Para obtener una introducción a la biblioteca, consulte la sección HybridCache de información general sobre el almacenamiento en caché.

Obtén la biblioteca

Instale el paquete Microsoft.Extensions.Caching.Hybrid.

dotnet add package Microsoft.Extensions.Caching.Hybrid

Registro del servicio

Agregue el servicio HybridCache al contenedor de inserción de dependencias (DI) mediante una llamada a AddHybridCache:

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthorization();

builder.Services.AddHybridCache();

El código anterior registra el servicio HybridCache con opciones predeterminadas. La API de registro también puede configurar opciones y serialización.

Obtención y almacenamiento de entradas de caché

El servicio HybridCache proporciona un método GetOrCreateAsync con dos sobrecargas, que acepta una clave y:

  • Un método de generador.
  • Estado y un método de generador.

El método usa la clave para intentar recuperar el objeto de la caché principal. Si el elemento no se encuentra en la memoria caché principal (un error en la memoria caché), entonces se comprueba la memoria caché secundaria, de haber una configurada. Si no encuentra los datos allí (otro error de caché), llama al método factory para obtener el objeto del origen de datos. A continuación, almacena el objeto en las memorias caché principal y secundaria. Nunca se llama al método factory si el objeto se encuentra en la memoria caché principal o secundaria (se alcanza una caché).

El servicio HybridCache garantiza que solo un llamador simultáneo para una clave determinada llame al método factory y todos los demás autores de llamadas esperan el resultado de esa llamada. El CancellationToken que se pasa a GetOrCreateAsync representa la cancelación combinada de todos los llamantes simultáneos.

La sobrecarga principal GetOrCreateAsync

Se recomienda la sobrecarga sin estado de GetOrCreateAsync para la mayoría de los escenarios. El código para llamarlo es relativamente sencillo. Este es un ejemplo:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Guía de clave de caché

El key pasado a GetOrCreateAsync debe identificar de forma única los datos que se almacenan en caché:

  • En términos de los valores de identificador usados para recuperar esos datos de su origen.
  • En términos de otros datos almacenados en caché en la aplicación.

Normalmente, ambos tipos de unicidad se garantizan mediante la concatenación de cadenas para crear una sola cadena de clave compuesta por diferentes partes concatenadas. Por ejemplo:

cache.GetOrCreateAsync($"/orders/{region}/{orderId}", ...);

O bien,

cache.GetOrCreateAsync($"user_prefs_{userId}", ...);

Es responsabilidad del autor de la llamada asegurarse de que un esquema de clave es válido y no puede hacer que los datos se confundan.

Evite usar la entrada de usuario externo directamente en las claves de caché. Por ejemplo, no use cadenas sin procesar de interfaces de usuario como claves de caché. Si lo hace, puede exponer la aplicación a riesgos de seguridad, como ataques de denegación de servicio o acceso no autorizado causados por la inundación de la memoria caché con claves aleatorias o sin significado. En los ejemplos válidos anteriores, los datos de preferencia de usuario y orden se separan claramente y usan identificadores de confianza:

  • Los identificadores orderid y userId se generan de forma interna.
  • region puede ser una enumeración o cadena de una lista predefinida de regiones conocidas.

No hay importancia en tokens como / o _. El valor de clave completo se trata como una cadena de identificación opaca. En este caso, podría omitir el / y _ sin ningún cambio en la forma en que las funciones de caché, pero normalmente se usa un delimitador para evitar ambigüedades; por ejemplo, $"order{customerId}{orderId}" podría provocar confusión entre:

  • customerId 42 con orderId 123
  • customerId 421 con orderId 23

Ambos ejemplos anteriores generarían la clave order42123de caché .

Esta guía se aplica igualmente a cualquier API de caché basada en string, como HybridCache, IDistributedCachey IMemoryCache.

Tenga en cuenta que la sintaxis de cadena interpolada ($"..." en los ejemplos anteriores de claves válidas) está directamente dentro de la llamada a GetOrCreateAsync. Esta sintaxis se recomienda al usar HybridCache, ya que permite mejoras futuras planeadas que omiten la necesidad de asignar un string para la clave en muchos escenarios.

Consideraciones clave adicionales

  • Las claves se pueden restringir a longitudes máximas válidas. Por ejemplo, la implementación predeterminada de HybridCache (a través de AddHybridCache(...)) restringe las claves a 1024 caracteres de forma predeterminada. Ese número se puede configurar a través de HybridCacheOptions.MaximumKeyLength, con llaves más largas que evitan los mecanismos de caché para prevenir la saturación.
  • Las claves deben ser secuencias Unicode válidas. Si se pasan secuencias Unicode no válidas, el comportamiento no está definido.
  • Cuando se usa una caché secundaria fuera del proceso, como IDistributedCache, la implementación de back-end puede imponer restricciones adicionales. Como ejemplo hipotético, un back-end determinado podría usar lógica de clave que no distingue mayúsculas de minúsculas. El valor predeterminado HybridCache (a través de AddHybridCache(...)) detecta este escenario para evitar ataques de confusión o ataques de alias (mediante la comparación de bits de las cadenas). Sin embargo, este escenario podría dar lugar a que las claves en conflicto se sobrescriban o eliminen antes de lo esperado.

La sobrecarga alternativa de GetOrCreateAsync

La sobrecarga alternativa podría reducir cierta sobrecarga de variables capturadas y devoluciones de llamada por instancia, pero a costa del código más complejo. En la mayoría de los escenarios, el aumento del rendimiento no supera la complejidad del código. Este es un ejemplo que usa la sobrecarga alternativa:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            (name, id, obj: this),
            static async (state, token) =>
            await state.obj.GetDataFromTheSourceAsync(state.name, state.id, token),
            cancellationToken: token
        );
    }

    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

El método SetAsync

En muchos escenarios, GetOrCreateAsync es la única API necesaria. Pero HybridCache también tiene SetAsync para almacenar un objeto en caché sin intentar recuperarlo primero.

Eliminación de entradas de caché por clave

Cuando los datos subyacentes de una entrada de caché cambien antes de que expiren, se quita la entrada explícitamente llamando a RemoveAsync con la clave de la entrada. Una sobrecarga permite especificar una colección de valores de clave.

Cuando se quita una entrada, se quita de las memorias caché principal y secundaria.

Eliminación de entradas de caché por etiqueta

Las etiquetas se pueden usar para agrupar entradas de caché e invalidarlas conjuntamente.

Establezca etiquetas al llamar a GetOrCreateAsync, como se muestra en el ejemplo siguiente:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Quite todas las entradas de una etiqueta especificada llamando a RemoveByTagAsync con el valor de etiqueta. Una sobrecarga permite especificar una colección de valores de etiqueta.

Ni ni IMemoryCacheIDistributedCache tiene compatibilidad directa con el concepto de etiquetas, por lo que la invalidación basada en etiquetas es solo una operación lógica . No quita activamente los valores de la caché local o distribuida. En su lugar, garantiza que los datos se tratan como un error de caché tanto de la caché local como de la caché remota al recibir datos con estas etiquetas. Los valores expiran de IMemoryCache y IDistributedCache de la manera habitual en función de la duración configurada.

Eliminación de todas las entradas de caché

La etiqueta asterisco (*) se reserva como carácter comodín y no se permite para valores individuales. La llamada RemoveByTagAsync("*") a tiene el efecto de invalidar todos losHybridCache datos, incluso los datos que no tienen ninguna etiqueta. Al igual que con las etiquetas individuales, se trata de una operación lógica y los valores individuales continúan existiendo hasta que expiran de forma natural. No se admiten coincidencias de estilo Glob. Por ejemplo, no puede usar RemoveByTagAsync("foo*") para quitar todo a partir de foo.

Consideraciones de etiquetas adicionales

  • El sistema no limita el número de etiquetas que puede usar, pero los grandes conjuntos de etiquetas podrían afectar negativamente al rendimiento.
  • Las etiquetas no pueden estar vacías, ser solo espacios en blanco, o el valor reservado *.

Opciones

El método AddHybridCache se puede usar para configurar los valores predeterminados globales. En el ejemplo siguiente se muestra cómo configurar algunas de las opciones disponibles:

// Add services to the container.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.MaximumPayloadBytes = 1024 * 1024;
        options.MaximumKeyLength = 1024;
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(5),
            LocalCacheExpiration = TimeSpan.FromMinutes(5)
        };
    });

El método GetOrCreateAsync también puede tomar un objeto HybridCacheEntryOptions para invalidar los valores predeterminados globales de una entrada de caché específica. Este es un ejemplo:

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        var tags = new List<string> { "tag1", "tag2", "tag3" };
        var entryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromMinutes(1),
            LocalCacheExpiration = TimeSpan.FromMinutes(1)
        };
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            entryOptions,
            tags,
            cancellationToken: token
        );
    }
    
    public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
    {
        string someInfo = $"someinfo-{name}-{id}";
        return someInfo;
    }
}

Para obtener más información sobre las opciones, consulte el código fuente:

Límites

Las siguientes propiedades de HybridCacheOptions permiten configurar límites que se aplican a todas las entradas de caché:

  • MaximumPayloadBytes: tamaño máximo de una entrada de caché. El valor predeterminado es 1 MB. Los intentos de almacenar valores por encima de este tamaño se registran y el valor no se almacena en la memoria caché.
  • MaximumKeyLength: longitud máxima de una clave de caché. El valor predeterminado es de 1024 caracteres. Los intentos de almacenar valores por encima de este tamaño se registran y el valor no se almacena en la memoria caché.

Serialización

Para usar una caché secundaria fuera de proceso se necesita la serialización. La serialización se configura como parte del registro del servicio HybridCache. Se pueden configurar serializadores de uso general y específicos del tipo mediante los métodos AddSerializer y AddSerializerFactory, encadenados desde la llamada a AddHybridCache. De manera predeterminada, el servicio controla internamente string y byte[], y usa System.Text.Json para todo lo demás. HybridCache también puede usar otros serializadores, como protobuf o XML.

En el ejemplo siguiente se configura el servicio para usar un serializador protobuf específico del tipo:

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
    {
        options.DefaultEntryOptions = new HybridCacheEntryOptions
        {
            Expiration = TimeSpan.FromSeconds(10),
            LocalCacheExpiration = TimeSpan.FromSeconds(5)
        };
    }).AddSerializer<SomeProtobufMessage, 
        GoogleProtobufSerializer<SomeProtobufMessage>>();

En el ejemplo siguiente se configura el servicio para usar un serializador protobuf de uso general que puede controlar muchos tipos de protobuf:

// Add services to the container.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromSeconds(10),
        LocalCacheExpiration = TimeSpan.FromSeconds(5)
    };
}).AddSerializerFactory<GoogleProtobufSerializerFactory>();

La caché secundaria requiere un almacén de datos, como Redis, SQL Server o Postgres. Para usar Azure Cache for Redis, por ejemplo:

  • Instale el paquete Microsoft.Extensions.Caching.StackExchangeRedis.

  • Cree una instancia de Azure Cache for Redis.

  • Obtenga una cadena de conexión que se conecte a la instancia de Redis. Para buscar la cadena de conexión, seleccione Mostrar claves de acceso en la página Información general de Azure Portal.

  • Almacene la cadena de conexión en la configuración de la aplicación. Por ejemplo, usa un archivo de secretos de usuario similar al siguiente JSON, con la cadena de conexión en la sección ConnectionStrings. Reemplace <the connection string> por la cadena de conexión real:

    {
      "ConnectionStrings": {
        "RedisConnectionString": "<the connection string>"
      }
    }
    
  • En la inserción de dependencias, registre la implementación de IDistributedCache que proporciona el paquete de Redis. Para ello, llame a AddStackExchangeRedisCache y pase la cadena de conexión. Por ejemplo:

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • La implementación IDistributedCache de Redis ya está disponible en el contenedor de inserción de dependencias de la aplicación. En HybridCache se usa como caché secundaria y se utiliza el serializador que se le ha configurado.

Para más información, vea la aplicación de ejemplo de serialización HybridCache.

Almacenamiento en caché

De forma predeterminada, HybridCache usa MemoryCache para su almacenamiento de caché principal. Las entradas en caché se almacenan en proceso, por lo que cada servidor tiene una caché independiente que se pierde cada vez que se reinicia el proceso del servidor. En el caso del almacenamiento fuera de proceso secundario, como Redis, SQL Server o Postgres, HybridCache usa la implementación configuradaIDistributedCache, si existe. Pero incluso sin una implementación de IDistributedCache, el servicio HybridCache todavía proporciona almacenamiento en caché en proceso y protección de stampede.

Nota:

Al invalidar las entradas de caché por clave o por etiquetas, se invalidan en el servidor actual y en el almacenamiento secundario fuera de proceso. Sin embargo, la caché en memoria en otros servidores no se ve afectada.

Optimización del rendimiento

Para optimizar el rendimiento, configure HybridCache para reutilizar objetos y evitar asignaciones byte[].

Reutilización de objetos

Al reutilizar instancias, HybridCache puede reducir la sobrecarga de las asignaciones de CPU y objetos asociadas a la deserialización por llamada. Esto puede provocar mejoras de rendimiento en escenarios en los que los objetos almacenados en caché son grandes o a los que se accede con frecuencia.

En el código existente típico que usa IDistributedCache, cada recuperación de un objeto de la memoria caché da como resultado la deserialización. Este comportamiento significa que cada llamador simultáneo obtiene una instancia independiente del objeto, que no puede interactuar con otras instancias. El resultado es la seguridad de subprocesos, ya que no hay ningún riesgo de modificaciones simultáneas en la misma instancia de objeto.

Dado que gran parte del uso de HybridCache se adaptará del código de IDistributedCache existente, HybridCache conserva este comportamiento de forma predeterminada para evitar introducir errores de simultaneidad. Sin embargo, los objetos son intrínsecamente seguros para subprocesos si:

  • Son tipos inmutables.
  • El código no los modifica.

En tales casos, informe HybridCache de que es seguro reutilizar instancias mediante la realización de los dos cambios siguientes:

  • Marcar el tipo como sealed. La palabra clave sealed en C# significa que la clase no se puede heredar.
  • Aplicar el atributo [ImmutableObject(true)] al tipo . El atributo [ImmutableObject(true)] indica que el estado del objeto no se puede cambiar después de crearlo.

Evitar asignaciones de byte[]

HybridCache también proporciona APIs opcionales para implementaciones de IDistributedCache, para evitar la asignación de byte[]. Esta característica se implementa mediante las versiones preliminares de los Microsoft.Extensions.Caching.StackExchangeRedispaquetes , Microsoft.Extensions.Caching.SqlServery Microsoft.Extensions.Caching.Postgres . Para obtener más información, consulte IBufferDistributedCache. Estos son los comandos de la CLI de .NET para instalar los paquetes:

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package Microsoft.Extensions.Caching.SqlServer
dotnet add package Microsoft.Extensions.Caching.Postgres

Implementaciones de HybridCache personalizadas

En el marco compartido se incluye una implementación concreta de la clase abstracta HybridCache y se proporciona a través de la inserción de dependencias. Pero los desarrolladores son bienvenidos a proporcionar o consumir implementaciones personalizadas de la API, por ejemplo FusionCache.

Uso de la caché híbrida con AOT nativo

Las siguientes consideraciones específicas de AOT nativas se aplican a HybridCache:

  • Serialización de

    AOT nativo no admite la serialización basada en reflexión en tiempo de ejecución. Si almacena en caché tipos personalizados, debe usar generadores de código o configurar explícitamente serializadores compatibles con AOT, como la generación de código System.Text.Json. HybridCache aún está en desarrollo y simplificar la manera de usarlo con AOT es una prioridad alta de ese desarrollo. Para más información, consulte la solicitud de incorporación dotnet/extensions#6475.

  • Recorte

    Asegúrese de que todos los tipos que almacena en caché se referencien de manera que impida que el compilador AOT los recorte. El uso de generadores de origen para la serialización ayuda con este requisito. Para obtener más información, consulte soporte de ASP.NET Core para AOT nativo.

Si configura la serialización y el recorte correctamente, HybridCache se comporta del mismo modo en AOT nativo que en las aplicaciones de ASP.NET Core normales.

Compatibilidad

La biblioteca HybridCache admite entornos de ejecución de .NET anteriores, hasta .NET Framework 4.7.2 y .NET Standard 2.0.

Recursos adicionales

Para obtener más información, consulte el HybridCache código fuente.