다음을 통해 공유


타이머 및 미리 알림

Orleans 런타임은 개발자가 조직에 대해 주기적인 동작을 지정할 수 있도록 타이머 및 미리 알림이라는 두 가지 메커니즘을 제공합니다.

타이머

타이머는 여러 활성화(조직 인스턴스화)에 걸쳐 있을 필요가 없는 주기적인 조직 동작을 만드는 데 사용됩니다. 타이머는 표준 .NET System.Threading.Timer 클래스와 동일합니다. 또한 타이머는 작동하는 조직 활성화 내에서 단일 스레드 실행을 보장하며 타이머 콜백이 AlwaysInterleaveAttribute로 표시된 조직 메서드인 것처럼 실행이 다른 요청과 인터리브됩니다.

각 활성화에는 연결된 타이머가 0개 이상 있을 수 있습니다. 런타임은 그와 연결된 활성화의 런타임 컨텍스트 내에서 각 타이머 루틴을 실행합니다.

타이머 사용량

타이머를 시작하려면 IDisposable 참조를 반환하는 Grain.RegisterTimer 메서드를 사용합니다.

protected IDisposable RegisterTimer(
    Func<object, Task> asyncCallback, // function invoked when the timer ticks
    object state,                     // object to pass to asyncCallback
    TimeSpan dueTime,                 // time to wait before the first timer tick
    TimeSpan period)                  // the period of the timer

타이머를 취소하려면 삭제합니다.

조직이 비활성화되거나 결함이 발생하고 해당 사일로가 충돌하는 경우 타이머의 트리거가 중단됩니다.

중요 고려 사항:

  • 활성화 컬렉션을 사용하도록 설정하면 타이머 콜백을 실행해도 활성화 상태가 유휴 상태에서 사용 중으로 변경되지 않습니다. 즉, 타이머를 사용하여 유휴 활성화의 비활성화를 연기할 수 없습니다.
  • Grain.RegisterTimer에 전달된 기간은 asyncCallback이 반환한 작업이 해결된 순간부터 asyncCallback의 다음 호출이 발생해야 하는 시점까지 경과된 시간입니다. 이렇게 하면 asyncCallback에 대한 연속 호출이 겹칠 수 없을 뿐만 아니라 asyncCallback이 완료되는 데 걸리는 시간이 asyncCallback이 호출되는 빈도에 영향을 줍니다. 이는 System.Threading.Timer의 의미 체계에서 중요한 편차입니다.
  • asyncCallback의 각 호출은 별도의 턴에서 활성화로 배달되며 동일한 활성화의 다른 턴과 동시에 실행되지 않습니다. 그러나 asyncCallback 호출은 메시지로 전달되지 않으므로 메시지 인터리빙 의미 체계가 적용되지 않습니다. 즉, asyncCallback 호출은 마치 조직이 재진입되는 것처럼 동작하고 다른 조직 요청과 동시에 실행됩니다. 조직의 요청 일정 의미 체계를 활용하기 위해 조직 메서드를 호출하여 asyncCallback 내에서 수행한 작업을 수행할 수 있습니다. 또 다른 대안은 AsyncLock 또는 SemaphoreSlim을 사용하는 것입니다. 더 자세한 설명은 Orleans GitHub 문제 #2574에서 확인할 수 있습니다.

미리 알림

미리 알림은 몇 가지 중요한 차이점을 제외하고 타이머와 유사합니다.

  • 미리 알림은 영구적이며 명시적으로 취소되지 않는 한 거의 모든 상황(부분 또는 전체 클러스터 다시 시작 포함)에서 계속 트리거됩니다.
  • 미리 알림 "정의"는 스토리지에 기록됩니다. 그러나 특정 시간을 가진 각 특정 발생은 그렇지 않습니다. 이는 특정 미리 알림 틱 시 클러스터가 다운되는 경우 누락되어 미리 알림의 다음 틱만 발생하는 부작용이 있습니다.
  • 미리 알림은 특정 활성화가 아닌 조직과 연결됩니다.
  • 미리 알림이 틱할 때 조직에 연결된 활성화가 없으면 조직이 만들어집니다. 활성화가 유휴 상태가 되어 비활성화되면 동일한 조직과 연결된 미리 알림이 다음에 틱할 때 조직을 다시 활성화합니다.
  • 미리 알림 배달은 메시지를 통해 발생하며 다른 모든 조직 방법과 동일한 인터리빙 의미 체계가 적용됩니다.
  • 미리 알림은 고주파 타이머에 사용해서는 안 됩니다. 해당 기간은 분, 시간 또는 일 단위로 측정해야 합니다.

구성

미리 알림은 영구적이기 때문에 스토리지를 사용하여 작동합니다. 미리 알림 하위 시스템이 작동하기 전에 사용할 스토리지 백업을 지정해야 합니다. 이 작업은 Use{X}ReminderService 확장 메서드를 통해 미리 알림 공급자 중 하나를 구성하여 수행됩니다. 여기서 X는 공급자의 이름(예: UseAzureTableReminderService)입니다.

Azure Table 구성:

// TODO replace with your connection string
const string connectionString = "YOUR_CONNECTION_STRING_HERE";
var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseAzureTableReminderService(connectionString)
    })
    .Build();

SQL:

const string connectionString = "YOUR_CONNECTION_STRING_HERE";
const string invariant = "YOUR_INVARIANT";
var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseAdoNetReminderService(options =>
        {
            options.ConnectionString = connectionString; // Redacted
            options.Invariant = invariant;
        });
    })
    .Build();

Azure 계정 또는 SQL 데이터베이스를 설정하지 않고도 미리 알림의 자리 표시자 구현이 작동하도록 하려면 미리 알림 시스템의 개발 전용 구현을 제공합니다.

var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseInMemoryReminderService();
    })
    .Build();

미리 알림 사용

미리 알림을 사용하는 조직은 IRemindable.ReceiveReminder 메서드를 구현해야 합니다.

Task IRemindable.ReceiveReminder(string reminderName, TickStatus status)
{
    Console.WriteLine("Thanks for reminding me-- I almost forgot!");
    return Task.CompletedTask;
}

미리 알림을 시작하려면 IGrainReminder 개체를 반환하는 Grain.RegisterOrUpdateReminder 메서드를 사용합니다.

protected Task<IGrainReminder> RegisterOrUpdateReminder(
    string reminderName,
    TimeSpan dueTime,
    TimeSpan period)
  • reminderName: 컨텍스트 조직의 범위 내에서 미리 알림을 고유하게 식별해야 하는 문자열입니다.
  • dueTime: 첫 번째 타이머 틱을 실행하기 전에 대기할 시간을 지정합니다.
  • period: 타이머의 기간을 지정합니다.

미리 알림은 단일 활성화의 수명 동안 유지되므로 삭제되는 것이 아니라 명시적으로 취소되어야 합니다. Grain.UnregisterReminder를 호출하여 미리 알림을 취소합니다.

protected Task UnregisterReminder(IGrainReminder reminder)

reminderGrain.RegisterOrUpdateReminder가 반환되는 핸들 개체입니다.

IGrainReminder 인스턴스는 활성화 수명을 초과하여 유효하지는 않습니다. 지속되는 방식으로 미리 알림을 식별하려면 미리 알림의 이름이 포함된 문자열을 사용합니다.

미리 알림의 이름만 있고 해당 IGrainReminder 인스턴스가 필요한 경우 Grain.GetReminder 메서드를 호출합니다.

protected Task<IGrainReminder> GetReminder(string reminderName)

사용할 항목 결정

다음과 같은 상황에서는 타이머를 사용하는 것이 좋습니다.

  • 활성화가 비활성화되거나 오류가 발생하면 타이머가 작동을 중단하는 것은 중요하지 않거나 바람직하지 않은 경우입니다.
  • 타이머의 해상도는 낮습니다(예: 초 또는 분 단위로 합당하게 표현 가능).
  • 타이머 콜백은 Grain.OnActivateAsync()에서 시작하거나 조직 메서드가 호출될 때 시작할 수 있습니다.

다음과 같은 상황에서는 미리 알림을 사용하는 것이 좋습니다.

  • 정기적인 동작이 활성화 및 모든 실패에서 살아남아야 하는 경우입니다.
  • 드물게 수행하는 작업(예: 분, 시간 또는 일 단위로 합당하게 표현 가능).

타이머와 미리 알림 결합

미리 알림과 타이머의 조합을 사용하여 목표를 달성하는 것이 좋습니다. 예를 들어 활성화에서 생존해야 하는 작은 해상도의 타이머가 필요한 경우 5분마다 실행되는 미리 알림을 사용할 수 있습니다. 이 미리 알림은 비활성화로 인해 손실되었을 수 있는 로컬 타이머를 다시 시작하는 조직을 깨우기 위한 것입니다.

POCO 조직 등록

POCO 조직으로 타이머 또는 미리 알림을 등록하려면 IGrainBase 인터페이스를 구현하고 ITimerRegistry 또는 IReminderRegistry를 조직 생성자에 삽입합니다.

using Orleans.Runtime;
using Orleans.Timers;

namespace Timers;

public sealed class PingGrain : IGrainBase, IPingGrain, IDisposable
{
    private const string ReminderName = "ExampleReminder";

    private readonly IReminderRegistry _reminderRegistry;

    private IGrainReminder? _reminder;

    public  IGrainContext GrainContext { get; }

    public PingGrain(
        ITimerRegistry timerRegistry,
        IReminderRegistry reminderRegistry,
        IGrainContext grainContext)
    {
        // Register timer
        timerRegistry.RegisterTimer(
            grainContext,
            asyncCallback: static async state =>
            {
                // Omitted for brevity...
                // Use state

                await Task.CompletedTask;
            },
            state: this,
            dueTime: TimeSpan.FromSeconds(3),
            period: TimeSpan.FromSeconds(10));

        _reminderRegistry = reminderRegistry;

        GrainContext = grainContext;
    }

    public async Task Ping()
    {
        _reminder = await _reminderRegistry.RegisterOrUpdateReminder(
            callingGrainId: GrainContext.GrainId,
            reminderName: ReminderName,
            dueTime: TimeSpan.Zero,
            period: TimeSpan.FromHours(1));
    }

    void IDisposable.Dispose()
    {
        if (_reminder is not null)
        {
            _reminderRegistry.UnregisterReminder(
                GrainContext.GrainId, _reminder);
        }
    }
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • IGrainBase, IPingGrainIDisposable을 구현하는 POCO 조직을 정의합니다.
  • 10초마다 호출되고 등록 후 3초 후에 시작되는 타이머를 등록합니다.
  • Ping이 호출되면 매시간 호출되는 미리 알림을 등록하고 등록 직후 시작됩니다.
  • Dispose 메서드는 등록된 미리 알림을 취소합니다.