Almacenamiento en caché distribuido en ASP.NET Core

Por Mohsin Nasir y smandia

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

Una caché distribuida es una caché compartida por varios servidores de aplicaciones, que normalmente se mantiene como un servicio externo a los servidores de aplicaciones que acceden a ella. Una caché distribuida puede mejorar el rendimiento y la escalabilidad de una aplicación de ASP.NET Core, especialmente cuando la aplicación está hospedada por un servicio en la nube o una granja de servidores.

Una caché distribuida tiene varias ventajas sobre otros escenarios de almacenamiento en caché en los que los datos almacenados en caché se almacenan en servidores de aplicaciones individuales.

Cuando se distribuyen los datos almacenados en caché, los datos:

  • Son coherentes (consistentes) entre solicitudes a varios servidores.
  • Sobreviven a los reinicios del servidor y a las implementaciones de aplicaciones.
  • No usan memoria local.

La configuración de caché distribuida es específica de la implementación. En este artículo se describe cómo configurar SQL Server y cachés distribuidas de Redis. También hay implementaciones de terceros disponibles, como NCache (NCache en GitHub). Independientemente de la implementación seleccionada, la aplicación interactúa con la memoria caché mediante la interfaz de IDistributedCache.

Vea o descargue el código de ejemplo (cómo descargarlo)

Requisitos previos

Agregue una referencia de paquete para el proveedor de caché distribuida que se usa:

Interfaz IDistributedCache

La interfaz IDistributedCache proporciona los métodos siguientes para manipular elementos en la implementación de caché distribuida:

  • Get, GetAsync: acepta una clave de cadena y recupera un elemento almacenado en caché como una matriz de byte[] si se encuentra en la memoria caché.
  • Set, SetAsync: agrega un elemento (como matriz de byte[]) a la memoria caché mediante una clave de cadena.
  • Refresh, RefreshAsync: actualiza un elemento de la caché en función de su clave, restableciendo su tiempo de expiración deslizante (si lo hubiera).
  • Remove, RemoveAsync: quita un elemento de caché basado en su clave de cadena.

Establecimiento de servicios de almacenamiento en caché distribuidos

Registro de una implementación de IDistributedCache en Program.cs. Entre las implementaciones proporcionadas por el marco que se describen en este tema se incluyen:

Caché distribuida de Redis

Se recomienda que las aplicaciones de producción usen Caché distribuida de Redis porque es la más eficaz. Para más información, vea Recomendaciones.

Redis es un almacén de datos en memoria de origen abierto, que suele usarse como caché distribuida. Puede configurar una instancia de Azure Cache for Redis para una aplicación de ASP.NET Core hospedada en Azure y usar una instancia de Azure Cache for Redis para el desarrollo local.

Una aplicación configura la implementación de caché mediante una instancia de RedisCache mediante una llamada a AddStackExchangeRedisCache. Para el almacenamiento en caché de resultados, use AddStackExchangeRedisOutputCache.

  1. Cree una instancia de Azure Cache for Redis.
  2. Copie la cadena de conexión principal (StackExchange.Redis) en Configuración.
    • Desarrollo local: guarde la cadena de conexión con Administrador de secretos.
    • Azure: guarde la cadena de conexión en la configuración de App Service u otro almacén seguro.

El código siguiente habilita Azure Cache for Redis:

builder.Services.AddStackExchangeRedisCache(options =>
 {
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
     options.InstanceName = "SampleInstance";
 });

En el código anterior se supone que la cadena de conexión principal (StackExchange.Redis) se guardó en la configuración con el nombre de clave MyRedisConStr.

Para más información, consulte Azure Cache for Redis.

Consulte este problema de GitHub para obtener una explicación sobre los enfoques alternativos de una caché local de Redis.

Caché de memoria distribuida

La caché de memoria distribuida (AddDistributedMemoryCache) es una implementación proporcionada por el marco de IDistributedCache que almacena los elementos en memoria. La caché de memoria distribuida no es una caché distribuida real. La instancia de la aplicación almacena los elementos almacenados en caché en el servidor donde se ejecuta la aplicación.

La caché de memoria distribuida es una implementación útil:

  • En escenarios de desarrollo y pruebas.
  • Cuando se usa un único servidor en producción y el consumo de memoria no es un problema. La implementación de la caché de memoria distribuida abstrae el almacenamiento de datos almacenados en caché. Permite implementar una solución de almacenamiento en caché distribuida verdadera en el futuro si se necesitan varios nodos o tolerancia a errores.

La aplicación de ejemplo usa la caché de memoria distribuida cuando la aplicación se ejecuta en el entorno de desarrollo en Program.cs:

builder.Services.AddDistributedMemoryCache();

Caché de SQL Server distribuida

La implementación de caché de SQL Server distribuida (AddDistributedSqlServerCache) permite que la caché distribuida use una base de datos de SQL Server como almacén de respaldo. Para crear una tabla de elementos en caché de SQL Server en una instancia de SQL Server, puede usar la herramienta sql-cache. La herramienta crea una tabla con el nombre y el esquema que especifique.

Cree una tabla en SQL Server ejecutando el comando sql-cache create. Proporcione la instancia de SQL Server (Data Source), la base de datos (Initial Catalog), el esquema (por ejemplo, dbo) y el nombre de tabla (por ejemplo, TestCache):

dotnet sql-cache create "Data Source=(localdb)/MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

Se registra un mensaje para indicar que la herramienta tuvo éxito:

Table and index were created successfully.

La tabla creada por la herramienta sql-cache tiene el esquema siguiente:

Tabla de caché de SQLServer

Nota

Una aplicación debe manipular los valores de caché mediante una instancia de IDistributedCache, no un SqlServerCache.

La aplicación de ejemplo implementa SqlServerCache en un entorno sin desarrollo en Program.cs:

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Nota

Un ConnectionString (y opcionalmente, SchemaName y TableName) suelen almacenarse fuera del control de origen (por ejemplo, almacenados por el Administrador de secretos o en archivos appsettings.json/appsettings.{Environment}.json). La cadena de conexión puede contener credenciales que se deben mantener fuera de los sistemas de control de origen.

Caché de NCache distribuida

NCache es una caché distribuida en memoria código abierto desarrollada de forma nativa en .NET y .NET Core. NCache funciona localmente y configurada como un clúster de caché distribuida para una aplicación de ASP.NET Core que se ejecuta en Azure o en otras plataformas de hospedaje.

Para instalar y configurar NCache en el equipo local, consulte Guía de introducción para Windows (.NET y .NET Core).

Para configurar NCache:

  1. Instale NuGet de código abierto de NCache.
  2. Configure el clúster de caché en client.ncconf.
  3. Agrega el código siguiente a Program.cs:
builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

Caché distribuida de Azure CosmosDB

Azure Cosmos DB se puede usar en ASP.NET Core como proveedor de estado de sesión mediante la interfaz IDistributedCache. Azure Cosmos DB es una base de datos relacional y NoSQL totalmente administrada para el desarrollo de aplicaciones modernas que ofrece alta disponibilidad, escalabilidad y acceso de baja latencia a los datos para aplicaciones críticas.

Después de instalar el paquete NuGet Microsoft.Extensions.Caching.Cosmos, configure una caché distribuida de Azure Cosmos DB de la siguiente manera:

Reutilización de un cliente existente

La manera más fácil de configurar la caché distribuida es reutilizar un cliente de Azure Cosmos DB existente. En este caso, la instancia CosmosClient no se eliminará cuando se elimine el proveedor.

services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
    cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
    cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
    cacheOptions.CosmosClient = existingCosmosClient;
    cacheOptions.CreateIfNotExists = true;
});

Creación de un nuevo cliente

Como alternativa, cree una instancia de un nuevo cliente. En este caso, la instancia CosmosClient se eliminará cuando se elimine el proveedor.

services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
    cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
    cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
    cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
    cacheOptions.CreateIfNotExists = true;
});

Uso de la caché distribuida

Para usar la interfaz de IDistributedCache, solicite una instancia de IDistributedCache en la aplicación. La instancia se proporciona mediante inyección de dependencias (DI).

Cuando se inicia la aplicación de ejemplo, IDistributedCache se inserta en Program.cs. La hora actual se almacena en caché mediante IHostApplicationLifetime (para más información, vea Host genérico: IHostApplicationLifetime):

app.Lifetime.ApplicationStarted.Register(() =>
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = System.Text.Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(20));
    app.Services.GetService<IDistributedCache>()
                              .Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

La aplicación de ejemplo inserta IDistributedCache en IndexModel para usarla en la página Índice.

Cada vez que se carga la página Índice, la memoria caché se comprueba durante el tiempo almacenado en caché en OnGetAsync. Si el tiempo almacenado en caché no ha expirado, se muestra la hora. Si han transcurrido 20 segundos desde la última vez que se accedió a la hora almacenada en caché (la última vez que se cargó esta página), la página muestra Tiempo almacenado en caché expirado.

Actualice inmediatamente la hora almacenada en caché a la hora actual seleccionando el botón Restablecer tiempo almacenado en caché . El botón desencadena el método de controlador OnPostResetCachedTime.

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string? CachedTimeUTC { get; set; }
    public string? ASP_Environment { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }

        ASP_Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (String.IsNullOrEmpty(ASP_Environment))
        {
            ASP_Environment = "Null, so Production";
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

No es necesario usar una duración singleton o con ámbito para instancias de IDistributedCache con las implementaciones integradas.

También puede crear una instancia de IDistributedCache donde quiera que necesite una en lugar de usar la inserción de dependencias, pero crear una instancia en el código puede hacer que el código sea más difícil de probar e infringir el principio de dependencias explícitas.

Recomendaciones

Al decidir qué implementación de IDistributedCache es la mejor para la aplicación, tenga en cuenta lo siguiente:

  • Infraestructura existente
  • Requisitos de rendimiento
  • Coste
  • Experiencia del equipo

Las soluciones de almacenamiento en caché suelen depender del almacenamiento en memoria para proporcionar una recuperación rápida de los datos almacenados en caché, pero la memoria es un recurso limitado y costoso de expandir. Almacene solo los datos usados habitualmente en una memoria caché.

En la mayoría de las aplicaciones, una caché de Redis proporciona un mayor rendimiento y una latencia menor que una caché de SQL Server. Sin embargo, se recomienda realizar pruebas comparativas para determinar las características de rendimiento de las estrategias de almacenamiento en caché.

Cuando se usa SQL Server como almacén de respaldo de caché distribuida, el uso de la misma base de datos para la memoria caché y el almacenamiento de datos normal de la aplicación y la recuperación pueden afectar negativamente al rendimiento de ambos. Se recomienda usar una instancia de SQL Server dedicada para el almacén de respaldo de caché distribuida.

Recursos adicionales

Una caché distribuida es una caché compartida por varios servidores de aplicaciones, que normalmente se mantiene como un servicio externo a los servidores de aplicaciones que acceden a ella. Una caché distribuida puede mejorar el rendimiento y la escalabilidad de una aplicación de ASP.NET Core, especialmente cuando la aplicación está hospedada por un servicio en la nube o una granja de servidores.

Una caché distribuida tiene varias ventajas sobre otros escenarios de almacenamiento en caché en los que los datos almacenados en caché se almacenan en servidores de aplicaciones individuales.

Cuando se distribuyen los datos almacenados en caché, los datos:

  • Son coherentes (consistentes) entre solicitudes a varios servidores.
  • Sobreviven a los reinicios del servidor y a las implementaciones de aplicaciones.
  • No usan memoria local.

La configuración de caché distribuida es específica de la implementación. En este artículo se describe cómo configurar SQL Server y cachés distribuidas de Redis. También hay implementaciones de terceros disponibles, como NCache (NCache en GitHub). Independientemente de la implementación seleccionada, la aplicación interactúa con la memoria caché mediante la interfaz de IDistributedCache.

Vea o descargue el código de ejemplo (cómo descargarlo)

Requisitos previos

Agregue una referencia de paquete para el proveedor de caché distribuida que se usa:

Interfaz IDistributedCache

La interfaz IDistributedCache proporciona los métodos siguientes para manipular elementos en la implementación de caché distribuida:

  • Get, GetAsync: acepta una clave de cadena y recupera un elemento almacenado en caché como una matriz de byte[] si se encuentra en la memoria caché.
  • Set, SetAsync: agrega un elemento (como matriz de byte[]) a la memoria caché mediante una clave de cadena.
  • Refresh, RefreshAsync: actualiza un elemento de la caché en función de su clave, restableciendo su tiempo de expiración deslizante (si lo hubiera).
  • Remove, RemoveAsync: quita un elemento de caché basado en su clave de cadena.

Establecimiento de servicios de almacenamiento en caché distribuidos

Registro de una implementación de IDistributedCache en Program.cs. Entre las implementaciones proporcionadas por el marco que se describen en este tema se incluyen:

Caché distribuida de Redis

Se recomienda que las aplicaciones de producción usen Caché distribuida de Redis porque es la más eficaz. Para más información, vea Recomendaciones.

Redis es un almacén de datos en memoria de origen abierto, que suele usarse como caché distribuida. Puede configurar una instancia de Azure Redis Cache para una aplicación de ASP.NET Core hospedada en Azure y usar una instancia de Azure Redis Cache para el desarrollo local.

Una aplicación configura la implementación de la caché usando una instancia de RedisCache (AddStackExchangeRedisCache).

  1. Cree una instancia de Azure Cache for Redis.
  2. Copie la cadena de conexión principal (StackExchange.Redis) en Configuración.
    • Desarrollo local: guarde la cadena de conexión con Administrador de secretos.
    • Azure: guarde la cadena de conexión en la configuración de App Service u otro almacén seguro.

El código siguiente habilita Azure Cache for Redis:

builder.Services.AddStackExchangeRedisCache(options =>
 {
     options.Configuration = builder.Configuration.GetConnectionString("MyRedisConStr");
     options.InstanceName = "SampleInstance";
 });

En el código anterior se supone que la cadena de conexión principal (StackExchange.Redis) se guardó en la configuración con el nombre de clave MyRedisConStr.

Para más información, consulte Azure Cache for Redis.

Consulte este problema de GitHub para obtener una explicación sobre los enfoques alternativos de una caché local de Redis.

Caché de memoria distribuida

La caché de memoria distribuida (AddDistributedMemoryCache) es una implementación proporcionada por el marco de IDistributedCache que almacena los elementos en memoria. La caché de memoria distribuida no es una caché distribuida real. La instancia de la aplicación almacena los elementos almacenados en caché en el servidor donde se ejecuta la aplicación.

La caché de memoria distribuida es una implementación útil:

  • En escenarios de desarrollo y pruebas.
  • Cuando se usa un único servidor en producción y el consumo de memoria no es un problema. La implementación de la caché de memoria distribuida abstrae el almacenamiento de datos almacenados en caché. Permite implementar una solución de almacenamiento en caché distribuida verdadera en el futuro si se necesitan varios nodos o tolerancia a errores.

La aplicación de ejemplo usa la caché de memoria distribuida cuando la aplicación se ejecuta en el entorno de desarrollo en Program.cs:

builder.Services.AddDistributedMemoryCache();

Caché de SQL Server distribuida

La implementación de caché de SQL Server distribuida (AddDistributedSqlServerCache) permite que la caché distribuida use una base de datos de SQL Server como almacén de respaldo. Para crear una tabla de elementos en caché de SQL Server en una instancia de SQL Server, puede usar la herramienta sql-cache. La herramienta crea una tabla con el nombre y el esquema que especifique.

Cree una tabla en SQL Server ejecutando el comando sql-cache create. Proporcione la instancia de SQL Server (Data Source), la base de datos (Initial Catalog), el esquema (por ejemplo, dbo) y el nombre de tabla (por ejemplo, TestCache):

dotnet sql-cache create "Data Source=(localdb)/MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

Se registra un mensaje para indicar que la herramienta tuvo éxito:

Table and index were created successfully.

La tabla creada por la herramienta sql-cache tiene el esquema siguiente:

Tabla de caché de SQLServer

Nota

Una aplicación debe manipular los valores de caché mediante una instancia de IDistributedCache, no un SqlServerCache.

La aplicación de ejemplo implementa SqlServerCache en un entorno sin desarrollo en Program.cs:

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Nota

Un ConnectionString (y opcionalmente, SchemaName y TableName) suelen almacenarse fuera del control de origen (por ejemplo, almacenados por el Administrador de secretos o en archivos appsettings.json/appsettings.{Environment}.json). La cadena de conexión puede contener credenciales que se deben mantener fuera de los sistemas de control de origen.

Caché de NCache distribuida

NCache es una caché distribuida en memoria código abierto desarrollada de forma nativa en .NET y .NET Core. NCache funciona localmente y configurada como un clúster de caché distribuida para una aplicación de ASP.NET Core que se ejecuta en Azure o en otras plataformas de hospedaje.

Para instalar y configurar NCache en el equipo local, consulte Guía de introducción para Windows (.NET y .NET Core).

Para configurar NCache:

  1. Instale NuGet de código abierto de NCache.
  2. Configure el clúster de caché en client.ncconf.
  3. Agrega el código siguiente a Program.cs:
builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

Uso de la caché distribuida

Para usar la interfaz de IDistributedCache, solicite una instancia de IDistributedCache en la aplicación. La instancia se proporciona mediante inyección de dependencias (DI).

Cuando se inicia la aplicación de ejemplo, IDistributedCache se inserta en Program.cs. La hora actual se almacena en caché mediante IHostApplicationLifetime (para más información, vea Host genérico: IHostApplicationLifetime):

app.Lifetime.ApplicationStarted.Register(() =>
{
    var currentTimeUTC = DateTime.UtcNow.ToString();
    byte[] encodedCurrentTimeUTC = System.Text.Encoding.UTF8.GetBytes(currentTimeUTC);
    var options = new DistributedCacheEntryOptions()
        .SetSlidingExpiration(TimeSpan.FromSeconds(20));
    app.Services.GetService<IDistributedCache>()
                              .Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});

La aplicación de ejemplo inserta IDistributedCache en IndexModel para usarla en la página Índice.

Cada vez que se carga la página Índice, la memoria caché se comprueba durante el tiempo almacenado en caché en OnGetAsync. Si el tiempo almacenado en caché no ha expirado, se muestra la hora. Si han transcurrido 20 segundos desde la última vez que se accedió a la hora almacenada en caché (la última vez que se cargó esta página), la página muestra Tiempo almacenado en caché expirado.

Actualice inmediatamente la hora almacenada en caché a la hora actual seleccionando el botón Restablecer tiempo almacenado en caché . El botón desencadena el método de controlador OnPostResetCachedTime.

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string? CachedTimeUTC { get; set; }
    public string? ASP_Environment { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }

        ASP_Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        if (String.IsNullOrEmpty(ASP_Environment))
        {
            ASP_Environment = "Null, so Production";
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

No es necesario usar una duración singleton o con ámbito para instancias de IDistributedCache con las implementaciones integradas.

También puede crear una instancia de IDistributedCache donde quiera que necesite una en lugar de usar la inserción de dependencias, pero crear una instancia en el código puede hacer que el código sea más difícil de probar e infringir el principio de dependencias explícitas.

Recomendaciones

Al decidir qué implementación de IDistributedCache es la mejor para la aplicación, tenga en cuenta lo siguiente:

  • Infraestructura existente
  • Requisitos de rendimiento
  • Coste
  • Experiencia del equipo

Las soluciones de almacenamiento en caché suelen depender del almacenamiento en memoria para proporcionar una recuperación rápida de los datos almacenados en caché, pero la memoria es un recurso limitado y costoso de expandir. Almacene solo los datos usados habitualmente en una memoria caché.

En la mayoría de las aplicaciones, una caché de Redis proporciona un mayor rendimiento y una latencia menor que una caché de SQL Server. Sin embargo, se recomienda realizar pruebas comparativas para determinar las características de rendimiento de las estrategias de almacenamiento en caché.

Cuando se usa SQL Server como almacén de respaldo de caché distribuida, el uso de la misma base de datos para la memoria caché y el almacenamiento de datos normal de la aplicación y la recuperación pueden afectar negativamente al rendimiento de ambos. Se recomienda usar una instancia de SQL Server dedicada para el almacén de respaldo de caché distribuida.

Recursos adicionales

Una caché distribuida es una caché compartida por varios servidores de aplicaciones, que normalmente se mantiene como un servicio externo a los servidores de aplicaciones que acceden a ella. Una caché distribuida puede mejorar el rendimiento y la escalabilidad de una aplicación de ASP.NET Core, especialmente cuando la aplicación está hospedada por un servicio en la nube o una granja de servidores.

Una caché distribuida tiene varias ventajas sobre otros escenarios de almacenamiento en caché en los que los datos almacenados en caché se almacenan en servidores de aplicaciones individuales.

Cuando se distribuyen los datos almacenados en caché, los datos:

  • Son coherentes (consistentes) entre solicitudes a varios servidores.
  • Sobreviven a los reinicios del servidor y a las implementaciones de aplicaciones.
  • No usan memoria local.

La configuración de caché distribuida es específica de la implementación. En este artículo se describe cómo configurar SQL Server y cachés distribuidas de Redis. También hay implementaciones de terceros disponibles, como NCache (NCache en GitHub). Independientemente de la implementación seleccionada, la aplicación interactúa con la memoria caché mediante la interfaz de IDistributedCache.

Vea o descargue el código de ejemplo (cómo descargarlo)

Requisitos previos

Para usar una caché distribuida de SQL Server, agregue una referencia al paquete Microsoft.Extensions.Caching.SqlServer.

Para usar una caché distribuida de Redis, agregue una referencia al paquete Microsoft.Extensions.Caching.StackExchangeRedis.

Para usar una caché distribuida de NCache, agregue una referencia al paquete NCache.Microsoft.Extensions.Caching.OpenSource.

Interfaz IDistributedCache

La interfaz IDistributedCache proporciona los métodos siguientes para manipular elementos en la implementación de caché distribuida:

  • Get, GetAsync: acepta una clave de cadena y recupera un elemento almacenado en caché como una matriz de byte[] si se encuentra en la memoria caché.
  • Set, SetAsync: agrega un elemento (como matriz de byte[]) a la memoria caché mediante una clave de cadena.
  • Refresh, RefreshAsync: actualiza un elemento de la caché en función de su clave, restableciendo su tiempo de expiración deslizante (si lo hubiera).
  • Remove, RemoveAsync: quita un elemento de caché basado en su clave de cadena.

Establecimiento de servicios de almacenamiento en caché distribuidos

Registro de una implementación de IDistributedCache en Startup.ConfigureServices. Entre las implementaciones proporcionadas por el marco que se describen en este tema se incluyen:

Caché de memoria distribuida

La caché de memoria distribuida (AddDistributedMemoryCache) es una implementación proporcionada por el marco de IDistributedCache que almacena los elementos en memoria. La caché de memoria distribuida no es una caché distribuida real. La instancia de la aplicación almacena los elementos almacenados en caché en el servidor donde se ejecuta la aplicación.

La caché de memoria distribuida es una implementación útil:

  • En escenarios de desarrollo y pruebas.
  • Cuando se usa un único servidor en producción y el consumo de memoria no es un problema. La implementación de la caché de memoria distribuida abstrae el almacenamiento de datos almacenados en caché. Permite implementar una solución de almacenamiento en caché distribuida verdadera en el futuro si se necesitan varios nodos o tolerancia a errores.

La aplicación de ejemplo usa la caché de memoria distribuida cuando la aplicación se ejecuta en el entorno de desarrollo en Startup.ConfigureServices:

services.AddDistributedMemoryCache();

Caché de SQL Server distribuida

La implementación de caché de SQL Server distribuida (AddDistributedSqlServerCache) permite que la caché distribuida use una base de datos de SQL Server como almacén de respaldo. Para crear una tabla de elementos en caché de SQL Server en una instancia de SQL Server, puede usar la herramienta sql-cache. La herramienta crea una tabla con el nombre y el esquema que especifique.

Cree una tabla en SQL Server ejecutando el comando sql-cache create. Proporcione la instancia de SQL Server (Data Source), la base de datos (Initial Catalog), el esquema (por ejemplo, dbo) y el nombre de tabla (por ejemplo, TestCache):

dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache

Se registra un mensaje para indicar que la herramienta tuvo éxito:

Table and index were created successfully.

La tabla creada por la herramienta sql-cache tiene el esquema siguiente:

Tabla de caché de SQLServer

Nota

Una aplicación debe manipular los valores de caché mediante una instancia de IDistributedCache, no un SqlServerCache.

La aplicación de ejemplo implementa SqlServerCache en un entorno sin desarrollo en Startup.ConfigureServices:

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = 
        _config["DistCache_ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});

Nota

Un ConnectionString (y opcionalmente, SchemaName y TableName) suelen almacenarse fuera del control de origen (por ejemplo, almacenados por el Administrador de secretos o en archivos appsettings.json/appsettings.{Environment}.json). La cadena de conexión puede contener credenciales que se deben mantener fuera de los sistemas de control de origen.

Caché distribuida de Redis

Redis es un almacén de datos en memoria de origen abierto, que suele usarse como caché distribuida. Puede configurar una instancia de Azure Redis Cache para una aplicación de ASP.NET Core hospedada en Azure y usar una instancia de Azure Redis Cache para el desarrollo local.

Una aplicación configura la implementación de la caché usando una instancia de RedisCache (AddStackExchangeRedisCache).

  1. Cree una instancia de Azure Cache for Redis.
  2. Copie la cadena de conexión principal (StackExchange.Redis) en Configuración.
    • Desarrollo local: guarde la cadena de conexión con Administrador de secretos.
    • Azure: guarde la cadena de conexión en la configuración de App Service u otro almacén seguro.

El código siguiente habilita Azure Cache for Redis:

public void ConfigureServices(IServiceCollection services)
{
    if (_hostContext.IsDevelopment())
    {
        services.AddDistributedMemoryCache();
    }
    else
    {
        services.AddStackExchangeRedisCache(options =>
        {
            options.Configuration = _config["MyRedisConStr"];
            options.InstanceName = "SampleInstance";
        });
    }

    services.AddRazorPages();
}

En el código anterior se supone que la cadena de conexión principal (StackExchange.Redis) se guardó en la configuración con el nombre de clave MyRedisConStr.

Para más información, consulte Azure Cache for Redis.

Consulte este problema de GitHub para obtener una explicación sobre los enfoques alternativos de una caché local de Redis.

Caché de NCache distribuida

NCache es una caché distribuida en memoria código abierto desarrollada de forma nativa en .NET y .NET Core. NCache funciona localmente y configurada como un clúster de caché distribuida para una aplicación de ASP.NET Core que se ejecuta en Azure o en otras plataformas de hospedaje.

Para instalar y configurar NCache en el equipo local, consulte Guía de introducción para Windows (.NET y .NET Core).

Para configurar NCache:

  1. Instale NuGet de código abierto de NCache.

  2. Configure el clúster de caché en client.ncconf.

  3. Agrega el código siguiente a Startup.ConfigureServices:

    services.AddNCacheDistributedCache(configuration =>    
    {        
        configuration.CacheName = "demoClusteredCache";
        configuration.EnableLogs = true;
        configuration.ExceptionsEnabled = true;
    });
    

Uso de la caché distribuida

Para usar la interfaz de IDistributedCache, solicite una instancia de IDistributedCache a cualquier constructor de la aplicación. La instancia se proporciona mediante inyección de dependencias (DI).

Cuando se inicia la aplicación de ejemplo, IDistributedCache se inserta en Startup.Configure. La hora actual se almacena en caché mediante IHostApplicationLifetime (para más información, vea Host genérico: IHostApplicationLifetime):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
    IHostApplicationLifetime lifetime, IDistributedCache cache)
{
    lifetime.ApplicationStarted.Register(() =>
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
    });

La aplicación de ejemplo inserta IDistributedCache en IndexModel para usarla en la página Índice.

Cada vez que se carga la página Índice, la memoria caché se comprueba durante el tiempo almacenado en caché en OnGetAsync. Si el tiempo almacenado en caché no ha expirado, se muestra la hora. Si han transcurrido 20 segundos desde la última vez que se accedió a la hora almacenada en caché (la última vez que se cargó esta página), la página muestra Tiempo almacenado en caché expirado.

Actualice inmediatamente la hora almacenada en caché a la hora actual seleccionando el botón Restablecer tiempo almacenado en caché . El botón desencadena el método de controlador OnPostResetCachedTime.

public class IndexModel : PageModel
{
    private readonly IDistributedCache _cache;

    public IndexModel(IDistributedCache cache)
    {
        _cache = cache;
    }

    public string CachedTimeUTC { get; set; }

    public async Task OnGetAsync()
    {
        CachedTimeUTC = "Cached Time Expired";
        var encodedCachedTimeUTC = await _cache.GetAsync("cachedTimeUTC");

        if (encodedCachedTimeUTC != null)
        {
            CachedTimeUTC = Encoding.UTF8.GetString(encodedCachedTimeUTC);
        }
    }

    public async Task<IActionResult> OnPostResetCachedTime()
    {
        var currentTimeUTC = DateTime.UtcNow.ToString();
        byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(20));
        await _cache.SetAsync("cachedTimeUTC", encodedCurrentTimeUTC, options);

        return RedirectToPage();
    }
}

Nota

No es necesario usar una duración singleton o con ámbito para instancias de IDistributedCache (al menos para las implementaciones integradas).

También puede crear una instancia de IDistributedCache donde quiera que necesite una en lugar de usar la inserción de dependencias, pero crear una instancia en el código puede hacer que el código sea más difícil de probar e infringir el principio de dependencias explícitas.

Recomendaciones

Al decidir qué implementación de IDistributedCache es la mejor para la aplicación, tenga en cuenta lo siguiente:

  • Infraestructura existente
  • Requisitos de rendimiento
  • Coste
  • Experiencia del equipo

Las soluciones de almacenamiento en caché suelen depender del almacenamiento en memoria para proporcionar una recuperación rápida de los datos almacenados en caché, pero la memoria es un recurso limitado y costoso de expandir. Almacene solo los datos usados habitualmente en una memoria caché.

Por lo general, una caché de Redis proporciona un mayor rendimiento y una latencia menor que una caché de SQL Server. Sin embargo, suele ser necesario realizar pruebas comparativas para determinar las características de rendimiento de las estrategias de almacenamiento en caché.

Cuando se usa SQL Server como almacén de respaldo de caché distribuida, el uso de la misma base de datos para la memoria caché y el almacenamiento de datos normal de la aplicación y la recuperación pueden afectar negativamente al rendimiento de ambos. Se recomienda usar una instancia de SQL Server dedicada para el almacén de respaldo de caché distribuida.

Recursos adicionales