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 並行執行的代碼塊。

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