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
- Vários objetos de dados persistentes nomeados por grão.
- 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.
- Armazenamento fornecedores podem ser desenvolvidos e publicados pela comunidade.
- 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:
- Microsoft.Orleans.Persistence.AdoNet destina-se a SQL bases de dados e outros sistemas de armazenamento suportados por ADO.NET. Para mais informações, consulte ADO.NET Persistência de Cereais.
- Microsoft.Orleans.Persistence.AzureStorage é para Azure Armazenamento, incluindo Armazenamento de Blobs do Azure, Azure Table Armazenamento, e Azure CosmosDB, através da Tabela Azure Armazenamento API. Para mais informações, consulte Azure Armazenamento Grain Persistence.
- Microsoft.Orleans.Persistence.DynamoDB é para o Amazon DynamoDB. Para mais informações, consulte a Amazon DynamoDB Grain Persistence.
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:
- Injetando
IPersistentState<TState>
no construtor do grão. - 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()
Task
e 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 Etag
s.
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 IGrainStorage
extensão seguindo o exemplo do fornecedor AzureTableGrainStorage aqui.