Partilhar via


Biblioteca HybridCache no ASP.NET Core

Importante

HybridCache ainda está em pré-visualização, mas será oficialmente lançado após .NET 9.0 numa futura atualização menor do .NET Extensions.

Este artigo explica como configurar e usar a biblioteca de HybridCache em um aplicativo ASP.NET Core. Para obter uma introdução à biblioteca, consulte a secção HybridCache da visão geral de cache.

Obtenha a biblioteca

Instale o pacote Microsoft.Extensions.Caching.Hybrid.

dotnet add package Microsoft.Extensions.Caching.Hybrid --version "9.0.0-preview.7.24406.2"

Registar o serviço

Adicione o serviço HybridCache ao container de injeção de dependências de ao chamar AddHybridCache:

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

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

builder.Services.AddHybridCache();

O código anterior registra o serviço HybridCache com opções padrão. A API de registo também pode configurar as opções e a serialização .

Obter e armazenar entradas de cache

O serviço HybridCache fornece um método GetOrCreateAsync com duas sobrecargas, pegando uma chave e:

  • Um método de fábrica.
  • Estado, e um método de fábrica.

O método usa a chave para tentar recuperar o objeto do cache primário. Se o item não for encontrado no cache primário (uma falha de cache), ele verificará o cache secundário se estiver configurado. Se ele não encontrar os dados lá (outra falha de cache), ele chama o método de fábrica para obter o objeto da fonte de dados. Em seguida, armazena o objeto em caches primários e secundários. O método de fábrica nunca é chamado se o objeto for encontrado no cache primário ou secundário (um acerto de cache).

O serviço HybridCache garante que apenas um chamador simultâneo para uma determinada chave chame o método de fábrica e todos os outros chamadores aguardam o resultado dessa chamada. O CancellationToken passado para GetOrCreateAsync representa o cancelamento combinado de todas as chamadas simultâneas.

O principal GetOrCreateAsync sobrecarga

A sobrecarga sem estado de GetOrCreateAsync é recomendada para a maioria dos cenários. O código para chamá-lo é relativamente simples. Aqui está um exemplo:

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

Orientação da chave de cache

O key passado para GetOrCreateAsync deve identificar exclusivamente os dados que estão sendo armazenados em cache:

  • Em termos dos valores de identificador usados para recuperar esses dados de sua fonte.
  • Em termos de outros dados armazenados em cache no aplicativo.

Ambos os tipos de exclusividade são geralmente assegurados utilizando a concatenação de strings para criar uma chave única composta por diferentes partes unidas numa só string. Por exemplo:

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

ou

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

É responsabilidade do chamador garantir que um esquema de chaves seja válido e não possa causar confusão nos dados.

Recomendamos que você não use a entrada de usuário externo na chave de cache. Por exemplo, não use valores em bruto string de uma interface como parte de uma chave de cache. Essas chaves podem permitir tentativas de acesso mal-intencionadas ou podem ser usadas em um ataque de negação de serviço, saturando seu cache com dados com chaves sem significado geradas a partir de cadeias aleatórias. Nos exemplos válidos anteriores, os dados de ordem e os dados de preferência do utilizador são claramente distintos.

  • orderid e userId são identificadores gerados internamente.
  • region pode ser um enum ou cadeia de caracteres de uma lista predefinida de regiões conhecidas.

Não há significado colocado em tokens como / ou _. O valor da chave inteira é tratado como uma cadeia de caracteres de identificação opaca. Nesse caso, você pode omitir o / e _ sem alterar a maneira como o cache funciona, mas um delimitador geralmente é usado para evitar ambiguidade - por exemplo, $"order{customerId}{orderId}" pode causar confusão entre:

  • customerId 42 com orderId 123
  • customerId 421 com orderId 23

(ambos gerariam a chave de cache order42123)

Esta orientação se aplica igualmente a qualquer API de cache baseada em string, como HybridCache, IDistributedCachee IMemoryCache.

Observe que a sintaxe de cadeia de caracteres interpolada embutida ($"..." nos exemplos anteriores de chaves válidas) está diretamente dentro da chamada GetOrCreateAsync. Essa sintaxe é recomendada ao usar HybridCache, pois permite melhorias futuras planejadas que ignoram a necessidade de alocar um string para a chave em muitos cenários.

Considerações importantes adicionais

  • As chaves podem ser restritas a comprimentos máximos válidos. Por exemplo, a implementação de HybridCache padrão (via AddHybridCache(...)) restringe as chaves a 1024 caracteres por padrão. Esse número é configurável via HybridCacheOptions.MaximumKeyLength, com teclas mais longas ignorando os mecanismos de cache para evitar a saturação.
  • As chaves devem ser sequências Unicode válidas. Se sequências Unicode inválidas forem passadas, o comportamento será indefinido.
  • Ao usar um cache secundário fora de processo, como IDistributedCache, a implementação de back-end específica pode impor restrições adicionais. Como um exemplo hipotético, um back-end específico pode usar lógica de chave que não diferencia maiúsculas de minúsculas. O HybridCache padrão (via AddHybridCache(...)) deteta esse cenário para evitar ataques de confusão, no entanto, ainda pode resultar em chaves conflitantes sendo substituídas ou removidas mais cedo do que o esperado.

A alternativa de sobrecarga GetOrCreateAsync

A sobrecarga alternativa pode reduzir algum overhead de variáveis capturadas e retornos de chamada por instância, mas às custas da complexidade do código. Para a maioria dos cenários, o aumento de desempenho não supera a complexidade do código. Aqui está um exemplo que usa o sobrecarregamento alternativo:

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

O método SetAsync

Em muitos cenários, GetOrCreateAsync é a única API necessária. Mas HybridCache também tem SetAsync para armazenar um objeto em cache sem tentar recuperá-lo primeiro.

Remover entradas de cache por chave

Quando os dados subjacentes de uma entrada de cache forem alterados antes que ela expire, remova a entrada explicitamente chamando RemoveAsync com a chave para a entrada. Uma sobrecarga de permite especificar uma coleção de valores de chave.

Quando uma entrada é removida, ela é removida do cache primário e do cache secundário.

Remover entradas de cache por tag

Importante

Este recurso ainda está em desenvolvimento. Se você tentar remover entradas por tag, notará que isso não tem qualquer efeito.

As tags podem ser usadas para agrupar entradas de cache e invalidá-las juntas.

Defina tags ao chamar GetOrCreateAsync, conforme mostrado no exemplo a seguir:

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

Remova todas as entradas de uma tag especificada chamando RemoveByTagAsync com o valor da tag. Uma sobrecarga de permite especificar uma coleção de valores de tag.

Quando uma entrada é removida, ela é removida dos caches primário e secundário.

Opções

O método AddHybridCache pode ser usado para configurar padrões globais. O exemplo a seguir mostra como configurar algumas das opções disponíveis:

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

O método GetOrCreateAsync também pode usar um objeto HybridCacheEntryOptions para substituir os padrões globais para uma entrada de cache específica. Aqui está um exemplo:

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 obter mais informações sobre as opções, consulte o código-fonte:

Limites

As seguintes propriedades de HybridCacheOptions permitem configurar limites que se aplicam a todas as entradas de cache:

  • MaximumPayloadBytes - Tamanho máximo de uma entrada de cache. O valor padrão é 1 MB. As tentativas de armazenar valores acima desse tamanho são registradas e o valor não é armazenado em cache.
  • MaximumKeyLength - Comprimento máximo de uma chave de cache. O valor padrão é 1024 caracteres. As tentativas de armazenar valores acima desse tamanho são registradas e o valor não é armazenado em cache.

Serialização

O uso de um cache secundário fora do processo requer serialização. A serialização é configurada como parte do registro do serviço HybridCache. Os serializadores específicos de tipo e de uso geral podem ser configurados por meio dos métodos AddSerializer e AddSerializerFactory, derivados da chamada AddHybridCache. Por padrão, a biblioteca lida com string e byte[] internamente e usa System.Text.Json para todo o resto. HybridCache também pode usar outros serializadores, como protobuf ou XML.

O exemplo a seguir configura o serviço para usar um serializador protobuf do tipo específico:

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

O exemplo a seguir configura o serviço para usar um serializador de protobuf de uso geral que pode lidar com muitos 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>();

O cache secundário requer um armazenamento de dados, como Redis ou SqlServer. Para usar Cache do Azure paraRedis, por exemplo:

  • Instale o pacote Microsoft.Extensions.Caching.StackExchangeRedis.

  • Crie uma instância do Cache do Azure para Redis.

  • Obtenha uma cadeia de conexão que se conecta à instância do Redis. Localize a string de conexão selecionando Mostrar chaves de acesso na página Visão Geral no portal do Azure.

  • Armazene a cadeia de conexão na configuração do aplicativo. Por exemplo, use um arquivo de segredos de usuário que se pareça com o JSON a seguir, com a cadeia de conexão na seção ConnectionStrings. Substitua <the connection string> pela cadeia de conexão real:

    {
      "ConnectionStrings": {
        "RedisConnectionString": "<the connection string>"
      }
    }
    
  • Registre no DI a implementação IDistributedCache que o pacote Redis fornece. Para fazer isso, chame AddStackExchangeRedisCachee forneça a string de conexão. Por exemplo:

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • A implementação do Redis IDistributedCache agora está disponível no contêiner DI do aplicativo. HybridCache o usa como cache secundário e usa o serializador configurado para ele.

Para obter mais informações, consulte o aplicativo de exemplo de serialização HybridCache.

Armazenamento em cache

Por padrãoHybridCache usa MemoryCache para seu armazenamento em cache principal. As entradas de cache são armazenadas durante o processo, portanto, cada servidor tem um cache separado que é perdido sempre que o processo do servidor é reiniciado. Para armazenamento secundário fora de processo, como Redis ou SQL Server, o HybridCache usa a implementação de IDistributedCache configurada, se houver. Mas, mesmo sem uma implementação IDistributedCache, o serviço HybridCache ainda fornece cache embutido e proteção contra tumulto .

Observação

Ao invalidar entradas de cache por chave ou por tags, elas são invalidadas no servidor atual e no armazenamento secundário fora de processo. No entanto, o cache na memória em outros servidores não é afetado.

Otimize o desempenho

Para otimizar o desempenho, configure HybridCache para reutilizar objetos e evitar alocações byte[].

Reutilizar objetos

Ao reutilizar instâncias, HybridCache pode reduzir a sobrecarga associada às alocações de CPU e de objetos que ocorrem a cada desserialização por chamada. Isso pode levar a melhorias de desempenho em cenários em que os objetos armazenados em cache são grandes ou acessados com frequência.

No código existente típico que usa IDistributedCache, cada recuperação de um objeto do cache resulta em desserialização. Esse comportamento significa que cada chamador simultâneo obtém uma instância separada do objeto, que não pode interagir com outras instâncias. O resultado é a segurança do thread, pois não há risco de modificações simultâneas na mesma instância do objeto.

Como grande parte do uso HybridCache será adaptado do código IDistributedCache existente, o HybridCache preserva esse comportamento por padrão para evitar a introdução de bugs de simultaneidade. No entanto, os objetos são inerentemente thread-safe se:

  • São tipos imutáveis.
  • O código não os modifica.

Nesses casos, informe HybridCache que é seguro reutilizar instâncias ao fazê-lo da seguinte forma:

  • Marcando o tipo como sealed. A palavra-chave sealed em C# significa que a classe não pode ser herdada.
  • Aplicação do atributo [ImmutableObject(true)] ao tipo. O atributo [ImmutableObject(true)] indica que o estado do objeto não pode ser alterado após sua criação.

Evite alocações byte[]

HybridCache também fornece APIs opcionais para implementações IDistributedCache, para evitar alocações byte[]. Esse recurso é implementado pelas versões de visualização dos pacotes Microsoft.Extensions.Caching.StackExchangeRedis e Microsoft.Extensions.Caching.SqlServer. Para obter mais informações, consulte IBufferDistributedCache Aqui estão os comandos da CLI do .NET para instalar os pacotes:

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

Implementações personalizadas do HybridCache

Uma implementação concreta da classe abstrata HybridCache está incluída na estrutura compartilhada e é fornecida por meio de injeção de dependência. Mas os desenvolvedores são bem-vindos para fornecer implementações personalizadas da API.

Compatibilidade

A biblioteca HybridCache suporta tempos de execução mais antigos do .NET, até .NET Framework 4.7.2 e .NET Standard 2.0.

Recursos adicionais

Para obter mais informações sobre HybridCache, consulte os seguintes recursos: