Udostępnij za pośrednictwem


Trwałość ziarna

Ziarna mogą mieć wiele nazwanych trwałych obiektów danych skojarzonych z nimi. Te obiekty stanu ładują się z magazynu podczas aktywacji ziarna, aby były dostępne podczas żądań. Mechanizm trwałości ziarna wykorzystuje rozszerzalny model wtyczki, co umożliwia korzystanie z dostawców pamięci masowej dla dowolnej bazy danych. Ten model trwałości jest przeznaczony dla uproszczenia i nie jest przeznaczony do obsługi wszystkich wzorców dostępu do danych. Ziarna mogą również uzyskiwać dostęp do baz danych bezpośrednio bez korzystania z modelu trwałości ziarna.

Diagram trwałości ziarna

Na wcześniejszym diagramie element UserGrain ma stan Profile i stan Cart, z których każdy jest przechowywany w osobnym systemie przechowywania.

Cele

  1. Obsługa wielu nazwanych obiektów danych trwałych na ziarno.
  2. Zezwalaj na wielu skonfigurowanych dostawców przechowywania, z których każdy z nich potencjalnie ma inną konfigurację i jest obsługiwany przez inny system przechowywania.
  3. Umożliwiaj społeczności tworzenie i publikowanie dostawców przechowywania.
  4. Nadaj dostawcom magazynu pełną kontrolę nad sposobem przechowywania danych stanu ziarna w trwałym magazynie zapasowym. Corollary: Orleans nie zapewnia kompleksowego rozwiązania magazynu ORM, ale umożliwia niestandardowym dostawcom magazynu obsługę określonych wymagań ORM zgodnie z potrzebami.

Pakiety

Dostawcy magazynów dla zbóż są dostępni Orleans na platformie NuGet. Oficjalnie obsługiwane pakiety obejmują:

API (Interfejs Programowania Aplikacji)

Ziarna współdziałają ze stanem trwałym przy użyciu IPersistentState<TState>, gdzie TState jest serializowalnym typem stanu:

public interface IPersistentState<TState> : IStorage<TState>
{
}

public interface IStorage<TState> : IStorage
{
    TState State { get; set; }
}

public interface IStorage
{
    string Etag { get; }

    bool RecordExists { get; }

    Task ClearStateAsync();

    Task WriteStateAsync();

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

    string Etag { get; }

    Task ClearStateAsync();

    Task WriteStateAsync();

    Task ReadStateAsync();
}

Orleans wstrzykuje instancje IPersistentState<TState> do ziarna jako parametry konstruktora. Możesz dodać adnotacje do tych parametrów za pomocą PersistentStateAttribute atrybutu, aby zidentyfikować nazwę wprowadzonego stanu i nazwę dostawcy magazynu, który go dostarcza. W poniższym przykładzie pokazano to przez wstrzyknięcie dwóch nazwanych stanów do konstruktora UserGrain :

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

Różne typy zbóż mogą używać różnych skonfigurowanych dostawców pamięci masowej, nawet jeśli oba są tego samego typu (np. dwa różne wystąpienia dostawcy pamięci masowej Azure Table Storage połączone z różnymi kontami Azure Storage).

Stan odczytu

Stan ziarna jest automatycznie odczytywany, gdy ziarno aktywuje się, ale ziarna są odpowiedzialne za jawne wyzwalanie zapisu dla dowolnego zmienionego stanu ziarna w razie potrzeby.

Jeśli ziarno chce jawnie ponownie odczytać swój najnowszy stan z magazynu zapasowego, powinno wywołać metodę ReadStateAsync. Spowoduje to ponowne załadowanie stanu ziarna z magazynu trwałego za pośrednictwem dostawcy magazynu. Poprzednia kopia stanu ziarna w pamięci jest nadpisywana i zastępowana po zakończeniu Task z ReadStateAsync().

Uzyskaj dostęp do wartości stanu przy użyciu State właściwości . Na przykład następująca metoda uzyskuje dostęp do stanu profilu zadeklarowany w powyższym kodzie:

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

Nie ma potrzeby wywoływania ReadStateAsync() podczas normalnego działania; Orleans ładuje stan automatycznie podczas aktywacji. Można jednak użyć ReadStateAsync() polecenia , aby odświeżyć stan zmodyfikowany zewnętrznie.

Aby uzyskać szczegółowe informacje na temat mechanizmów obsługi błędów, zobacz sekcję Tryby awarii poniżej.

Stan zapisywania

Stan można zmodyfikować za pomocą State właściwości . Zmodyfikowany stan nie jest automatycznie utrwalany. Zamiast tego decydujesz, kiedy należy utrwały stan, wywołując metodę WriteStateAsync . Na przykład następująca metoda aktualizuje właściwość w State i zachowuje zaktualizowany stan.

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

Koncepcyjnie środowisko Orleans uruchomieniowe pobiera głęboką kopię obiektu danych stanu ziarna do użycia podczas jakichkolwiek operacji zapisu. Zgodnie z opisami środowisko uruchomieniowe może używać reguł optymalizacji i algorytmów heurystycznych, aby uniknąć wykonywania niektórych lub wszystkich kopii głębokiej w pewnych okolicznościach, pod warunkiem, że oczekiwane semantyki izolacji logicznej są zachowywane.

Aby uzyskać szczegółowe informacje na temat mechanizmów obsługi błędów, zobacz sekcję Tryby awarii poniżej.

Wyczyść stan

Metoda ClearStateAsync czyści stan ziarna w magazynie. W zależności od dostawcy ta operacja może opcjonalnie całkowicie usunąć stan ziarna.

Rozpocznij

Przed tym jak ziarno będzie mogło wykorzystać trwałość, należy skonfigurować dostawcę pamięci w silosie.

Najpierw skonfiguruj dostawców magazynu, jeden dla stanu profilu i jeden dla stanu koszyka:

using IHost host = new HostBuilder()
    .UseOrleans(siloBuilder =>
    {
        siloBuilder.AddAzureTableGrainStorage(
            name: "profileStore",
            configureOptions: options =>
            {
                // Configure the storage connection key
                options.ConfigureTableServiceClient(
                    "DefaultEndpointsProtocol=https;AccountName=data1;AccountKey=SOMETHING1");
            })
            .AddAzureBlobGrainStorage(
                name: "cartStore",
                configureOptions: options =>
                {
                    // Configure the storage connection key
                    options.ConfigureTableServiceClient(
                        "DefaultEndpointsProtocol=https;AccountName=data2;AccountKey=SOMETHING2");
                });
    })
    .Build();
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();

Ważne

Firma Microsoft zaleca korzystanie z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Jeśli łączysz się z usługą Azure SQL, zalecaną metodą uwierzytelniania jest użycie tożsamości zarządzanych dla zasobów platformy Azure.

Po skonfigurowaniu dostawcy magazynu o nazwie "profileStore"można uzyskać dostęp do tego dostawcy z poziomu ziarna.

Stan trwały można dodać do ziarna na dwa podstawowe sposoby:

  1. Wstrzykując IPersistentState<TState> w konstruktor ziarna.
  2. Dziedzicząc z Grain<TGrainState>.

Zalecanym sposobem dodawania pamięci do ziarna jest wstrzykiwanie IPersistentState<TState> do jego konstruktora ze skojarzonym atrybutem [PersistentState("stateName", "providerName")]. Aby uzyskać szczegółowe informacje na temat Grain<TState>, zobacz Używanie Grain<TState> do dodawania magazynu do ziarna poniżej. Użycie Grain<TState> nadal jest obsługiwane, ale jest uznawane za starsze podejście.

Zadeklaruj klasę, która będzie przechowywać stan Twojego ziarna.

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

    public Date DateOfBirth { get; set; }
}

Wstrzyknij IPersistentState<ProfileState> do konstruktora ziarna.

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

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

Ważne

Stan profilu użytkownika nie zostanie załadowany w momencie wstrzyknięcia go do konstruktora, więc dostęp do niego w tym czasie jest nieprawidłowy. Stan zostanie załadowany zanim OnActivateAsync zostanie wywołane.

Teraz, gdy ziarno ma stan trwały, możesz dodać metody odczytu i zapisu stanu:

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

Tryby awarii dla operacji utrwalania

Tryby awarii dla operacji odczytu

Błędy zwrócone przez dostawcę magazynu podczas początkowego odczytu danych stanu dla określonego ziarna kończą się niepowodzeniem operacji aktywacji dla tego ziarna. W takich przypadkach metoda zwrotna cyklu życia dla tego ziarna nie zostanie wywołana. Oryginalne żądanie do ziarna, które spowodowało błędy aktywacji, jest zwracane do wywołującego, tak jak w przypadku każdego innego błędu podczas aktywacji ziarna. Błędy napotkane przez dostawcę magazynu podczas odczytywania danych o stanie dla określonego ziarna powodują wyjątek od .ReadStateAsyncTask Ziarno może obsługiwać lub ignorować Task wyjątek, podobnie jak każdy inny Task w Orleans.

Każda próba wysłania komunikatu do ziarna, które nie załadowało się przy starcie silosu z powodu braku lub nieprawidłowej konfiguracji dostawcy magazynu, zwraca błąd trwały BadProviderConfigException.

Tryby awarii dla operacji zapisu

Błędy napotkane przez dostawcę magazynu podczas zapisywania danych o stanie dla określonego ziarna powodują wyjątek zgłoszony przez element WriteStateAsync()Task. Zazwyczaj oznacza to, że wyjątek wywołania ziarna jest zwracany do wywołującego klienta, pod warunkiem, że element jest poprawnie w łańcuchu WriteStateAsync()Task do końcowego zwrotu Task dla tej metody ziarna. Jednak w niektórych zaawansowanych scenariuszach można napisać kod ziarna, aby szczególnie zajmować się takimi błędami zapisu, podobnie jak w przypadku obsługi innych błędów Task.

Ziarna wykonujące obsługę błędów lub kod odzyskiwania muszą przechwytywać wyjątki lub błędy WriteStateAsync()Task, a nie ponownie je wyrzucać, co oznacza, że pomyślnie obsłużyły błąd zapisu.

Rekomendacje

Użyj serializacji JSON lub innego formatu serializacji odpornej na wersje

Kod ewoluuje i często obejmuje typy przechowywania. Aby uwzględnić te zmiany, skonfiguruj odpowiedni serializator. W przypadku większości dostawców magazynu dostępna jest opcja UseJson lub podobna, aby użyć JSON jako formatu serializacji. Upewnij się, że podczas ewolucji kontraktów danych nadal można załadować przechowywane dane.

Dodawanie magazynu do ziarna przy użyciu Grain<TState>

Ważne

Użycie Grain<T> do dodawania przestrzeni magazynowej dla ziarna jest uznawane za funkcjonalność starszego typu. Dodaj magazyn ziarna zgodnie z wcześniejszym opisem, używając IPersistentState<T>.

Klasy ziarna dziedziczone z Grain<T> (gdzie T jest typem danych stanu specyficznego dla aplikacji, wymagający trwałości) mają swój stan ładowany automatycznie z określonej pamięci.

Oznacz takie ziarna przy pomocy StorageProviderAttribute, określając nazwaną instancję dostawcy magazynu do odczytu/zapisu danych stanu dla tego ziarna.

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

Klasa Grain<T> bazowa definiuje następujące metody dla podklas do wywołania:

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

Zachowanie tych metod odpowiada ich wcześniej zdefiniowanym odpowiednikom na IPersistentState<TState>.

Stwórz dostawcę przechowywania

Istnieją dwie części interfejsów API trwałości stanu: interfejs API dostępny dla ziarna za pośrednictwem IPersistentState<T> lub Grain<T>, oraz interfejs API dostawcy magazynu, skoncentrowany na IGrainStorage—który muszą zaimplementować dostawcy magazynu.

/// <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="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be populated for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Read operation on the specified grain.</returns>
    Task ReadStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Write data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">State data object to be written for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Write operation on the specified grain.</returns>
    Task WriteStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);

    /// <summary>Delete / Clear data function for this storage instance.</summary>
    /// <param name="stateName">Name of the state for this grain</param>
    /// <param name="grainId">Grain ID</param>
    /// <param name="grainState">Copy of last-known state data object for this grain.</param>
    /// <typeparam name="T">The grain state type.</typeparam>
    /// <returns>Completion promise for the Delete operation on the specified grain.</returns>
    Task ClearStateAsync<T>(
        string stateName, GrainId grainId, IGrainState<T> grainState);
}
/// <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);
}

Aby stworzyć własnego dostawcę pamięci masowej, zaimplementuj ten interfejs i zarejestruj tę implementację. Aby zapoznać się z przykładem istniejącej implementacji dostawcy magazynu, zobacz AzureBlobGrainStorage.

Semantyka dostawcy pamięci masowej

Nieprzezroczysta wartość typowa dla Etag dostawcy (string) może zostać ustawiona przez dostawcę magazynu w metadanych stanu ziarna w trakcie odczytu stanu. Niektórzy dostawcy mogą zdecydować się na pozostawienie tego jako null, jeśli nie używają Etag.

Każda próba wykonania operacji zapisu, gdy dostawca magazynu wykryje Etag naruszenie ograniczeń , powinno spowodować , że zapis Task zostanie uszkodzony z błędem przejściowym InconsistentStateException i zawijaniem bazowego wyjątku magazynu.

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

Wszelkie inne warunki awarii z operacji magazynu muszą spowodować, że zwrócony Task będzie uszkodzony z wyjątkiem wskazującym problem z podstawowym magazynem. W wielu przypadkach ten wyjątek może zostać zwrócony do obiektu wywołującego, który wyzwolił operację przechowywania, wywołując metodę na ziarnie. Ważne jest, aby rozważyć, czy wywołujący może deserializować ten wyjątek. Na przykład klient może nie załadować określonej biblioteki trwałości zawierającej typ wyjątku. Z tego powodu zaleca się przekonwertowanie wyjątków na takie, które mogą być propagowane z powrotem do wywołującego.

Mapowanie danych

Indywidualni dostawcy magazynowania powinni zdecydować, jak najlepiej przechowywać stan ziarna – blob (różne formaty / zserializowane formy) lub kolumna na pole to oczywiste wybory.

Rejestrowanie dostawcy magazynu

Środowisko Orleans uruchomieniowe rozpoznaje dostawcę magazynu od dostawcy usług (IServiceProvider) podczas tworzenia ziarna. Środowisko uruchomieniowe rozpoznaje wystąpienie klasy IGrainStorage. Jeśli dostawca magazynu ma nazwę (na przykład za pośrednictwem atrybutu [PersistentState(stateName, storageName)]), to zostaje ustalone nazwane wystąpienie IGrainStorage.

Aby zarejestrować nazwane wystąpienie IGrainStorage, użyj metody rozszerzenia AddSingletonNamedService, postępując zgodnie z przykładem dostawcy AzureTableGrainStorage tutaj.