다음을 통해 공유


조직 지속성

조직에는 여러 개의 명명된 영구 데이터 개체가 연결되어 있을 수 있습니다. 이러한 상태 개체는 요청 중에 사용할 수 있도록 조직 활성화 중에 스토리지에서 로드됩니다. 조직 지속성은 확장 가능한 플러그 인 모델을 사용하므로 모든 데이터베이스에 대한 스토리지 공급자를 사용할 수 있습니다. 이 지속성 모델은 단순성을 위해 설계되었으며 모든 데이터 액세스 패턴을 다루지는 않습니다. 또한 조직은 조직 지속성 모델을 사용하지 않고 데이터베이스에 직접 액세스할 수 있습니다.

위의 다이어그램에서 UserGrain에는 프로필 상태와 카트 상태가 있으며, 각 상태는 별도의 스토리지 시스템에 저장됩니다.

목표

  1. 조직당 여러 명명된 영구 데이터 개체입니다.
  2. 구성된 여러 스토리지 공급자는 각각 다른 구성을 가지며 다른 스토리지 시스템에서 지원될 수 있습니다.
  3. 스토리지 공급자는 커뮤니티에서 개발하고 게시할 수 있습니다.
  4. 스토리지 공급자는 영구 백업 저장소에 조직 상태 데이터를 저장하는 방법을 완전히 제어할 수 있습니다. 결과: Orleans는 포괄적인 ORM 스토리지 솔루션을 제공하지 않고 대신 사용자 지정 스토리지 공급자가 필요한 경우 특정 ORM 요구 사항을 지원할 수 있도록 합니다.

패키지

Orleans 조직 스토리지 공급자는 NuGet에서 찾을 수 있습니다. 공식적으로 유지 관리되는 패키지는 다음과 같습니다.

API

조직은 IPersistentState<TState>를 사용하여 영구 상태와 상호 작용합니다. 여기서 TState는 직렬화 가능한 상태입니다.

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

IPersistentState<TState>의 인스턴스는 생성자 매개 변수로 조직에 삽입됩니다. 이러한 매개 변수는 삽입되는 상태의 이름과 이를 제공하는 스토리지 공급자의 이름을 식별하는 PersistentStateAttribute 특성으로 주석을 추가할 수 있습니다. 다음 예제에서는 두 개의 명명된 상태를 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;
    }
}

서로 다른 조직 유형은 둘 다 동일한 형식인 경우에도 서로 다른 구성된 스토리지 공급자를 사용할 수 있습니다. 예를 들어 서로 다른 Azure Storage 계정에 연결된 두 개의 서로 다른 Azure Table Storage 공급자 인스턴스가 있습니다.

읽기 상태

조직이 활성화되면 조직 상태가 자동으로 읽히지만, 필요한 경우 조직은 변경된 모든 조직 상태에 대한 쓰기를 명시적으로 트리거해야 합니다.

조직이 백업 저장소에서 이 조직의 최신 상태를 명시적으로 다시 읽으려는 경우 조직은 ReadStateAsync 메서드를 호출해야 합니다. 그러면 스토리지 공급자를 통해 영구 저장소에서 조직 상태가 다시 로드되고, ReadStateAsync()에서 Task가 완료되면 조직 상태의 이전 메모리 내 복사본을 덮어쓰고 바꿉니다.

상태 값은 State 속성을 사용하여 액세스됩니다. 예를 들어 다음 메서드는 위의 코드에서 선언된 프로필 상태에 액세스합니다.

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

정상 작업 중에는 ReadStateAsync()를 호출할 필요가 없습니다. 활성화하는 동안 상태가 자동으로 로드됩니다. 그러나 ReadStateAsync()는 외부에서 수정된 상태를 새로 고치는 데 사용할 수 있습니다.

오류 처리 메커니즘에 대한 자세한 내용은 아래의 오류 모드 섹션을 참조하세요.

쓰기 상태

State 속성을 통해 상태를 수정할 수 있습니다. 수정된 상태는 자동으로 유지되지 않습니다. 대신 개발자는 WriteStateAsync 메서드를 호출하여 상태를 유지할 시기를 결정합니다. 예를 들어 다음 메서드는 State의 속성을 업데이트하고 업데이트된 상태를 유지합니다.

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

개념적으로 Orleans 런타임은 쓰기 작업 중에 사용할 조직 상태 데이터 개체의 전체 복사본을 가져옵니다. 기본적으로 런타임은 최적화 규칙과 추론을 사용하여 예상되는 논리적 격리 의미 체계가 유지되는 경우 일부 또는 모든 심층 복사를 수행하지 않도록 할 수 있습니다.

오류 처리 메커니즘에 대한 자세한 내용은 아래의 오류 모드 섹션을 참조하세요.

상태 지우기

ClearStateAsync 메서드는 스토리지에서 조직의 상태를 지웁니다. 공급자에 따라 이 작업은 필요에 따라 조직 상태를 완전히 삭제할 수 있습니다.

시작

조직이 지속성을 사용하려면 먼저 사일로에서 스토리지 공급자를 구성해야 합니다.

먼저 프로필 상태 및 카트 상태에 대한 스토리지 공급자를 구성합니다.

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

이제 스토리지 공급자가 "profileStore"라는 이름으로 구성되었으므로 조직에서 이 공급자에 액세스할 수 있습니다.

영구 상태는 다음 두 가지 기본 방법으로 조직에 추가할 수 있습니다.

  1. 조직의 생성자에 IPersistentState<TState>를 주입합니다.
  2. Grain<TGrainState>에서 상속합니다.

조직에 스토리지를 추가하는 권장 방법은 연결된 [PersistentState("stateName", "providerName")] 특성을 사용하여 조직의 생성자에 IPersistentState<TState>를 주입하는 것입니다. Grain<TState>에 대한 자세한 아래를 참조하세요. 이는 여전히 지원되지만 레거시 접근 방식으로 간주됩니다.

그레인의 상태를 유지할 클래스를 선언합니다.

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

    public Date DateOfBirth
}

조직의 생성자에 IPersistentState<ProfileState>를 주입합니다.

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

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

참고

프로필 상태는 생성자에 삽입될 때 로드되지 않으므로 해당 시점에 액세스하는 것은 유효하지 않습니다. OnActivateAsync가 호출되기 전에 상태가 로드됩니다.

이제 조직이 영구 상태가 되었으므로 상태를 읽고 쓰는 메서드를 추가할 수 있습니다.

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

지속성 작업에 대한 오류 모드

읽기 작업에 대한 오류 모드

특정 조직의 상태 데이터를 처음 읽는 동안 스토리지 공급자가 반환한 오류는 해당 조직의 활성화 작업에 실패합니다. 이 경우 해당 조직의 OnActivateAsync() 수명 주기 콜백 메서드에 대한 호출은 없습니다. 활성화의 원인이 된 조직에 대한 원래 요청은 조직이 활성화 중에 다른 오류와 동일한 방식으로 호출자에게 다시 오류가 발생합니다. 스토리지 공급자가 특정 조직의 상태 데이터를 읽을 때 발생하는 오류로 인해 ReadStateAsync()Task에서 예외가 발생합니다. 조직은 Orleans의 다른 Task와 마찬가지로 Task 예외를 처리하거나 무시하도록 선택할 수 있습니다.

누락되거나 잘못된 스토리지 공급자 구성으로 인해 사일로 시작 시 로드하지 못한 조직에 메시지를 보내려고 하면 영구 오류 BadProviderConfigException이 반환됩니다.

쓰기 작업에 대한 오류 모드

스토리지 공급자가 특정 조직의 상태 데이터를 쓸 때 발생하는 오류로 인해 WriteStateAsync()Task에서 예외가 throw됩니다. 일반적으로 이는 WriteStateAsync()Task가 이 조직 메서드에 대한 최종 반환 Task에 올바르게 연결된 경우 해당 조직 호출 예외가 클라이언트 호출자에게 다시 throw됨을 의미합니다. 그러나 특정 고급 시나리오에서는 다른 오류가 있는 Task를 처리할 수 있는 것처럼 이러한 쓰기 오류를 구체적으로 처리하기 위해 조직 코드를 작성할 수 있습니다.

오류 처리/복구 코드를 실행하는 조직은 쓰기 오류를 성공적으로 처리했음을 나타내기 위해 예외/오류 WriteStateAsync()Task를 catch하여 다시 throw하지 않아야 합니다.

권장 사항

JSON 직렬화 또는 다른 버전 허용 직렬화 형식 사용

코드가 발전하고 스토리지 유형도 포함되는 경우가 많습니다. 이러한 변경 내용을 수용하려면 적절한 직렬 변환기를 구성해야 합니다. 대부분의 스토리지 공급자의 경우 JSON을 직렬화 형식으로 사용하는 UseJson 옵션 또는 이와 유사한 옵션을 사용할 수 있습니다. 데이터 계약을 발전시킬 때 이미 저장된 데이터를 계속 로드할 수 있는지 확인합니다.

Grain<TState>를 사용하여 조직에 스토리지 추가

중요

Grain<T>을 사용하여 조직에 스토리지를 추가하는 것은 레거시 기능으로 간주됩니다. 이전에 설명한 대로 IPersistentState<T>를 사용하여 조직 스토리지를 추가해야 합니다.

Grain<T>에서 상속되는 조직 클래스(여기서 T는 지속되어야 하는 애플리케이션별 상태 데이터 형식임)는 지정된 스토리지에서 해당 상태가 자동으로 로드됩니다.

이러한 조직은 이 조직의 상태 데이터를 읽고 쓰는 데 사용할 스토리지 공급자의 명명된 인스턴스를 지정하는 StorageProviderAttribute로 표시됩니다.

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

Grain<T> 기본 클래스는 호출할 서브클래스에 대해 다음 메서드를 정의했습니다.

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

이러한 메서드의 동작은 이전에 정의된 IPersistentState<TState>의 해당 메서드에 해당합니다.

스토리지 공급자 만들기

상태 지속성 API에는 다음 두 부분이 있습니다. 즉, IPersistentState<T> 또는 Grain<T>을 통해 조직에 노출되는 API와 스토리지 공급자가 구현해야 하는 인터페이스인 IGrainStorage를 중심으로 하는 스토리지 공급자 API가 있습니다.

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

이 인터페이스를 구현하고 해당 구현을 등록하여 사용자 지정 스토리지 공급자를 만듭니다. 기존 스토리지 공급자 구현의 예는 AzureBlobGrainStorage를 참조하세요.

스토리지 공급자 의미 체계

불투명 공급자별 Etag 값(string)은 상태를 읽을 때 채워진 조직 상태 메타데이터의 일부로 스토리지 공급자가 설정할 수 있습니다. 일부 공급자는 Etag를 사용하지 않는 경우 이 작업을 null로 두도록 선택할 수 있습니다.

스토리지 공급자가 Etag 제약 조건 위반을 감지할 때 쓰기 작업을 수행하려고 하면 일시적 오류 InconsistentStateException로 인해 쓰기 Task에 오류가 발생하고 기본 스토리지 예외가 래핑됩니다.

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

스토리지 작업의 다른 모든 오류 조건으로 인해 기본 스토리지 문제를 나타내는 예외와 함께 반환된 Task중단되어야 합니다. 대부분의 경우 이 예외는 조직에서 메서드를 호출하여 스토리지 작업을 트리거한 호출자에게 다시 throw될 수 있습니다. 호출자가 이 예외를 역직렬화할 수 있는지 여부를 고려하는 것이 중요합니다. 예를 들어 클라이언트가 예외 형식을 포함하는 특정 지속성 라이브러리를 로드하지 않았을 수 있습니다. 이러한 이유로 예외를 호출자에게 다시 전파할 수 있는 예외로 변환하는 것이 좋습니다.

데이터 매핑

개별 스토리지 공급자는 조직 상태를 가장 잘 저장하는 방법을 결정해야 합니다. Blob(다양한 형식/직렬화된 양식) 또는 필드당 열은 명백한 선택 사항입니다.

스토리지 공급자 등록

Orleans 런타임은 조직을 만들 때 서비스 공급자(IServiceProvider)의 스토리지 공급자를 확인합니다. 런타임은 IGrainStorage의 인스턴스를 확인합니다. 예를 들어 [PersistentState(stateName, storageName)] 특성을 통해 스토리지 공급자가 명명된 경우 IGrainStorage의 명명된 인스턴스가 확인됩니다.

IGrainStorage의 명명된 인스턴스를 등록하려면 여기에서 AzureTableGrainStorage 공급자의 예제에 따라 AddSingletonNamedService 확장 메서드를 사용합니다.