Trwałość ziarna

Z ziarnami może być skojarzonych wiele nazwanych trwałych obiektów danych. Te obiekty stanu są ładowane z magazynu podczas aktywacji ziarna, aby były dostępne podczas żądań. Trwałość ziarna używa rozszerzalnych modeli wtyczek, dzięki czemu można używać dostawców magazynu dla dowolnej bazy danych. Ten model trwałości został zaprojektowany dla uproszczenia i nie jest przeznaczony do obsługi wszystkich wzorców dostępu do danych. Ziarno może również uzyskać bezpośredni dostęp do baz danych bez użycia modelu trwałości ziarna.

Na powyższym diagramie userGrain ma stan Profil i Stan koszyka, z których każdy jest przechowywany w oddzielnym systemie magazynowania.

Cele

  1. Wiele nazwanych trwałych obiektów danych na ziarno.
  2. Wielu skonfigurowanych dostawców magazynu, z których każdy może mieć inną konfigurację i może być zapasowy przez inny system magazynowania.
  3. Storage mogą być opracowywani i publikowani przez społeczność.
  4. Storage mają pełną kontrolę nad tym, jak przechowują dane stanu ziarna w trwałym magazynie kopii zapasowej. Corollary: Orleans is not providing a comprehensive ORM storage solution, but instead allows custom storage providers to support specific ORM requirements as and when required.

Pakiety

Dostawców magazynu ziarna wartości logicznych można znaleźć na NuGet. Oficjalnie utrzymywane pakiety obejmują:

interfejs API

Ziarno wchodzi w interakcję ze stanem trwałym przy użyciu IPersistentState<TState> typu stanu, TState gdzie jest typem stanu, który można serializuje:

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

Wystąpienia klasy są IPersistentState<TState> wstrzykiwane do ziarna jako parametry konstruktora. Te parametry mogą być oznaczone za PersistentStateAttribute pomocą atrybutu , aby zidentyfikować nazwę wstrzykiwanego stanu i nazwę dostawcy magazynu, który je udostępnia. Poniższy przykład ilustruje 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 ziarna mogą używać różnych skonfigurowanych dostawców magazynu, nawet jeśli oba typy są tego samego typu; na przykład dwa różne wystąpienia dostawcy Storage Azure Table połączone z różnymi kontami usługi Azure Storage.

Stan odczytu

Stan ziarna zostanie automatycznie odczytany po aktywowaniu ziarna, ale w razie potrzeby ziarno jest odpowiedzialne za jawne wyzwalanie zapisu dla dowolnego zmienionego stanu ziarna.

Jeśli ziarno chce jawnie ponownie odczytać najnowszy stan tego ziarna z magazynu zapasowego, ziarno powinno wywołać metodę ReadStateAsync . Spowoduje to ponowne załadowanie stanu ziarna z magazynu trwałego za pośrednictwem dostawcy magazynu, TaskReadStateAsync() a poprzednia kopia stanu ziarna w pamięci zostanie zastąpiona i zastąpiona po zakończeniu procesu od.

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

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

Nie ma potrzeby wywołania podczas ReadStateAsync() normalnego działania; stan jest ładowany automatycznie podczas aktywacji. Można jednak ReadStateAsync() użyć do odświeżenia stanu, który jest modyfikowany zewnętrznie.

Zobacz sekcję Tryby awarii poniżej, aby uzyskać szczegółowe informacje o mechanizmach obsługi błędów.

Stan zapisu

Stan można zmodyfikować za pomocą State właściwości . Zmodyfikowany stan nie jest automatycznie utrwalany. Zamiast tego deweloper decyduje o tym, kiedy utrwalić stan, wywołując WriteStateAsync metodę . Na przykład następująca metoda aktualizuje właściwość na i State utrwala zaktualizowany stan:

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

Koncepcyjnie środowisko uruchomieniowe Orleans Runtime użyje głębokiej kopii obiektu danych stanu ziarna do użycia podczas wszystkich operacji zapisu. W środowisku uruchomieniowym można użyć reguł optymalizacji i heurystyki, aby uniknąć wykonywania niektórych lub wszystkich kopii głębokich w pewnych okolicznościach, pod warunkiem, że oczekiwana semantyka izolacji logicznej zostanie zachowana.

Zobacz sekcję Tryby awarii poniżej, aby uzyskać szczegółowe informacje o mechanizmach obsługi błędów.

Wyczyść stan

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

Rozpoczęcie pracy

Aby można było użyć trwałości dla ziarna, należy skonfigurować dostawcę magazynu w silosie.

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

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

Teraz, gdy dostawca magazynu został skonfigurowany z "profileStore"nazwą , możemy uzyskać dostęp do tego dostawcy z ziarnem.

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

  1. Przez wstrzyknięcie IPersistentState<TState> do konstruktora ziarna.
  2. Dziedzicząc z .Grain<TGrainState>

Zalecanym sposobem dodania magazynu do ziarna IPersistentState<TState> jest wstrzyknięcie do konstruktora ziarna za pomocą skojarzonego [PersistentState("stateName", "providerName")] atrybutu. Aby uzyskać szczegółowe informacje na temat , zobacz poniżej. Ta metoda jest nadal obsługiwana, ale jest uznawana za starszą.

Zadeklaruj klasę do przechowywania stanu ziarna:

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

    public Date DateOfBirth
}

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

Uwaga

Stan profilu nie zostanie załadowany w momencie jego iniekcji do konstruktora, więc uzyskanie do niego dostępu jest nieprawidłowe w tym czasie. Stan zostanie załadowany przed wywoływaniem OnActivateAsync .

Teraz, gdy ziarno ma stan trwały, możemy 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 trwałości

Tryby awarii dla operacji odczytu

Błędy zwracane przez dostawcę magazynu podczas początkowego odczytu danych o stanie dla tego konkretnego ziarna nie powiodą operacji aktywacji dla tego ziarna; W takim przypadku nie będzie żadnych wywołań metody wywołania zwrotnego cyklu życia tego ziarna. Oryginalne żądanie do ziarna, które spowodowało aktywację, zostanie uszkodzone z powrotem do wywołującego, tak samo jak każde inne niepowodzenie podczas aktywacji ziarna. Błędy napotkane przez dostawcę magazynu podczas odczytywania danych o stanie dla określonego ziarna spowoduje wyjątek od .ReadStateAsync()Task Ziarno może obsługiwać lub ignorować Task wyjątek, podobnie jak inne w Task orleanie.

Każda próba wysłania komunikatu do ziarna, które nie powiodło się podczas uruchamiania silosu z powodu brakującej/złej konfiguracji dostawcy magazynu, zwróci 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 spowoduje wyjątek zgłaszany przez usługę WriteStateAsync()Task. Zazwyczaj oznacza to, że wyjątek wywołania ziarna zostanie zwrócony do wywołującego klienta, pod warunkiem, WriteStateAsync()TaskTask że metoda jest poprawnie w łańcuchu w końcowym zwracaniu dla tej metody grain. Jednak w niektórych zaawansowanych scenariuszach istnieje możliwość napisania kodu ziarnistego w celu obsługi takich błędów zapisu, podobnie jak w przypadku każdego innego błędu Task.

Ziarna, które wykonują kod obsługi błędów/odzyskiwania, muszą przechwycić wyjątki /Taskbłędy i nie zgłaszać ich ponownie, aby oznaczać, że pomyślnie obsłużyły błąd zapisu.

Zalecenia

Używanie serializacji JSON lub innego formatu serializacji z tolerancją dla wersji

Kod ewoluuje i często obejmuje również typy magazynów. Aby uwzględnić te zmiany, należy skonfigurować odpowiedni serializator. W przypadku większości dostawców magazynu dostępna UseJson jest opcja lub podobna opcja do użycia w formacie JSON jako format serializacji. Upewnij się, że podczas rozwoju kontraktów danych, które już przechowywane dane będą nadal ładowalne.

Dodawanie magazynu< do ziarna przy użyciu grainTState>

Ważne

Użycie Grain<T> funkcji w celu dodania magazynu do ziarna jest uznawane za Grain<T> funkcjonalność: magazyn ziarna należy dodać przy użyciu opisanego IPersistentState<T> wcześniej.

Klasy ziarna dziedziczące Grain<T> z klasy ( T gdzie jest typem danych stanu specyficznego dla aplikacji, który musi być utrwalony) będą automatycznie ładowane ze swojego magazynu.

Takie ziarna są oznaczone za StorageProviderAttribute pomocą obiektu , który określa nazwane wystąpienie dostawcy magazynu do użycia do odczytywania/zapisywania danych o stanie dla tego ziarna.

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

Klasa Grain<T> bazowa zdefiniowała 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 odpowiednikom zdefiniowanym IPersistentState<TState> wcześniej.

Tworzenie dostawcy magazynu

Interfejsy API trwałości stanu mają dwie części: interfejs API IPersistentState<T>Grain<T>ujmowany w ziarno za pośrednictwem lub i interfejs API dostawcy magazynu, IGrainStorage który jest wyśrodkowany — interfejs, który dostawcy magazynu muszą zaimplementować:

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

Utwórz niestandardowego dostawcę magazynu, implementując ten interfejs i rejestrując tę implementację. Aby uzyskać przykład istniejącej implementacji dostawcy magazynu, zobacz AzureBlobGrainStorage.

Storage semantyki dostawcy

Nieprzezroczysta Etag wartość specyficzna dla dostawcy (stringEtag zostać ustawiona przez dostawcę magazynu jako część metadanych stanu ziarna wypełnionych podczas odczytywania stanu. Niektórzy dostawcy mogą zdecydować się pozostawić to tak, null jakby nie używali s Etag.

Każda próba wykonania operacji EtagEtagTaskInconsistentStateException zapisu, gdy dostawca magazynu wykryje naruszenie ograniczeń, powinna spowodować błąd zapisu z błędem przejściowym i opakowanie 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ć uszkodzenie zwracanych danych z wyjątkiem wskazującym źródłowy problem z magazynem. W wielu przypadkach ten wyjątek może zostać zgłoszony do wywołującego, który wyzwolił operację magazynu przez wywołanie metody na ziarnie. Ważne jest, aby rozważyć, czy wywołujący będzie mógł deserializować ten wyjątek. Na przykład klient mógł nie załadować określonej biblioteki trwałości zawierającej typ wyjątku. Z tego powodu zaleca się przekonwertowanie wyjątków na wyjątki, które mogą być propagowane z powrotem do wywołującego.

Mapowanie danych

Poszczegalni dostawcy magazynu powinni zdecydować, jak najlepiej przechowywać stan ziarna — obiekty blob (różne formaty/serializowane formularze) lub kolumny na pole są oczywistymi wyborami.

Rejestrowanie dostawcy magazynu

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

Aby zarejestrować nazwane wystąpienie klasy IGrainStorage, AddSingletonNamedService użyj metody rozszerzenia zgodnie z przykładem dostawcy IGrainStorage.