Grain Timers and Reminders

Orleans 提供了 Timers 和 Reminders两种机制。

Timers

计时器与 .NET 里提供的 System.Threading.Timer 类基本相同。此外,计时器在单独的线程上执行。如果有多个与之关联的计时器,在运行时会进行上下文切换去执行每个计时器。

用法:

要启动计时器,直接使用 RegisterTimer 方法就可以,该方法在 Grain 类中实现,方法参数说明:

Func<object, Task> asyncCallback // 计时器开始计时调用的函数
object state                     // asyncCallback 函数所需参数
TimeSpan dueTime                 // 启动到第一次执行间隔的时间量
TimeSpan period                  // 等行为下一次执行间隔的时间量

方法返回一个 IDisposable 句柄,通过 Dispose 方法来停止计时器。

另外,一旦 Grain 被停用或者 Silo 崩坏,计时器将停止触发。

接下来我们看下使用的代码:

public interface ITimerGrain : IGrainWithIntegerKey
{
    Task Start();
    Task Finish();
}
public class TimerGrain : Grain, ITimerGrain
{
    private readonly ILogger logger;

    private IDisposable? disposable;

    public TimerGrain(ILogger<HelloGrain> logger)
    {
        this.logger = logger;
    }

    public Task Start()
    {
        disposable = RegisterTimer(o =>
                                   {
                                       logger.LogInformation($"time tick = {DateTime.Now}\n ");
                                       return Task.CompletedTask;
                                   }, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    public Task Finish()
    {
        if (disposable != null)
        {
            logger.LogInformation($"计时器停止--{DateTime.Now}\n");
            disposable.Dispose();
            disposable = null;
        }

        return Task.CompletedTask;
    }
}

client 调用

var timerGrain = client.GetGrain<ITimerGrain>(1);
await timerGrain.Start();

//休息个10秒钟再来取消计时器
Thread.Sleep(10000);

await timerGrain.Finish();

计时器的用法比较简单,但我们也要注意如下事项:

  • 启用收集 Grain 时,计时器不会将 Grain 激活的状态从空闲更改为使用中。不会因计时器在执行回调函数而推迟取消激活原本空闲的 Grain 。Grain 一旦被停用,那么定时器也将被丢弃。

  • 从 asyncCallback 返回的 Task 被解析之前,不会执行下一次 asyncCallback 。 计时器永远不会重叠调用 asyncCallback。而且 asyncCallback 所需要的执行时间会影响到调用 asyncCallback 的频率。这与 System.Threading.Timer 一个很重要的不同点。

  • 任何由 asyncCallback 返回的异常或错误的 Task 都会被记录,但不会阻止下一次 asyncCallback 进入队列。

  • asyncCallback 调用不会作为消息传递,因此它不会受消息交互场景的影响。

以上就是 Timers 的用法。接下来看下 Reminders 。

Reminders

Reminders 翻译过来为提醒,但我觉得这不太贴切,所以就保留英文。Reminders 与计时器有类似的地方,我们先看它的用法,最后再来说它们之间的区别。

Reminders 要依赖存储,所以在使用之前要指定存储方式。存储方式可以在 Silo 配置,创建 SiloHostBuilder 对象时调用 UseXReminderService 扩展方法进行配置,其中 X 是存储方式名称。例如:我使用 MySql 数据库,在配置之前先添加 NuGet 引用 Microsoft.Orleans.Reminders.AdoNet 和 MySql.Data ,具体的配置如下:

.UseAdoNetReminderService(options => 
    {
        options.ConnectionString = "Server=localhost;Port=3306;Database=yld;Uid=root;Pwd=mysql;";
        options.Invariant = "MySql.Data.MySqlClient";
    })

除了 SQL 数据库,还支持如下图:

每个具体的配置方案这里就不介绍,相信都会。另外,Orleans 还提供了一个开发环境下使用的内存存储方式,其配置比较简单直接使用 UseInMemoryReminderService 方法即可。

这里我就使用 MySql 来做示例。

先做些准备工作,创建一个数据,这里数据库名称就叫 yld 。然后到 Github 上下载 MySQL-Reminders.sql 的数据库脚本 。执行脚本,准备工作完成。 接下来按下面步骤启动一个 Reminders 。

1、定义一个 IReminderGrain 接口如下:

public interface IReminderGrain : IGrainWithIntegerKey
{
    Task Execute();
    Task Cancel();
}

2、定义一个 ReminderGrain 类,该类继承 Grain 和 IReminderGrain 、IRemindable 接口。

3、实现 IRemindable 接口里的 ReceiveReminder 方法。

4、在 Execute 方法调用 Grain 类的 RegisterOrUpdateReminder 方法,进行注册。该方法参数说明如下:

string reminderName     // 设置名称,该名称必须唯一
TimeSpan dueTime        // 启动到第一次执行间隔的时间量
TimeSpan period         // 等行为下一次执行间隔的时间量

该方法返回一个 IGrainReminder 。

5、通过 Grain 类的 UnregisterReminder 方法可以取消 Reminders ,该方法接收一个 IGrainReminder 参数。IGrainReminder 可以通过 Grain 类的 GetReminder 方法获取,GetReminder 方法是通过名称来获取 IGrainReminder 。

完整代码如下:

public class ReminderGrain : Grain, IReminderGrain, IRemindable
{
    private IGrainReminder? reminder;
    private readonly ILogger logger;

    public ReminderGrain(ILogger<HelloGrain> logger)
    {
        this.logger = logger;
    }
    public Task Cancel()
    {
        if (reminder != null)
        {
            logger.LogInformation($"结束 Reminider --{DateTime.Now}\n");
            UnregisterReminder(reminder);
        }

        return Task.CompletedTask;
    }

    public async Task Execute()
    {
        reminder = await RegisterOrUpdateReminder("Reminder Demo", TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(60));
    }

    public Task ReceiveReminder(string reminderName, TickStatus status)
    {
        logger.LogInformation($"执行 Reminider --{DateTime.Now}\n");
        return Task.CompletedTask;
    }
}

client 调用

var reminderGrain = client.GetGrain<IReminderGrain>(1);
await reminderGrain.Execute();

Console.WriteLine("待待 120 秒 ...");
Thread.Sleep(120000);

Console.WriteLine("Reminder 停止--" + DateTime.Now);
await reminderGrain.Cancel();

Reminders 启动时会在数据库表 orleansreminderstable 中新增一条记录。暂停 Reminder 时会把该条记录删除。

完整代码,放在 github 上。

Timers 与 Reminders 区别

  • Reminders 是持久的,因为它通过存储,保证在部分或全部集群重启时可以继承触发,除非明确取消

  • Reminders 不会特定的时间去执行特定的任务,这样会错过时间执行任务,只能执行下一次任务。

  • Reminders 与 Grain 是相互关联,无需单独激活。

  • 如果 Reminders 还在执行中,而没有 Grain 可以关联,则会自动创建一个 Grain 。如果 Grain 状态变为空闲且被停用,则将会在下一次执行时激活该 Grain。

  • Reminders 可以通过消息传递,并受制于其它 grain 方法的交互场景。

  • Reminders 不是一个高频计时器,最小单位为 1 分钟。它的周期以分钟,小时或天为单位。不能以秒为单位会报错。

如何选择

  • grain 停用或 silo 崩掉,计时器会停止作业从而影响任务的执行,而 Reminders 不会。如果对任务执行比较重要,要求比较高,集群重启不影响任务执行,那就选 Reminders 。否则,就选择 Timers 。

  • 计时器的作业频率快,以秒或分钟为单元。而 Reminders 执行一些频率比较慢的任务,例如:以分钟、小时或天为单位。

  • 计时器可以在调用 grain 方法启动或在 Grain 类的 OnActivateAsync 方法启动。

Timers 和 Reminders 的 dueTime 参数尽量不设置为 0 。

 最后,祝大家学习愉快!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值