Condividi tramite


Persistenza della granulosità

Ai grani possono essere associati più oggetti dati persistenti denominati. Questi oggetti di stato vengono caricati dall'archiviazione durante l'attivazione granulare in modo che siano disponibili durante le richieste. La persistenza granulare usa un modello di plug-in estendibile in modo che sia possibile usare i provider di archiviazione per qualsiasi database. Questo modello di persistenza è progettato per semplicità e non è progettato per coprire tutti i modelli di accesso ai dati. I grani possono anche accedere direttamente ai database, senza usare il modello di persistenza granulosità.

Nel diagramma precedente UserGrain ha uno stato Profilo e uno stato Carrello, ognuno dei quali viene archiviato in un sistema di archiviazione separato.

Obiettivi

  1. Più oggetti dati persistenti denominati per granularità.
  2. Più provider di archiviazione configurati, ognuno dei quali può avere una configurazione diversa ed essere supportato da un sistema di archiviazione diverso.
  3. Archiviazione provider possono essere sviluppati e pubblicati dalla community.
  4. Archiviazione provider hanno il controllo completo sul modo in cui archiviano i dati dello stato di granularità nell'archivio di backup permanente. Corollary: Orleans non offre una soluzione di archiviazione ORM completa, ma consente invece ai provider di archiviazione personalizzati di supportare requisiti ORM specifici, come e quando necessario.

Pacchetti

I provider di archiviazione per il grano di Orleans sono disponibili NuGet. I pacchetti gestiti ufficialmente includono:

API

I grani interagiscono con lo stato persistente usando IPersistentState<TState> dove TState è il tipo di stato serializzabile:

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

Le istanze di IPersistentState<TState> vengono inserite nel grano come parametri del costruttore. Questi parametri possono essere annotati con un PersistentStateAttribute attributo per identificare il nome dello stato inserito e il nome del provider di archiviazione che lo fornisce. L'esempio seguente illustra questa operazione tramite l'inserimento di due stati denominati nel UserGrain costruttore :

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

Tipi di granularità diversi possono usare provider di archiviazione configurati diversi, anche se entrambi sono dello stesso tipo. ad esempio due diverse istanze del provider Archiviazione tabelle di Azure, connesse a account Archiviazione di Azure diversi.

Stato di lettura

Lo stato di granulosità verrà letto automaticamente quando la granulosità viene attivata, ma i grani sono responsabili dell'attivazione esplicita della scrittura per qualsiasi stato di granulosità modificato quando necessario.

Se un chicco vuole ri-leggere in modo esplicito lo stato più recente per questo grano dall'archivio sottostante, il grano deve chiamare il ReadStateAsync metodo . In questo modo lo stato grain verrà ricaricato dall'archivio permanente tramite il provider di archiviazione e la copia in memoria TaskReadStateAsync() precedente dello stato grain verrà sovrascritta e sostituita al termine dell'operazione da .

Il valore dello stato è accessibile tramite la State proprietà . Ad esempio, il metodo seguente accede allo stato del profilo dichiarato nel codice precedente:

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

Non è necessario chiamare durante il normale ReadStateAsync() funzionamento. Lo stato viene caricato automaticamente durante l'attivazione. Tuttavia, ReadStateAsync() può essere usato per aggiornare lo stato che viene modificato esternamente.

Per informazioni dettagliate sui meccanismi di gestione degli errori, vedere la sezione Modalità di errore riportata di seguito.

Stato scrittura

Lo stato può essere modificato tramite la State proprietà . Lo stato modificato non viene salvato automaticamente in modo permanente. Lo sviluppatore decide invece quando rendere persistente lo stato chiamando il WriteStateAsync metodo . Ad esempio, il metodo seguente aggiorna una proprietà in State e rende persistente lo stato aggiornato:

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

Concettualmente, il runtime di Orleans accetta una copia completa dell'oggetto dati dello stato granulare per l'uso durante qualsiasi operazione di scrittura. In alcuni casi, il runtime può usare regole di ottimizzazione ed euristica per evitare di eseguire una o tutte le operazioni di copia completa, a condizione che venga mantenuta la semantica di isolamento logico prevista.

Per informazioni dettagliate sui meccanismi di gestione degli errori, vedere la sezione Modalità di errore riportata di seguito.

Cancella stato

Il ClearStateAsync metodo cancella lo stato del grano nell'archiviazione. A seconda del provider, questa operazione può facoltativamente eliminare completamente lo stato grain.

Introduzione

Prima che una granularità possa usare la persistenza, è necessario configurare un provider di archiviazione nel silo.

Configurare prima di tutto i provider di archiviazione, uno per lo stato del profilo e uno per lo stato del carrello:

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

Ora che un provider di archiviazione è stato configurato con il nome "profileStore", è possibile accedere a questo provider da una grana.

Lo stato persistente può essere aggiunto a una granularità in due modi principali:

  1. Inserimento nel IPersistentState<TState> costruttore del grano.
  2. Ereditando da Grain<TGrainState>.

Il modo consigliato per aggiungere spazio di archiviazione a una IPersistentState<TState> granularità è inserire nel costruttore del grano un attributo [PersistentState("stateName", "providerName")] associato. Per informazioni dettagliate su , vedere di seguito. Questo è ancora supportato, ma è considerato un approccio legacy.

Dichiarare una classe per contenere lo stato del grano:

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

    public Date DateOfBirth
}

Inserire nel IPersistentState<ProfileState> costruttore del grano:

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

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

Nota

Lo stato del profilo non verrà caricato nel momento in cui viene inserito nel costruttore, pertanto l'accesso non è valido in quel momento. Lo stato verrà caricato prima della OnActivateAsync chiamata a .

Ora che lo stato del grano è persistente, è possibile aggiungere metodi per leggere e scrivere lo stato:

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

Modalità di errore per le operazioni di persistenza

Modalità di errore per le operazioni di lettura

Gli errori restituiti dal provider di archiviazione durante la lettura iniziale dei dati di stato per quel particolare grano avranno esito negativo nell'operazione di attivazione per tale granulosità. In tal caso, non sarà presente alcuna chiamata al metodo di callback del ciclo di vita di tale granularità. La richiesta originale al grano che ha causato l'attivazione verrà tornata al chiamante, come qualsiasi altro errore durante l'attivazione del grano. Gli errori rilevati dal provider di archiviazione durante la lettura dei dati di stato per una particolare granularità generano un'eccezione da ReadStateAsync()Task. La granulosità può scegliere di gestire o ignorare l'eccezione Task , proprio come qualsiasi altra Task in Orleans.

Qualsiasi tentativo di inviare un messaggio a un grano che non è stato caricato in fase di avvio del silo a causa di una configurazione del provider di archiviazione mancante o non valido restituirà l'errore permanente BadProviderConfigException.

Modalità di errore per le operazioni di scrittura

Gli errori rilevati dal provider di archiviazione durante la scrittura dei dati di stato per una particolare granularità generano un'eccezione generata da WriteStateAsync()Task. In genere, ciò significa che l'eccezione di chiamata grain verrà generata di nuovo al chiamante client, WriteStateAsync()TaskTask a condizione che sia concatenato correttamente al risultato finale per questo metodo grain. Tuttavia, in alcuni scenari avanzati è possibile scrivere codice granulare per gestire in modo specifico tali errori di scrittura, proprio come possono gestire qualsiasi altro errore Task.

I grani che eseguono codice di gestione degli errori/recupero devono intercettare eccezioni/Taskerrori e non generarle di nuovo, per indicare che l'errore di scrittura è stato gestito correttamente.

Consigli

Usare la serializzazione JSON o un altro formato di serializzazione a tolleranza di versione

Il codice si evolve e questo include spesso anche i tipi di archiviazione. Per supportare queste modifiche, è necessario configurare un serializzatore appropriato. Per la maggior parte dei provider di archiviazione, UseJson è disponibile un'opzione o un'opzione simile per usare JSON come formato di serializzazione. Assicurarsi che durante l'evoluzione dei contratti dati i dati già archiviati siano ancora caricabili.

Uso di GrainTState<> per aggiungere spazio di archiviazione a una granularità

Importante

L'uso Grain<T> di per aggiungere spazio di archiviazione a una granularità è considerato Grain<T> : l'archiviazione granulare deve essere aggiunta usando come IPersistentState<T> descritto in precedenza.

Le classi grain che ereditano da Grain<T> ( T dove è un tipo di dati di stato specifico dell'applicazione che deve essere reso persistente) avranno lo stato caricato automaticamente dalla risorsa di archiviazione specificata.

Tali grani sono contrassegnati con un che StorageProviderAttribute specifica un'istanza denominata di un provider di archiviazione da usare per la lettura/scrittura dei dati di stato per questa granularità.

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

La Grain<T> classe di base ha definito i metodi seguenti per le sottoclassi da chiamare:

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

Il comportamento di questi metodi corrisponde alle controparti definite in IPersistentState<TState> precedenza.

Creare un provider di archiviazione

Le API di persistenza dello stato sono due: l'API IPersistentState<T>Grain<T>esposta al grano tramite o e l'API del provider di archiviazione, IGrainStorage che è centrata, ovvero l'interfaccia che i provider di archiviazione devono implementare:

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

Creare un provider di archiviazione personalizzato implementando questa interfaccia e registrando tale implementazione. Per un esempio di implementazione del provider di archiviazione esistente, vedere AzureBlobGrainStorage.

Archiviazione semantica del provider

Un valore specifico del provider Etag opaco (stringEtag essere impostato da un provider di archiviazione come parte dei metadati dello stato grain popolati durante la lettura dello stato. Alcuni provider possono scegliere di lasciare questa opzione null come se non usavano Etags.

Qualsiasi tentativo di eseguire un'operazione di scrittura quando il provider EtagEtagTaskInconsistentStateException di archiviazione rileva una violazione del vincolo deve causare l'errore di scrittura con errore temporaneo e il wrapping dell'eccezione di archiviazione sottostante.

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

Qualsiasi altra condizione di errore da un'operazione di archiviazione deve causare l'errore restituito con un'eccezione che indica il problema di archiviazione sottostante. In molti casi, questa eccezione può essere generata di nuovo al chiamante che ha attivato l'operazione di archiviazione chiamando un metodo sulla granularità . È importante valutare se il chiamante sarà in grado di deserializzare questa eccezione. Ad esempio, il client potrebbe non avere caricato la libreria di persistenza specifica contenente il tipo di eccezione. Per questo motivo, è consigliabile convertire le eccezioni in eccezioni che possono essere propagate nuovamente al chiamante.

Mapping dei dati

I singoli provider di archiviazione devono decidere come archiviare al meglio lo stato di granularità: blob (vari formati/moduli serializzati) o colonna per campo sono scelte ovvie.

Registrare un provider di archiviazione

Il runtime Disaccoglierà un provider di archiviazione dal provider di servizi (IServiceProvider) quando viene creata una granularità. Il runtime risolverà un'istanza di IGrainStorage. Se il provider di archiviazione è denominato, ad esempio tramite l'attributo [PersistentState(stateName, storageName)] , verrà risolta un'istanza denominata IGrainStorage di .

Per registrare un'istanza denominata di IGrainStorage, usare il metodo AddSingletonNamedService di estensione seguendo l'esempio del IGrainStorage.