ProtoActor 中常用的的模式

ProtoActor 中常用的的模式

Message Throttling -- 消息限流

简单限流

在最简单的情况下,我们可以在每次消息处理之后添加延迟来限制 Actor 的消息处理。

actor 接收一条消息,处理,然后等待 X 时长。然后处理下一条。

增加延时以限流

这种方式完全可行,一般场景下也够用。但这会带来一个缺点,即每条消息发送都会导致延迟。 即使您的 actor 仅在一个小时内收到两条消息,但如果您连续发送这些消息,则第二条消息仍必须延时等待处理。

C# 实现的一个例子程序:

public class ThrottledActor : IActor
{
    public async Task ReceiveAsync(IContext context)
    {
        switch (context.Message)
        {
            case string msg:
            {
                Console.WriteLine("Got Message " + msg);

                //add the required delay here
                await Task.Delay(TimeSpan.FromMilliseconds(333));
                break;
            }
        }
    }
}

Time Sliced Throttling -- 分时限流

分时限流

节流的另一种方法是将消息处理分为多个时间片。 这在外部API中很常见,供应商可能允许您每分钟对他们的服务进行100次调用。 Github还有其他的一些人就采用的这种方法。 基本上,它的作用是使您可以在给定的时间段内无限制地发出X个请求,没有任何延迟。 但是当您尝试发送的请求数量超出了此时间段内的允许范围,则这些请求将被推迟到下一个时间段。

C# 实现的一个例子程序:

// periodically sent to self message
public class Tick {}

public class ThrottledActor : IActor
{
    private readonly Queue<object> _messages = new Queue<object>();
    private readonly ISimpleScheduler _scheduler = new SimpleScheduler();
    private int _tokens = 0;

    public async Task ReceiveAsync(IContext context)
    {
        switch (context.Message)
        {
            case Started _:
            {
                _scheduler.ScheduleTellRepeatedly(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), context.Self, new Tick(), out _);
                break;
            }
            case Tick _:
            {
                _tokens = 3;
                await ConsumeTimeSliceAsync();
                break;
            }
            default:
            {
                _messages.Enqueue(context.Message);
                await ConsumeTimeSliceAsync();
                break;
            }
        }
    }

    private async Task ConsumeTimeSliceAsync()
    {
        while (_tokens > 0 && _messages.Any())
        {
            var message = _messages.Dequeue();
            _tokens--;
            await ThrottledReceiveAsync(message);
        }
    }

    private Task ThrottledReceiveAsync(object message)
    {
        Console.WriteLine("Got Message " + message);
        return Actor.Done;
    }
}

Work Pulling Pattern -- 工作拉取模式

参考 akka 的 work pulling pattern :https://www.michaelpollmeier.com/akka-work-pulling-pattern

此模式的目标是防止 worker overflow(工人溢出),就是 producer 分派的任务超过了 workers 的处理能力。 如果不加处理,将导致无限的 mailbox 溢出,或者由于 mailbox 容量有限而丢弃消息。

这种模式下流程是反过来的,由 worker 告诉 producer 我已就绪,可以接受工作了。

一旦 producer 有一些工作要做,它可以将其转发给就绪的 workers。

worker 收到工作,处理完毕后将结果发送回 producer,让 producer 知道该 worker 可以再次工作了。

乍一看,这似乎像轮询(polling),但事实并非如此,因为当 producer 将工作推送给就绪的 workers 时,我们还是继续使用 push 语法。

其内涵是,workder 不必反复向 producer 寻求分派任务,它只是发出信号我已就绪,可以接收任务了。

工作拉取模式

此模式的更高级替代方法是 Reactive Streams (响应式工作流),JVM 和 .NET 中提供了这种模式。 例如,Akka, MongoDB Reactive Streams Java Provider, Spring Project Reactor 以及其他。

Limit Concurrency -- 限制并发

某些情况下,限制与资源的并行交互的数量会很有用。

比如某些第三方服务由于某些原因(技术上的或者法律上的),限制不能同时处理X个以上的并发请求。

在这种情况下,您可以使用 round-robin router 来解决问题。

限制并发

如上图所示,我们使用 round-robin router 将消息转发给三个 worker actors。然后,这些 worker actors 与有限的资源进行交互。

因为 actors 每次只处理一条消息,所以我们可以保证在任何给定时间点,对有限资源的并行请求都不会超过三个。

Sheduling Periodic Messages -- 定期消息计划

Scheduling Messages

在 C# 中,我们提供了 ISimpleSchedulerinterface 接口的实现 SimpleScheduler, 这可以让你操作 ScheduleTellOnce, ScheduleRequestOnce 以及 ScheduleTellRepeatedly

ISimpleScheduler scheduler = new SimpleScheduler();
var pid = context.Spawn(Actor.FromProducer(() => new ScheduleGreetActor()));

scheduler
    .ScheduleTellOnce(TimeSpan.FromMilliseconds(100), context.Self, new SimpleMessage("test 1"))
    .ScheduleTellOnce(TimeSpan.FromMilliseconds(200), context.Self, new SimpleMessage("test 2"))
    .ScheduleTellOnce(TimeSpan.FromMilliseconds(300), context.Self, new SimpleMessage("test 3"))
    .ScheduleTellOnce(TimeSpan.FromMilliseconds(400), context.Self, new SimpleMessage("test 4"))
    .ScheduleTellOnce(TimeSpan.FromMilliseconds(500), context.Self, new SimpleMessage("test 5"))
    .ScheduleRequestOnce(TimeSpan.FromSeconds(1), context.Self, pid, new Greet("Daniel"))
    .ScheduleTellOnce(TimeSpan.FromSeconds(5), context.Self, new Hello())
    .ScheduleTellRepeatedly(TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(500), context.Self, new HickUp(), out timer);

如果要在给定的时间段后执行某种动作,另一种方式是:

context.AwaitTask(Task.Delay(1000), t => {
     //do stuff after 1000ms w/o blocking the actor while waiting
});

这里异步地等待任务完成,然后将消息发送回 actor 本身,其中包含要求 actor 并行执行的代码块。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章