Partilhar via


Persistência de cereais

Os grãos podem ter vários objetos de dados persistentes nomeados associados a eles. Estes objetos de estado são carregados a partir do armazenamento durante a ativação do grão para que estejam disponíveis durante os pedidos. A persistência de cereais utiliza um modelo de plugin extensível para que os fornecedores de armazenamento para qualquer base de dados possam ser utilizados. Este modelo de persistência foi concebido para a simplicidade, e não se destina a cobrir todos os padrões de acesso a dados. Os grãos também podem aceder diretamente às bases de dados, sem utilizar o modelo de persistência de cereais.

No diagrama acima, o UserGrain tem um estado de Perfil e um Estado Cart , cada um dos quais é armazenado num sistema de armazenamento separado.

Objetivos

  1. Vários objetos de dados persistentes nomeados por grão.
  2. Vários fornecedores de armazenamento configurados, cada um dos quais pode ter uma configuração diferente e ser apoiado por um sistema de armazenamento diferente.
  3. Armazenamento fornecedores podem ser desenvolvidos e publicados pela comunidade.
  4. Armazenamento fornecedores têm controlo total sobre como armazenam dados do estado dos cereais na loja de apoio persistente. Corolário: Orleans não está a fornecer uma solução de armazenamento ORM abrangente, mas permite que os fornecedores de armazenamento personalizados suportem requisitos específicos de ORM conforme e quando necessário.

Pacote

Os fornecedores de armazenamento de cereais de Orleans podem ser encontrados no NuGet. Os pacotes oficialmente mantidos incluem:

API

Os grãos interagem com o seu estado persistente utilizando IPersistentState<TState> onde TState está o tipo de estado serializável:

public interface IPersistentState<TState> where TState : new()
{
    TState State { get; set; }
    string Etag { get; }
    Task ClearStateAsync();
    Task WriteStateAsync();
    Task ReadStateAsync();
}

Os casos são IPersistentState<TState> injetados no grão como parâmetros de construção. Estes parâmetros podem ser anotados com um PersistentStateAttribute atributo para identificar o nome do estado que está a ser injetado e o nome do fornecedor de armazenamento que o fornece. O exemplo que se segue demonstra-o injetando dois Estados nomeados no UserGrain construtor:

public class UserGrain : Grain, IUserGrain
{
    private readonly IPersistentState<ProfileState> _profile;
    private readonly IPersistentState<CartState> _cart;

    public UserGrain(
        [PersistentState("profile", "profileStore")] IPersistentState<ProfileState> profile,
        [PersistentState("cart", "cartStore")] IPersistentState<CartState> cart)
    {
        _profile = profile;
        _cart = cart;
    }
}

Diferentes tipos de grãos podem utilizar diferentes fornecedores de armazenamento configurados, mesmo que ambos sejam do mesmo tipo; por exemplo, duas instâncias diferentes do Azure Table Armazenamento fornecedor, ligadas a diferentes contas Azure Armazenamento.

Ler estado

O estado dos cereais será automaticamente lido quando o grão é ativado, mas os grãos são responsáveis por ativar explicitamente a escrita para qualquer estado de grão alterado quando necessário.

Se um grão quiser relê-lo explicitamente o estado mais recente para este grão da loja de apoio, o grão deve chamar o ReadStateAsync método. Isto recarregará o estado do grão da loja persistente através do fornecedor de armazenamento, e a cópia prévia na memória do estado do grão será substituída e substituída quando a Task partir de ReadStateAsync() concluída.

O valor do estado é acessado através do State imóvel. Por exemplo, o seguinte método acede ao estado de perfil declarado no código acima:

public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

Não há necessidade de ligar ReadStateAsync() durante o funcionamento normal; o estado é carregado automaticamente durante a ativação. No entanto, ReadStateAsync() pode ser usado para refrescar o estado que é modificado externamente.

Consulte a secção modos de avaria abaixo para obter mais detalhes sobre os mecanismos de manuseamento de erros.

Escrever estado

O estado pode ser modificado através da State propriedade. O estado modificado não é automaticamente persistido. Em vez disso, o desenvolvedor decide quando persistir do estado, chamando o WriteStateAsync método. Por exemplo, o seguinte método atualiza uma propriedade State e persiste o estado atualizado:

public async Task SetNameAsync(string name)
{
    _profile.State.Name = name;
    await _profile.WriteStateAsync();
}

Conceptualmente, o Runtime de Orleans levará uma cópia profunda do objeto de dados do estado do grão para a sua utilização durante qualquer operação de escrita. Sob as capas, o tempo de execução pode usar regras de otimização e heurística para evitar a realização de alguma ou a totalidade da cópia profunda em algumas circunstâncias, desde que a semântica de isolamento lógico esperado seja preservada.

Consulte a secção modos de avaria abaixo para obter mais detalhes sobre os mecanismos de manuseamento de erros.

Estado claro

O ClearStateAsync método limpa o estado do grão no armazenamento. Dependendo do fornecedor, esta operação pode eliminar opcionalmente o estado dos cereais.

Introdução

Antes de um grão poder utilizar a persistência, um fornecedor de armazenamento deve ser configurado no silo.

Em primeiro lugar, configurar os fornecedores de armazenamento, um para o estado de perfil e outro para o estado do carrinho:

var host = new HostBuilder()
    .UseOrleans(siloBuilder =>
    {
        siloBuilder.AddAzureTableGrainStorage(
            name: "profileStore",
            configureOptions: options =>
            {
                // Use JSON for serializing the state in storage
                options.UseJson = true;

                // Configure the storage connection key
                options.ConnectionString =
                    "DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1";
            })
            .AddAzureBlobGrainStorage(
                name: "cartStore",
                configureOptions: options =>
                {
                    // Use JSON for serializing the state in storage
                    options.UseJson = true;

                    // Configure the storage connection key
                    options.ConnectionString =
                        "DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2";
                });
    })
    .Build();

Agora que um fornecedor de armazenamento foi configurado com o nome "profileStore", podemos aceder a este fornecedor a partir de um grão.

O estado persistente pode ser adicionado a um grão de duas maneiras primárias:

  1. Injetando IPersistentState<TState> no construtor do grão.
  2. Herdando de Grain<TGrainState>.

A forma recomendada de adicionar armazenamento a um grão é injetando IPersistentState<TState> no construtor do grão com um atributo associado [PersistentState("stateName", "providerName")] . Para mais detalhes,Grain<TState> consulte abaixo. Isto ainda é apoiado, mas é considerado uma abordagem legado.

Declare uma classe para manter o estado dos nossos cereais:

[Serializable]
public class ProfileState
{
    public string Name { get; set; }

    public Date DateOfBirth
}

Injetar IPersistentState<ProfileState> no construtor do grão:

public class UserGrain : Grain, IUserGrain
{
    private readonly IPersistentState<ProfileState> _profile;

    public UserGrain(
        [PersistentState("profile", "profileStore")]
        IPersistentState<ProfileState> profile)
    {
        _profile = profile;
    }
}

Nota

O estado de perfil não será carregado no momento em que é injetado no construtor, pelo que o acesso é inválido nessa altura. O estado será carregado antes OnActivateAsync de ser chamado.

Agora que o grão tem um estado persistente, podemos adicionar métodos para ler e escrever o estado:

public class UserGrain : Grain, IUserGrain
    {
    private readonly IPersistentState<ProfileState> _profile;

    public UserGrain(
        [PersistentState("profile", "profileStore")]
        IPersistentState<ProfileState> profile)
    {
        _profile = profile;
    }

    public Task<string> GetNameAsync() => Task.FromResult(_profile.State.Name);

    public async Task SetNameAsync(string name)
    {
        _profile.State.Name = name;
        await _profile.WriteStateAsync();
    }
}

Modos de avaria para operações de persistência

Modos de falha para operações de leitura

As falhas devolvidas pelo fornecedor de armazenamento durante a leitura inicial dos dados estatais para esse grão em particular falharão na operação de ativação desse grão; nesse caso, não haverá qualquer chamada para o método de retorno do ciclo de vida desse OnActivateAsync() grão. O pedido original ao grão que causou a ativação será falhado de volta ao chamador, da mesma forma que qualquer outra falha durante a ativação do grão. As falhas encontradas pelo fornecedor de armazenamento ao ler dados estatais de um determinado grão resultarão numa exceção de ReadStateAsync()Task. O grão pode optar por manusear ou ignorar a Task exceção, como qualquer outro Task em Orleães.

Qualquer tentativa de enviar uma mensagem a um grão que não tenha sido carregado no tempo de arranque do silo devido a um config de um fornecedor de armazenamento em falta/mau armazenamento devolverá o erro BadProviderConfigExceptionpermanente .

Modos de falha para operações de escrita

As falhas encontradas pelo fornecedor de armazenamento ao escrever dados estatais para um determinado grão resultarão numa exceção lançada por WriteStateAsync()Task. Normalmente, isto significa que a exceção da chamada de grão será recorrida para o cliente chamador, desde que esteja WriteStateAsync()Task corretamente acorrentado na devolução Task final para este método de grão. No entanto, é possível em certos cenários avançados escrever código de grão para lidar especificamente com tais erros de escrita, assim como eles podem lidar com qualquer outro defeito Task.

Os grãos que executam o código de manuseamento de erros/recuperação devem apanhar exceções /falhas WriteStateAsync()Taske não jogá-las novamente, para significar que lidaram com sucesso com o erro de escrita.

Recomendações

Utilize a serialização JSON ou outro formato de serialização tolerante a versão

O código evolui e isto inclui frequentemente tipos de armazenamento, também. Para acomodar estas alterações, deve ser configurado um serializer apropriado. Para a maioria dos fornecedores de armazenamento, uma UseJson opção ou similar está disponível para usar o JSON como formato de serialização. Certifique-se de que, ao evoluir em contratos de dados, os dados já armazenados continuarão a ser carregados.

Usando o Estado-Maior<> para adicionar armazenamento a um grão

Importante

A utilização Grain<T> para adicionar armazenamento a um grão é considerada uma funcionalidade antiga : o armazenamento de cereais deve ser adicionado como IPersistentState<T> descrito anteriormente.

As classes de Grain<T> cereais que herdam (onde T é um tipo de dados de estado específico de aplicação que precisa de ser persistido) terão o seu estado carregado automaticamente a partir de armazenamento especificado.

Estes grãos são marcados com um StorageProviderAttribute que especifica uma instância nomeada de um fornecedor de armazenamento para usar para ler/escrever os dados do estado para este grão.

[StorageProvider(ProviderName="store1")]
public class MyGrain : Grain<MyGrainState>, /*...*/
{
  /*...*/
}

A Grain<T> classe base definiu os seguintes métodos para as subclasses chamarem:

protected virtual Task ReadStateAsync() { /*...*/ }
protected virtual Task WriteStateAsync() { /*...*/ }
protected virtual Task ClearStateAsync() { /*...*/ }

O comportamento destes métodos corresponde aos seus homólogos definidos IPersistentState<TState> anteriormente.

Criar um fornecedor de armazenamento

Existem duas partes para as APIs de persistência do Estado: a API exposta ao grão via IPersistentState<T> ou Grain<T>, e o fornecedor de armazenamento API, que está centrado em torno IGrainStorage - a interface que os fornecedores de armazenamento devem implementar:

/// <summary>
/// Interface to be implemented for a storage able to read and write Orleans grain state data.
/// </summary>
public interface IGrainStorage
{
    /// <summary>Read data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">State data object to be populated for this grain.</param>
    /// <returns>Completion promise for the Read operation on the specified grain.</returns>
    Task ReadStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);

    /// <summary>Write data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">State data object to be written for this grain.</param>
    /// <returns>Completion promise for the Write operation on the specified grain.</returns>
    Task WriteStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);

    /// <summary>Delete / Clear data function for this storage instance.</summary>
    /// <param name="grainType">Type of this grain [fully qualified class name]</param>
    /// <param name="grainReference">Grain reference object for this grain.</param>
    /// <param name="grainState">Copy of last-known state data object for this grain.</param>
    /// <returns>Completion promise for the Delete operation on the specified grain.</returns>
    Task ClearStateAsync(
        string grainType, GrainReference grainReference, IGrainState grainState);
}

Crie um fornecedor de armazenamento personalizado implementando esta interface e registando essa implementação. Para um exemplo de uma implementação existente do fornecedor de armazenamento, consulte AzureBlobGrainStorage.

Semântica de provedor de Armazenamento

Um valor específico Etag do fornecedor opaco (string) pode ser definido por um fornecedor de armazenamento como parte dos metadados do estado de cereais povoados quando o estado foi lido. Alguns fornecedores podem optar por deixar isto como null se não usassem Etags.

Qualquer tentativa de efetuar uma operação de escrita quando o fornecedor de armazenamento detetar uma Etag violação de restrição deve fazer com que a escrita Task seja falhada com erro InconsistentStateException transitório e embrulhendo a exceção de armazenamento subjacente.

public class InconsistentStateException : OrleansException
{
    public InconsistentStateException(
    string message,
    string storedEtag,
    string currentEtag,
    Exception storageException)
        : base(message, storageException)
    {
        StoredEtag = storedEtag;
        CurrentEtag = currentEtag;
    }

    public InconsistentStateException(
        string storedEtag,
        string currentEtag,
        Exception storageException)
        : this(storageException.Message, storedEtag, currentEtag, storageException)
    {
    }

    /// <summary>The Etag value currently held in persistent storage.</summary>
    public string StoredEtag { get; }

    /// <summary>The Etag value currently held in memory, and attempting to be updated.</summary>
    public string CurrentEtag { get; }
}

Quaisquer outras condições de avaria de uma operação de armazenamento devem fazer com que a desemargem Task seja quebrada com uma exceção que indique a questão de armazenamento subjacente. Em muitos casos, esta exceção pode ser ressarcido para o chamador que desencadeou a operação de armazenamento, chamando um método para o grão. É importante considerar se o chamador será ou não capaz de deserizar esta exceção. Por exemplo, o cliente pode não ter carregado a biblioteca de persistência específica contendo o tipo de exceção. Por esta razão, é aconselhável converter exceções em exceções que podem ser propagadas de volta ao chamador.

Mapeamento de dados

Os fornecedores individuais de armazenamento devem decidir qual a melhor forma de armazenar o estado dos cereais – blob (vários formatos / formas serializadas) ou coluna-por-campo são escolhas óbvias.

Registar um fornecedor de armazenamento

O tempo de execução de Orleans resolverá um fornecedor de armazenamento do prestador de serviços (IServiceProvider) quando um grão é criado. O tempo de execução resolverá um caso de IGrainStorage. Se o fornecedor de armazenamento for nomeado, por exemplo através do [PersistentState(stateName, storageName)] atributo, então uma instância nomeada IGrainStorage será resolvida.

Para registar uma instância nomeada, utilize o AddSingletonNamedService método de IGrainStorageextensão seguindo o exemplo do fornecedor AzureTableGrainStorage aqui.