Cache distribuído em ASP.NET Core

Por Mohsin Nasir e smandia

Um cache distribuído é um cache compartilhado por vários servidores de aplicativos, normalmente mantido como um serviço externo para os servidores de aplicativo que o acessam. Um cache distribuído pode melhorar o desempenho e a escalabilidade de um aplicativo ASP.NET Core, especialmente quando o aplicativo é hospedado por um serviço de nuvem ou um farm de servidores.

Um cache distribuído tem várias vantagens em relação a outros cenários de cache em que os dados armazenados em cache são armazenados em servidores de aplicativos individuais.

Quando os dados armazenados em cache são distribuídos, os dados:

  • É coerente (consistente) entre solicitações para vários servidores.
  • Sobrevive a reinicializações de servidor e implantações de aplicativo.
  • Não usa memória local.

A configuração de cache distribuído é específica da implementação. Este artigo descreve como configurar caches distribuídos SQL Server e Redis. Implementações de terceiros também estão disponíveis, como NCache (NCache no GitHub). Independentemente da implementação selecionada, o aplicativo interage com o cache usando a IDistributedCache interface.

Exibir ou baixar código de exemplo (como baixar)

Pré-requisitos

Para usar um cache distribuído SQL Server, adicione uma referência de pacote ao pacote Microsoft.Extensions.Caching.SqlServer.

Para usar um cache distribuído redis, adicione uma referência de pacote ao pacote Microsoft.Extensions.Caching.StackExchangeRedis .

Para usar o cache distribuído NCache, adicione uma referência de pacote ao pacote NCache.Microsoft.Extensions.Caching.OpenSource .

Interface IDistributedCache

A IDistributedCache interface fornece os seguintes métodos para manipular itens na implementação do cache distribuído:

  • Get, GetAsync: aceita uma chave de cadeia de caracteres e recupera um item armazenado em cache como uma byte[] matriz, se encontrado no cache.
  • Set, SetAsync: adiciona um item (como byte[] matriz) ao cache usando uma chave de cadeia de caracteres.
  • Refresh, RefreshAsync: atualiza um item no cache com base em sua chave, redefinindo seu tempo limite de expiração deslizante (se houver).
  • Remove, RemoveAsync: remove um item de cache com base em sua chave de cadeia de caracteres.

Estabelecer serviços de cache distribuído

Registrar uma implementação de IDistributedCache in Program.cs. As implementações fornecidas pela estrutura descritas neste tópico incluem:

Cache de Memória Distribuída

O Cache de Memória Distribuída (AddDistributedMemoryCache) é uma implementação IDistributedCache fornecida pela estrutura que armazena itens na memória. O Cache de Memória Distribuída não é um cache distribuído real. Os itens armazenados em cache são armazenados pela instância do aplicativo no servidor em que o aplicativo está em execução.

O Cache de Memória Distribuída é uma implementação útil:

  • Em cenários de desenvolvimento e teste.
  • Quando um único servidor é usado em produção e consumo de memória não é um problema. Implementar o Cache de Memória Distribuída abstrai o armazenamento de dados armazenado em cache. Ele permite implementar uma solução de cache distribuída verdadeira no futuro se vários nós ou tolerância a falhas se tornarem necessários.

O aplicativo de exemplo usa o Cache de Memória Distribuída quando o aplicativo é executado no ambiente de desenvolvimento em Program.cs:

builder.Services.AddDistributedMemoryCache();

Cache de SQL Server Distribuído

A implementação do Cache SQL Server Distribuído (AddDistributedSqlServerCache) permite que o cache distribuído use um banco de dados SQL Server como repositório de suporte. Para criar uma SQL Server tabela de itens armazenados em cache em uma instância de SQL Server, você pode usar a sql-cache ferramenta. A ferramenta cria uma tabela com o nome e o esquema especificados.

Crie uma tabela no SQL Server executando o sql-cache create comando. Forneça a instância SQL Server (Data Source), o banco de dados (Initial Catalog), o esquema (por exemplo, dbo) e o nome da tabela (por exemplo, TestCache):

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

Uma mensagem é registrada para indicar que a ferramenta foi bem-sucedida:

Table and index were created successfully.

A tabela criada pela sql-cache ferramenta tem o seguinte esquema:

Tabela de cache sqlServer

Observação

Um aplicativo deve manipular valores de cache usando uma instância de IDistributedCache, não um SqlServerCache.

O aplicativo de exemplo implementa SqlServerCache em um ambiente que não seja de desenvolvimento em Program.cs:

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

Observação

Um ConnectionString (e, opcionalmente, SchemaName e TableName) normalmente são armazenados fora do controle do código-fonte (por exemplo, armazenados pelo Gerenciador de Segredos ou em appsettings.json/appsettings.{Environment}.json arquivos). A cadeia de conexão pode conter credenciais que devem ser mantidas fora dos sistemas de controle do código-fonte.

Cache Redis Distribuído

O Redis é um armazenamento de dados código aberto na memória, que geralmente é usado como um cache distribuído. Você pode configurar um Cache Redis do Azure para um aplicativo de ASP.NET Core hospedado no Azure e usar um Cache Redis do Azure para desenvolvimento local.

Um aplicativo configura a implementação do cache usando uma RedisCache instância (AddStackExchangeRedisCache).

  1. Crie um Cache do Azure para Redis.
  2. Copie a cadeia de conexão primária (StackExchange.Redis) para a Configuração.
    • Desenvolvimento local: salve a cadeia de conexão com o Gerenciador de Segredos.
    • Azure: salve a cadeia de conexão na Configuração Serviço de Aplicativo ou em outro repositório seguro.

O código a seguir habilita o Cache do Azure para Redis:

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

O código anterior pressupõe que a cadeia de conexão primária (StackExchange.Redis) foi salva na configuração com o nome MyRedisConStrda chave.

Para obter mais informações, consulte Azure Cache for Redis.

Consulte este problema do GitHub para uma discussão sobre abordagens alternativas para um cache Redis local.

Cache NCache Distribuído

O NCache é um cache distribuído código aberto na memória desenvolvido nativamente no .NET e no .NET Core. O NCache funciona localmente e configurado como um cluster de cache distribuído para um aplicativo ASP.NET Core em execução no Azure ou em outras plataformas de hospedagem.

Para instalar e configurar o NCache em seu computador local, consulte Introdução Guia do Windows (.NET e .NET Core).

Para configurar o NCache:

  1. Instale o NCache código aberto NuGet.
  2. Configure o cluster de cache em client.ncconf.
  3. Adicione o seguinte código a Program.cs:
builder.Services.AddNCacheDistributedCache(configuration =>
{
    configuration.CacheName = "democache";
    configuration.EnableLogs = true;
    configuration.ExceptionsEnabled = true;
});

Usar o cache distribuído

Para usar a IDistributedCache interface, solicite uma instância do IDistributedCache aplicativo. A instância é fornecida por DI (injeção de dependência).

Quando o aplicativo de exemplo é iniciado, IDistributedCache é injetado em Program.cs. A hora atual é armazenada em cache usando IHostApplicationLifetime (para obter mais informações, consulte 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);
});

O aplicativo de exemplo injeta IDistributedCache no IndexModel para uso pela página Índice.

Cada vez que a página Índice é carregada, o cache é verificado quanto ao tempo armazenado em cache.OnGetAsync Se o tempo armazenado em cache não tiver expirado, a hora será exibida. Se 20 segundos tiverem decorrido desde a última vez em que o tempo armazenado em cache foi acessado (a última vez em que esta página foi carregada), a página exibirá o tempo em cache expirado.

Atualize imediatamente o tempo armazenado em cache para a hora atual selecionando o botão Redefinir Tempo Armazenado em Cache . O botão dispara o OnPostResetCachedTime método manipulador.

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();
    }
}

Não é necessário usar um tempo de vida Singleton ou Escopo para IDistributedCache instâncias com as implementações internas.

Você também pode criar uma IDistributedCache instância onde você precisar de uma em vez de usar DI, mas criar uma instância no código pode tornar seu código mais difícil de testar e viola o Princípio de Dependências Explícitas.

Recomendações

Ao decidir qual IDistributedCache implementação é melhor para seu aplicativo, considere o seguinte:

  • Infraestrutura existente
  • Requisitos de desempenho
  • Custo
  • Experiência em equipe

As soluções de cache geralmente dependem do armazenamento na memória para fornecer recuperação rápida de dados armazenados em cache, mas a memória é um recurso limitado e dispendido para expandir. Armazene apenas dados comumente usados em um cache.

Geralmente, um cache Redis fornece maior taxa de transferência e latência menor do que um cache SQL Server. No entanto, o benchmarking geralmente é necessário para determinar as características de desempenho das estratégias de cache.

Quando SQL Server é usado como um repositório de backup de cache distribuído, o uso do mesmo banco de dados para o cache e o armazenamento e a recuperação de dados comuns do aplicativo podem afetar negativamente o desempenho de ambos. É recomendável usar uma instância de SQL Server dedicada para o repositório de backup de cache distribuído.

Recursos adicionais

Um cache distribuído é um cache compartilhado por vários servidores de aplicativos, normalmente mantido como um serviço externo para os servidores de aplicativo que o acessam. Um cache distribuído pode melhorar o desempenho e a escalabilidade de um aplicativo ASP.NET Core, especialmente quando o aplicativo é hospedado por um serviço de nuvem ou um farm de servidores.

Um cache distribuído tem várias vantagens em relação a outros cenários de cache em que os dados armazenados em cache são armazenados em servidores de aplicativos individuais.

Quando os dados armazenados em cache são distribuídos, os dados:

  • É coerente (consistente) entre solicitações para vários servidores.
  • Sobrevive a reinicializações de servidor e implantações de aplicativo.
  • Não usa memória local.

A configuração de cache distribuído é específica da implementação. Este artigo descreve como configurar caches distribuídos SQL Server e Redis. Implementações de terceiros também estão disponíveis, como NCache (NCache no GitHub). Independentemente da implementação selecionada, o aplicativo interage com o cache usando a IDistributedCache interface.

Exibir ou baixar código de exemplo (como baixar)

Pré-requisitos

Para usar um cache distribuído SQL Server, adicione uma referência de pacote ao pacote Microsoft.Extensions.Caching.SqlServer.

Para usar um cache distribuído redis, adicione uma referência de pacote ao pacote Microsoft.Extensions.Caching.StackExchangeRedis .

Para usar o cache distribuído NCache, adicione uma referência de pacote ao pacote NCache.Microsoft.Extensions.Caching.OpenSource .

Interface IDistributedCache

A IDistributedCache interface fornece os seguintes métodos para manipular itens na implementação do cache distribuído:

  • Get, GetAsync: aceita uma chave de cadeia de caracteres e recupera um item armazenado em cache como uma byte[] matriz, se encontrado no cache.
  • Set, SetAsync: adiciona um item (como byte[] matriz) ao cache usando uma chave de cadeia de caracteres.
  • Refresh, RefreshAsync: atualiza um item no cache com base em sua chave, redefinindo seu tempo limite de expiração deslizante (se houver).
  • Remove, RemoveAsync: remove um item de cache com base em sua chave de cadeia de caracteres.

Estabelecer serviços de cache distribuído

Registrar uma implementação de IDistributedCache in Startup.ConfigureServices. As implementações fornecidas pela estrutura descritas neste tópico incluem:

Cache de Memória Distribuída

O Cache de Memória Distribuída (AddDistributedMemoryCache) é uma implementação IDistributedCache fornecida pela estrutura que armazena itens na memória. O Cache de Memória Distribuída não é um cache distribuído real. Os itens armazenados em cache são armazenados pela instância do aplicativo no servidor em que o aplicativo está em execução.

O Cache de Memória Distribuída é uma implementação útil:

  • Em cenários de desenvolvimento e teste.
  • Quando um único servidor é usado em produção e consumo de memória não é um problema. Implementar o Cache de Memória Distribuída abstrai o armazenamento de dados armazenado em cache. Ele permite implementar uma solução de cache distribuída verdadeira no futuro se vários nós ou tolerância a falhas se tornarem necessários.

O aplicativo de exemplo usa o Cache de Memória Distribuída quando o aplicativo é executado no ambiente de desenvolvimento em Startup.ConfigureServices:

services.AddDistributedMemoryCache();

Cache de SQL Server Distribuído

A implementação do Cache SQL Server Distribuído (AddDistributedSqlServerCache) permite que o cache distribuído use um banco de dados SQL Server como repositório de suporte. Para criar uma SQL Server tabela de itens armazenados em cache em uma instância de SQL Server, você pode usar a sql-cache ferramenta. A ferramenta cria uma tabela com o nome e o esquema especificados.

Crie uma tabela no SQL Server executando o sql-cache create comando. Forneça a instância SQL Server (Data Source), o banco de dados (Initial Catalog), o esquema (por exemplo, dbo) e o nome da tabela (por exemplo, TestCache):

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

Uma mensagem é registrada para indicar que a ferramenta foi bem-sucedida:

Table and index were created successfully.

A tabela criada pela sql-cache ferramenta tem o seguinte esquema:

Tabela de cache sqlServer

Observação

Um aplicativo deve manipular valores de cache usando uma instância de IDistributedCache, não um SqlServerCache.

O aplicativo de exemplo implementa SqlServerCache em um ambiente que não seja de desenvolvimento em Startup.ConfigureServices:

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

Observação

Um ConnectionString (e, opcionalmente, SchemaName e TableName) normalmente são armazenados fora do controle do código-fonte (por exemplo, armazenados pelo Gerenciador de Segredos ou em appsettings.json/appsettings.{Environment}.json arquivos). A cadeia de conexão pode conter credenciais que devem ser mantidas fora dos sistemas de controle do código-fonte.

Cache Redis Distribuído

O Redis é um armazenamento de dados código aberto na memória, que geralmente é usado como um cache distribuído. Você pode configurar um Cache Redis do Azure para um aplicativo de ASP.NET Core hospedado no Azure e usar um Cache Redis do Azure para desenvolvimento local.

Um aplicativo configura a implementação do cache usando uma RedisCache instância (AddStackExchangeRedisCache).

  1. Crie um Cache do Azure para Redis.
  2. Copie a cadeia de conexão primária (StackExchange.Redis) para a Configuração.
    • Desenvolvimento local: salve a cadeia de conexão com o Gerenciador de Segredos.
    • Azure: salve a cadeia de conexão na Configuração Serviço de Aplicativo ou em outro repositório seguro.

O código a seguir habilita o Cache do Azure para Redis:

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

    services.AddRazorPages();
}

O código anterior pressupõe que a cadeia de conexão primária (StackExchange.Redis) foi salva na configuração com o nome MyRedisConStrda chave.

Para obter mais informações, consulte Azure Cache for Redis.

Consulte este problema do GitHub para uma discussão sobre abordagens alternativas para um cache Redis local.

Cache NCache Distribuído

O NCache é um cache distribuído código aberto na memória desenvolvido nativamente no .NET e no .NET Core. O NCache funciona localmente e configurado como um cluster de cache distribuído para um aplicativo ASP.NET Core em execução no Azure ou em outras plataformas de hospedagem.

Para instalar e configurar o NCache em seu computador local, consulte Introdução Guia do Windows (.NET e .NET Core).

Para configurar o NCache:

  1. Instale o NCache código aberto NuGet.

  2. Configure o cluster de cache em client.ncconf.

  3. Adicione o seguinte código a Startup.ConfigureServices:

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

Usar o cache distribuído

Para usar a IDistributedCache interface, solicite uma instância de IDistributedCache qualquer construtor no aplicativo. A instância é fornecida por DI (injeção de dependência).

Quando o aplicativo de exemplo é iniciado, IDistributedCache é injetado em Startup.Configure. A hora atual é armazenada em cache usando IHostApplicationLifetime (para obter mais informações, consulte 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);
    });

O aplicativo de exemplo injeta IDistributedCache no IndexModel para uso pela página Índice.

Cada vez que a página Índice é carregada, o cache é verificado quanto ao tempo armazenado em cache.OnGetAsync Se o tempo armazenado em cache não tiver expirado, a hora será exibida. Se 20 segundos tiverem decorrido desde a última vez em que o tempo armazenado em cache foi acessado (a última vez em que esta página foi carregada), a página exibirá o tempo em cache expirado.

Atualize imediatamente o tempo armazenado em cache para a hora atual selecionando o botão Redefinir Tempo Armazenado em Cache . O botão dispara o OnPostResetCachedTime método manipulador.

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();
    }
}

Observação

Não é necessário usar um tempo de vida singleton ou escopo para IDistributedCache instâncias (pelo menos para as implementações internas).

Você também pode criar uma IDistributedCache instância onde quer que precise de uma em vez de usar DI, mas a criação de uma instância no código pode tornar seu código mais difícil de testar e viola o Princípio de Dependências Explícitas.

Recomendações

Ao decidir qual IDistributedCache implementação é melhor para seu aplicativo, considere o seguinte:

  • Infraestrutura existente
  • Requisitos de desempenho
  • Custo
  • Experiência em equipe

As soluções de cache geralmente dependem do armazenamento na memória para fornecer recuperação rápida de dados armazenados em cache, mas a memória é um recurso limitado e dispendido para expandir. Armazene apenas dados usados com frequência em um cache.

Em geral, um cache Redis fornece maior taxa de transferência e latência menor do que um cache de SQL Server. No entanto, o benchmarking geralmente é necessário para determinar as características de desempenho das estratégias de cache.

Quando SQL Server é usado como um repositório de backup de cache distribuído, o uso do mesmo banco de dados para o cache e o armazenamento e a recuperação de dados comuns do aplicativo podem afetar negativamente o desempenho de ambos. É recomendável usar uma instância de SQL Server dedicada para o repositório de backup de cache distribuído.

Recursos adicionais