Biblioteca do HybridCache no ASP.NET Core

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Este artigo explica como configurar e usar a biblioteca do HybridCache em um aplicativo ASP.NET Core. Para ver uma introdução à biblioteca, confira a seção HybridCache da visão geral do cache.

Obter a biblioteca

Instale o pacote Microsoft.Extensions.Caching.Hybrid.

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

Registrar o serviço

Adicione o serviço HybridCache ao contêiner de DI (injeção de dependência) chamando 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 registro também pode configurar opções e serialização.

Obter e armazenar entradas do cache

O serviço HybridCache fornece um método GetOrCreateAsync com duas sobrecargas, usando 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 perda no cache), ele verificará o cache secundário, caso ele esteja configurado. Se ele não encontrar os dados nele (outra perda no cache), ele chamará o método de fábrica para obter o objeto da fonte de dados. Em seguida, ele armazenará o objeto nos caches primário e secundário. O método de fábrica nunca será chamado se o objeto for encontrado no cache primário ou secundário (uma ocorrência no cache).

O serviço HybridCache garante que apenas um chamador simultâneo para determinada chave chame o método de fábrica e todos os outros chamadores aguardem pelo resultado dessa chamada. O CancellationToken transmitido para GetOrCreateAsync representa o cancelamento combinado de todos os chamadores simultâneos.

A sobrecarga principal GetOrCreateAsync

A sobrecarga sem estado GetOrCreateAsync é recomendada para a maioria dos cenários. O código usado para chamá-la é relativamente simples. Veja 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;
    }
}

A sobrecarga alternativa GetOrCreateAsync

A sobrecarga alternativa pode reduzir um pouco da sobrecarga das variáveis capturadas e dos retornos de chamada por instância, mas em detrimento de um código mais complexo. Na maioria dos cenários, o aumento de desempenho não supera a complexidade do código. Este é um exemplo que usa a 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;
    }
}

O método SetAsync

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

Remover entradas de cache por chave

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

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

Remover entradas de cache por marca

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

Defina as marcas ao chamar GetOrCreateAsync, conforme mostrado no seguinte 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;
    }
}

Remova todas as entradas de uma marca especificada chamando RemoveByTagAsync com o valor da marca. Uma sobrecarga permite que você especifique uma coleção de valores de marca.

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 seguinte exemplo 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 de uma entrada do cache específica. Veja 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, confira o código-fonte:

Limites

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

  • MaximumPayloadBytes – Tamanho máximo de uma entrada do cache. O valor padrão é 1 MB. As tentativas de armazenar valores desse tamanho são registradas em log e o valor não é armazenado em cache.
  • MaximumKeyLength – Comprimento máximo de uma chave do cache. O valor padrão é 1.024 caracteres. As tentativas de armazenar valores desse tamanho são registradas em log 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. Serializadores específicos de tipo e de uso geral podem ser configurados por meio dos métodos AddSerializer e AddSerializerFactory encadeados da chamada AddHybridCache. Por padrão, a biblioteca processa string e byte[] internamente e usa System.Text.Json para todo o restante. HybridCache também pode usar outros serializadores, como protobuf ou XML.

O seguinte exemplo configura o serviço para usar um serializador protobuf específico do 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>>();

O seguinte exemplo configura o serviço para usar um serializador 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 o usar o Cache do Azure para Redis, 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 conecte à instância do Redis. Localize a cadeia 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 do usuário semelhante ao 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 na DI a implementação de IDistributedCache fornecida pelo pacote Redis. Para fazer isso, chame AddStackExchangeRedisCachee passe a cadeia de conexão. Por exemplo:

    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = 
            builder.Configuration.GetConnectionString("RedisConnectionString");
    });
    
  • A implementação IDistributedCache do Redis agora está disponível no contêiner de DI do aplicativo. HybridCache o usa como o 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ão, o HybridCache usa MemoryCache para o armazenamento de cache primário. As entradas do cache são armazenadas em processo. Portanto, cada servidor tem um cache separado que é perdido sempre que o processo do servidor é reiniciado. Para o armazenamento secundário fora de processo, como o Redis ou o SQL Server, o HybridCache usa a implementação de IDistributedCache configurada, se houver. Porém, mesmo sem uma implementação de IDistributedCache, o serviço HybridCache ainda fornece proteção contra dispersão e cache em processo.

Otimizar o desempenho

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

Reutilizar objetos

Ao reutilizar instâncias, HybridCache pode reduzir a sobrecarga de alocações de CPU e objeto associadas à desserialização por chamada. Isso pode levar a aprimoramentos 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 de thread, pois não há risco de modificações simultâneas na mesma instância de objeto.

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

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

Nesses casos, informe HybridCache que é seguro reutilizar instâncias:

  • Marcando o tipo como sealed. A palavra-chave sealed em C# significa que a classe não pode ser herdada.
  • Aplicando o atributo [ImmutableObject(true)] ao tipo. O atributo [ImmutableObject(true)] indica que o estado do objeto não pode ser alterado depois de criado.

Evitar alocações de byte[]

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

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

Implementações personalizadas do HybridCache

Uma implementação concreta da classe abstrata HybridCache é incluída na estrutura compartilhada e é fornecida por meio de injeção de dependência. Porém, os desenvolvedores podem ficar à vontade para fornecer implementações personalizadas da API.

Compatibilidade

A biblioteca HybridCache dá suporte a runtimes mais antigos do .NET, até o .NET Framework 4.7.2 e o .NET Standard 2.0.

Recursos adicionais

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