ractor - ラストアクターフレームワーク

(Rust actor framework)

Created at: 2023-01-04 00:24:32
Language: Rust
License: MIT

ラクター

発音 R-アクトール

純粋な Rust アクターフレームワーク。アーランのgen_serverにインスパイアされた、Rustのスピード+パフォーマンスで!

  • ギットハブ
  • crates.io
  • docs.rs
  • docs.rs
  • CI/メイン
  • コデコフ
  • ractor
    :ラクターのダウンロード
  • ractor_cluster
    :ractor_clusterダウンロード

に関しては

ractor
RustでErlangのようなアクターフレームワークを構築および維持する問題を解決しようとします。それは与える ジェネリックプリミティブのセットであり、従来のアクターメッセージ処理ロジックとともにアクターの監視ツリーと管理を自動化するのに役立ちます。それは重く構築されています の厳しい要件。
tokio
ractor

ractor
は、コードなしで100%錆びて書かれた最新のアクターフレームワークです。
unsafe

さらに、分散(クラスターのような)シナリオで展開するために必要なコンパニオンライブラリがあります。 まだ一般公開の準備ができていませんが、進行中の作業であり、まもなく登場します!

ractor
ractor_cluster
ractor
ractor_cluster

なぜラクター?

Rustで書かれた他のアクターフレームワーク(Actixriker、またはTokioのアクターのみ)に加えて、このRedditの投稿にまとめられたリスト全体があります。

Ractorは、純粋なアーランをもっとモデル化することで、違うことを試みます。これは、各アクターが追加費用なしで他のアクターのスーパーバイザーになることができることを意味します(単にそれらをリンクするだけです!さらに、Erlangのパターンは非常にうまく機能し、業界でよく利用されているため、Erlangのパターンと緊密なロジックを維持することを目指しています。

gen_server

さらに、生成する必要のあるある種の「ランタイム」または「システム」に基づいて構築せずに書きました。アクタは、他の基本的なランタイムと組み合わせて独立して実行でき、オーバーヘッドはほとんど追加されません。

ractor
tokio

現在、以下を完全にサポートしています。

  1. シングルスレッド・メッセージ処理
  2. アクター監督ツリー
  3. アクターへのリモート プロシージャ コール
  4. タイマー
  5. Erlangの登録済みプロセスからの名前付きアクターレジストリ()
    ractor::registry
  6. アーランのpgモジュールからのプロセスグループ()
    ractor::pg

ロードマップでは、分散アクタークラスターを含むErlang機能をさらに追加します。

パフォーマンス

のアクターは一般的に非常に軽量であり、独自のホストシステムで実行できるベンチマークがあります。

ractor

cargo bench -p ractor

取り付け

Cargo.tomlの依存関係に以下を追加してインストールします。

ractor

[dependencies]
ractor = "0.7"

顔立ち

ractor
現在、単一の機能、つまり次の機能を公開しています。

  1. cluster
    これは、ネットワーク リンクを介してアクターのクラスターを設定および管理するために必要なさまざまな機能を公開します。これは進行中の作業であり、#16で追跡されています。
    ractor_cluster

アクターの操作

のアクターは非常に軽量で、スレッドセーフとして扱うことができます。各アクターは、一度に1つのハンドラー関数のみを呼び出し、 並列で実行されることはありません。アクター モデルに従うと、明確に定義された状態と処理ロジックを持つマイクロサービスが実現します。

ractor

アクターの例は次のとおりです。

ping-pong

use ractor::{cast, Actor, ActorProcessingErr, ActorRef};

/// [PingPong] is a basic actor that will print
/// ping..pong.. repeatedly until some exit
/// condition is met (a counter hits 10). Then
/// it will exit
pub struct PingPong;

/// This is the types of message [PingPong] supports
#[derive(Debug, Clone)]
pub enum Message {
    Ping,
    Pong,
}

impl Message {
    // retrieve the next message in the sequence
    fn next(&self) -> Self {
        match self {
            Self::Ping => Self::Pong,
            Self::Pong => Self::Ping,
        }
    }
    // print out this message
    fn print(&self) {
        match self {
            Self::Ping => print!("ping.."),
            Self::Pong => print!("pong.."),
        }
    }
}

// the implementation of our actor's "logic"
#[async_trait::async_trait]
impl Actor for PingPong {
    // An actor has a message type
    type Msg = Message;
    // and (optionally) internal state
    type State = u8;
    // Startup initialization args
    type Arguments = ();

    // Initially we need to create our state, and potentially
    // start some internal processing (by posting a message for
    // example)
    async fn pre_start(
        &self,
        myself: ActorRef<Self>,
        _: (),
    ) -> Result<Self::State, ActorProcessingErr> {
        // startup the event processing
        cast!(myself, Message::Ping)?;
        // create the initial state
        Ok(0u8)
    }

    // This is our main message handler
    async fn handle(
        &self,
        myself: ActorRef<Self>,
        message: Self::Msg,
        state: &mut Self::State,
    ) -> Result<(), ActorProcessingErr> {
        if *state < 10u8 {
            message.print();
            cast!(myself, message.next())?;
            *state += 1;
        } else {
            println!();
            myself.stop(None);
            // don't send another message, rather stop the agent after 10 iterations
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    let (_actor, handle) = Actor::spawn(None, PingPong, ())
        .await
        .expect("Failed to start ping-pong actor");
    handle
        .await
        .expect("Ping-pong actor failed to exit properly");
}

出力されます

$ cargo run
ping..pong..ping..pong..ping..pong..ping..pong..ping..pong..
$ 

メッセージング アクター

アクター間の通信手段は、メッセージを相互に渡すことです。開発者は、任意のメッセージタイプを定義できます。 によってサポートされます。4つの同時メッセージタイプがあり、優先的にリッスンされます。彼らです

Send + 'static
ractor

  1. シグナル: シグナルはすべての中で最も優先度が高く、処理が現在行われている場所でアクターを中断します (これには非同期作業の終了が含まれます)。そこ は今日は1つのシグナルのみであり、すべての作業を直ちに終了します。これには、メッセージ処理または監視イベント処理が含まれます。
    Signal::Kill
  2. 停止:事前定義された停止信号もあります。必要に応じて「停止理由」を指定できますが、オプションです。Stop は正常な終了であり、現在非同期を実行していることを意味します。 作業が完了し、次のメッセージ処理の反復では、Stop が今後の監視イベントまたは通常のメッセージよりも優先されます。終了しません 指定された理由に関係なく、現在実行中の作業。
  3. スーパービジョンイベント:スーパーバイザーイベントは、子役の起動、死亡、および/または未処理のパニックが発生した場合に、子役からスーパーバイザーへのメッセージです。監督イベント は、アクターのスーパーバイザーが子供のイベントを通知され、生涯のイベントを処理できる方法です。
  4. メッセージ: 通常のユーザー定義のメッセージは、アクターへの最後の通信チャネルです。これらは、4 つのメッセージの種類の中で最も優先度が低く、一般的なアクターの作業を示します。最初の 3つのメッセージタイプ(シグナル、停止、監視)は、アクターのライフサイクルイベントでない限り、通常は静かですが、このチャネルは、アクターがやりたいことを行う「仕事」チャネルです。

分散クラスタ内のラクタ

Ractorアクターは、ノード間接続+ノードの命名を管理するErlangのEPMDと同様に、アクターの分散プールを構築するためにも使用できます。私たちの実装では、分散アクターを容易にするためにractor_clusterがあります。

ractor

ractor_cluster
その中に単一のメインタイプ、つまりプロセスのホストを表すものがあります。さらに、分散アクターを構築する際の開発者の効率を促進するために、いくつかのマクロと手続き型マクロがあります。に責任があります
NodeServer
node()
NodeServer

  1. このホストに接続されているリモートノードを表すすべての着信および発信アクターを管理します。
    NodeSession
  2. 着信セッション要求を受け入れるサーバーソケットをホストする管理。
    TcpListener

ただし、ノード相互接続のロジックの大部分は、

NodeSession

  1. ストリームの読み取りと書き込みを管理する基になる TCP 接続。
  2. このノードとピアへの接続との間の認証
  3. リモートシステムで生成されたアクターのアクターライフサイクルの管理。
  4. ノード間ですべてのインターアクターメッセージを送信します。
  5. PG グループ同期の管理

等。。

は、シリアル化されたメッセージのみを処理する本質的に型指定されていないアクターである を生成し、メッセージの逆シリアル化を元のシステムに任せることで、ローカルアクターをリモートシステムで使用できるようにします。また、保留中の RPC 要求を追跡して、応答時に要求と応答を照合します。一般的に標準外で使用することを意図していないsを具体的にサポートするために追加された特別な拡張ポイントがあります

NodeSession
RemoteActor
ractor
RemoteActor

Actor::spawn(Some("name".to_string()), MyActor).await

パターン。

リモートサポートアクターの設計

すべてのアクターが同じように作成されるわけではないことに注意してください。アクターは、ネットワークリンクを介して送信されるメッセージタイプをサポートする必要があります。これは、すべてのメッセージがサポートする必要がある特性の特定のメソッドをオーバーライドすることによって行われます。Rustには特殊化サポートがないため、使用する場合は、クレート内のすべてのメッセージタイプの特性を派生させる必要があります。ただし、これをサポートするために、これをより痛みのないプロセスにするための手続き型マクロがいくつかあります

ractor::Message
ractor_cluster
ractor::Message

インプロセスのみのアクターの基本的な Message 特性の導出

多くのアクターはローカル専用であり、ネットワークリンクを介してメッセージを送信する必要はありません。これは最も基本的なシナリオであり、この場合はデフォルトの特性実装で問題ありません。次の方法ですばやく導き出すことができます。

ractor::Message

use ractor_cluster::RactorMessage;
use ractor::RpcReplyPort;

#[derive(RactorMessage)]
enum MyBasicMessageType {
    Cast1(String, u64),
    Call1(u8, i64, RpcReplyPort<Vec<String>>),
}

これにより、手動で書き出すことなく、デフォルトの特性が実装されます。

ractor::Message

リモートアクターのネットワークシリアル化可能なメッセージ特性の導出

アクターでリモート処理をサポートする場合は、別の派生ステートメントを使用する必要があります。

use ractor_cluster::RactorClusterMessage;
use ractor::RpcReplyPort;

#[derive(RactorClusterMessage)]
enum MyBasicMessageType {
    Cast1(String, u64),
    #[rpc]
    Call1(u8, i64, RpcReplyPort<Vec<String>>),
}

これにより、実装のためにかなりの量の基礎となる定型文が追加されます(自分で見てください!しかし、簡単な答えは、各列挙型バリアントは引数のバイト配列、バリアント名にシリアル化する必要があり、RPCの場合はバイト配列を受信するポートを与え、応答を逆シリアル化する必要があります。引数または応答型内の各型は、この値をバイト配列に書き込んでバイト配列からデコードできるという特性を実装する必要があります。メッセージ型定義 (protobuf) に使用している場合は、型に対してこれを自動実装するマクロがあります。

cargo expand
ractor_cluster::BytesConvertable
prost

ractor_cluster::derive_serialization_for_prost_type! {MyProtobufType}

それに加えて、あなたがするようにあなたの俳優を書くだけです。アクター自体は、定義した場所に存在し、他のクラスターからネットワークリンクを介して送信されたメッセージを受信できます。

貢献

の原作者は、ショーン・ローラー(@slawlor)、ディロン・ジョージ(@dillonrg)、エヴァン・オー(@afterdusk)です。貢献の詳細については、CONTRIBUTING.md を参照してください。

ractor
ractor

ライセンス

このプロジェクトはMITの下でライセンスされています。