iT邦幫忙

2022 iThome 鐵人賽

DAY 17
1

Orleans 提供Grain兩種方式來實現定時任務,一種是 Timer,另一種是 Reminder。

Timer不需要在Silo端額外配置紀錄設定用的Provider,而Reminder需要,Orleans框架有提供內建 In-Memory的 Provider,以方便測試之用,官方也有提供將Reminder設定儲存至Azure Table Storage,以及 ADO.NET連線的SQL資料庫的Provider,藉由安裝官方Nuget套件就可以使用。

Grain的Timer使用法

使用Timer的方法是在Grain實作專案的程式碼內呼叫 Grain基礎類別提供的 RegisterTimer 函式,如以下使用Timer每秒鐘印一筆Log的Grain程式範例:

public class ProducerGrain : Grain, IProducerGrain
{
    private readonly ILogger<ProducerGrain> _logger;

    private IDisposable? _timer;

    private int _counter = 0;
    
    public ProducerGrain(ILogger<ProducerGrain> logger )
    {
        _logger = logger;
    }
    
    public Task StartProducing()
    {
        if (_timer is not null)
        {
            throw new Exception("This grain is already producing events");
        }
        
        //Register a timer that produce an event every second
        var period = TimeSpan.FromSeconds(1);
        _timer = RegisterTimer(TimerTick, null, period, period);
        
        _logger.LogInformation("I will produce a new event every {Period}", period);
        return Task.CompletedTask;
    }

    private async Task TimerTick(object _)
    {
        var value = _counter++;
        _logger.LogInformation("Producing event {EventNumber}", value);
    }

    public Task StopProducing()
    {
        if(_timer is not null)
        {
            _timer.Dispose();
            _timer = null;
        }
        return Task.CompletedTask;
    }
}

Grain Timer API提供的創建語法上和一般 .NET BCL(Base Class Library) 提供的 System.Threading.Timer 很像,第一個參數是Timer觸發時會執行的 Func<Object, Task> 型態的開發者自行撰寫delegate,第二個參數是呼叫該delegate時,會輸入的參數,第三個參數是Timer的第一次觸發間隔時間,第四個參數是Timer的重複觸發間隔時間,如果第四個參數是 TimeSpan.Zero,則Timer只會觸發一次。

Grain的Timer要停止,必須呼叫其執行 RegisterTimer()方法回傳的 IDisposable物件的 Dispose()方法,並且也沒有提供 Change() 方法來修改或停止Timer。

Grain Timer適合使用於觸發間隔時間小於一分鐘的事務,如果是需要間隔時間大於一分鐘,並且跨Grain的起始->閒置->休眠的生命週期之事務,建議使用Reminder。

Grain的Reminder使用法

Reminder是需要有在Silo層設定Provider才能使用的功能,但它可以跨Grain的生命週期,即使在Reminder觸發時Grain已經是從記憶體中移除的休眠狀態,Orleans Runtime也會讓該Grain重新啟動,並且執行Reminder的API所對應開發者撰寫的事件處理函式實作部分。

Reminder寫法

要使用Reminder的Grain類別定義,必須實作 IRemindable 介面提供的 ReceiveReminder() 事件處理函式,這是讓Reminder觸發時執行的程式,而一開始需要在某些進入點呼叫 RegisterOrUpdateReminder() 方法來註冊Reminder,如此以便讓Reminder正常排程觸發:

public class MyReminderGrain : Orleans.Grain, IMyReminder, IRemindable
{
    private readonly ILogger<MyReminderGrain> _logger;
    private readonly Dictionary<string, ReminderInfo> _registeredReminders = new ();

    public MyReminderGrain(ILogger<MyReminderGrain> logger)
    {
        _logger = logger;
    }

    // call this method to register a reminder
    public async Task RegisterReminder(string reminderName, int upperLimit)
    {
        if(_registeredReminders.ContainsKey(reminderName))
        {
            throw new Exception($"Reminder {reminderName} is already registered");
        }
        var reminder = await RegisterOrUpdateReminder(reminderName, TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1));
        _registeredReminders[reminderName] = new ReminderInfo { Reminder = reminder , UpperLimit = upperLimit };


        return Task.FromCompleted();
    }

    /* 
    other RPC methods ...
     */

    public async Task ReceiveReminder(string reminderName, TickStatus status)
    {
        _logger.LogInformation("Reminder {ReminderName} is triggered", reminderName);

        var reminderInfo = _registeredReminders[reminderName];

        reminderInfo.CalledCount++;

        if (reminderInfo.CalledCount >= reminderInfo.UpperLimit)
        {
            await UnregisterReminder(reminderInfo.Reminder);
            _outputMsg.Output($"Reminder: {reminderName} unregistered.");
            _registeredReminders.Remove(reminderName);
        }
    }
}

//Custom class to hold reminder info
public class ReminderInfo
{
    public int CalledCount { get; set; } = 0;
    public int UpperLimit { get; set; } = 10;

    public IGrainReminder Reminder { get; set; }
}

Reminder在不需要時,可以呼叫 UnregisterReminder() 方法來取消註冊,但因為此方法需要一個 IGrainReminder輸入參數,因此在Grain本身需準備儲存已註冊Reminder的成員變數,此範例是用一個叫 _registeredReminders 的Dictionary資料結構變數來存。

Reminder的Provider安裝與Silo設定

內建的In-Memory Reminder Provider設定方法如下,需呼叫 UseInMemoryReminderService() 擴充方法:

.UseOrleans(builder =>
    {
        builder.UseInMemoryReminderService();
        /*
        other configuration
        */
    })

官方提供的Azure Table Storage Reminder Provider設定方法如下,需安裝Microsoft.Orleans.Reminders.AzureStorage套件以便呼叫 UseAzureTableReminderService() 擴充方法:

 .UseOrleans(builder =>
    {
        builder.UseAzureTableReminderService(
            options => options.ConnectionString = "The_Azure_Table_Conn" );
        /*
        other configuration
        */
    })

官方提供的ADO.NET Reminder Provider設定方法如下,類似使用 ADO.NET 的Grain Storage Provider一樣,要在目標資料庫建立資料表,而建立資料表的SQL Script要依使用的資料庫來選擇要執行的種類下載執行;在Silo配置方面,需安裝Microsoft.Orleans.Reminders.AdoNet套件以便呼叫 UseAdoNetReminderService() 擴充方法:

 .UseOrleans(builder =>
    {
        builder.UseAdoNetReminderService(
            options => {
                options.ConnectionString = "The_AdoNet_Conn";
                options.Invariant = "The_matched_AdoNet_Invariant"; );

        /*
        other configuration
        */
    })

明天繼續介紹Orleans Grain的另一個重要功能:Grain Observer,事件(event)觸發功能。


上一篇
[16]---Orleans Grain的 重新進入(Reentrant) 功能介紹與 死結(Deadlock)問題解決
下一篇
[18]---Orleans的Grain事件發送機制:Observer
系列文
Microsoft Orleans雲原生開發框架從小白到大神39
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言