約8年前、私は1つのサービスを開発したチームで働き始めました。 サービスインターフェイスは非常にシンプルで、4つのメソッドしかなく、1つのタスクを実行しました。 この間、コードは絶えず変化していました。新しいビジネスルールと制限が実装され、バージョン管理が追加されました。 ある時点で、フロントエンドはほとんど機能を必要としませんでしたが、これはサービスの奥深くに「埋め込まれ」ていました。 必要な機能の実装はコンポーネントの形で開発され、追加のメソッドを介してサービスからアクセスできるようにするための問題はありませんでした。1つを除いて、サービスメソッドの論理的な接続が侵害されました。
この問題は、外部アクセスを必要とするこれらの小さな内部コンポーネントをすべて個別のサービスに変換すれば解決できます。 この場合、フロントエンドは機能にアクセスできます。 メインサービスはよりコンパクトになり、その役割はオーケストレーションを呼び出すために削減されました。
WCFを使用してサービスを構築しました。 ロードバランサー、新しいURL、その他の付加機能を備えた、少なくとも3〜4台のサーバーで、WCFの50行のコードにサービスを展開することは、悪い考えのように思えました。 そして、私はいくつかの軽さ、視点が欲しかった...
数年後、Workflow Foundationの別のプロジェクトに参加しました。 XAMLエディターで何が起こったのかを見て、「ワークフロー全体を一連のメッセージとして想像してみませんか」と考えました。
キノテスト
正直なところ、私は利用可能なソリューションを検索しませんでした。 当時(4-5年前) オルレアンについてはほとんど知られていませんでしたが、自転車の建設が始まってからアッカについて知りました。 一方で、それは悪いことであり、プロの開発者などにはふさわしくありません。 一方、何か新しいことが判明したかもしれない...どれだけ良いか悪いかが判明した、尊敬される読者は判断できる。
そこで、私はkinoを作成し始めました: NetMQ上で俳優のようなコミュニケーションフレームワーク。 接尾辞は「類似」です。なぜなら、古典的な俳優は監督者の階層的な組織を持ち、彼らはステートフルであり、一般に数学モデル全体を持っているからです...
一言で言えば
キノでのコミュニケーションの主な手段はメッセージです。 各メッセージには、適切なハンドラーを見つけるために使用されるバージョンとタイプがあります。 ルールからわずかな逸脱がありますが、今はそれについて話しましょう。
アクターは、メッセージの主な消費者およびプロデューサーです。 アクターはインターフェースをアナウンスし、受信できるメッセージのタイプとバージョンを示します。 群衆には別のメンバーMessageHubがあり 、これもメッセージを送受信できます。 ただし、それらの間には特定の違いがあります。 アクターはサービスと見なされる必要があります。着信メッセージが受信されたときにのみ応答できます。 MessageHubは、メッセージを送信し、必要に応じて応答メッセージを(試行)受信できるクライアントです。 したがって、ほとんどの場合、最初のメッセージはMessageHubを介して送信され、1つ以上のActorによって処理されます。
MessageRouterは 、メッセージ受信者を検索するために必要です。 ルーティングテーブル(バージョンとメッセージの種類(ID)との対応、およびそれを処理できるアクターのリスト)を格納します。 1つのプロセスに対して、1つのMessageRouterで十分です。
1つのプロセス/ホストの範囲を超えるには、外部の世界、つまり、他のMessageRouterとそのルーティングテーブルに関する知識を得る必要があります。 このような知識のソースは、 ランデブーサービスです。 これは、 キノベースのアプリケーション用に設定する必要がある唯一の既知のアドレスです。 Rendezvousは、すべての人から受け入れ、接続の新規作成と削除、存在しないルートの削除、接続の確立に関するすべてのMessageRouterの情報を配布します。 ランデブーサービスは、 キノコンポーネントの単一ネットワークを形成します。
また、しかしより詳細に
1.メッセージ
これは、 キノネットワークを散歩するために送信できる典型的なメッセージです。
public class MyMessage : Payload { private static readonly byte[] MessageIdentity = "NAMESPACE.MYMESSAGE".GetBytes(); private static readonly byte[] MessageVersion = "1.0".GetBytes(); // , .. , public override byte[] Version => MessageVersion; public override byte[] Identity => MessageIdentity; }
配信(配信パターン)メッセージには、 ユニキャスト 、 ブロードキャスト 、およびダイレクトの 3つのサポートされた方法があります。 最初の場合、メッセージはネットワークに登録された1つのハンドラーのみに送信されます。 第二に-みんなに。
IPayload payload = new MyMessage(); IMessage message = Message.Create(payload, DistributionPattern.Broadcast);
テスト中に特に役立つ可能性がある直接配布の場合、メッセージは特定のMessageRouter 'yに送信されます。
IMessage message = Message.CreateFlowStartMessage(new MyMessage()); message.SetReceiverNode(receiverIdentity); //
次のようにして、受信したメッセージのデータにアクセスできます。
MyMessage payload = message.GetPayload<MyMessage>();
2.アクター
独自のアクターを作成するには、アクターからクラスを継承し、少なくとも1つのメッセージハンドラーメソッドを実装する必要があります。
public class MyMessageProcessor : Actor { [MessageHandlerDefinition(typeof (MyMessage))] public async Task<IActorResult> MyMessageHandler(IMessage message) { // } [MessageHandlerDefinition(typeof (OtherMessage))] public Task<IActorResult> OtherMessageHandler(IMessage message) { // } } ```cs , *kino*. , : ```cs public class LocalMessageProcessor : Actor { // [MessageHandlerDefinition(typeof (LocalMessage), true)] public async Task<IActorResult> MyMessageHandler(IMessage message) { // } }
フレームワークは、アクターのハンドラーメソッドが宣言されたタイプのメッセージのみを受信することを保証します。 さらに、返される結果は、さまざまな配信パターンを持つ任意のタイプの1つ以上のメッセージです。 つまり、最初の送信者に応答を送信すると同時に、他の人に何か他のものについて通知することができます。
public class MyMessageProcessor : Actor { [MessageHandlerDefinition(typeof (MyMessage))] public async Task<IActorResult> MyMessageHandler(IMessage message) { MyMessage payload = message.GetPayload<MyMessage>(); var result = await UpdateDb(payload); IMessage response = Message.Create(new ResponseMessage(result)); IMessage notifyRequest = Message.Create(new NotifyMessage(result), DistributionPattern.Broadcast); return new ActionResult(response, notifyRequest); } }
3. ActorHost
ActorHostについてはまだ話していません。 これは、いくつかの機能を実行するコンポーネントです。
- 登録されているすべてのアクタのハンドラメソッドへのリンクを保存します
- MessageRouterにハンドラーを登録します
- MessageRouterからActorsへの処理のためのメッセージを受け取り、応答をMessageRouterに送り返します 。
ActorHostのハンドラーメソッドの呼び出しは、1つのスレッドで行われます(例外は非同期メソッドです)。 したがって、 ActorHostは、同じメッセージのハンドラーの複数の登録をサポートしていません。 同じプロセス内で同じタイプのアクターをスケーリングする必要がある場合、それぞれに対してActorHostの新しいインスタンスを作成する必要があります。 ActorHostManagerは、 ActorHostsの選択と作成におけるこれらすべての困難を処理します。
// MyActor IActor actor = new MyActor(); // , ActorHost actorHostManager.AssignActor(actor); // , actor = new MyActor(); // , MyActor actorHostManager.AssignActor(actor, ActorHostInstancePolicy.AlwaysCreateNew);
4. MessageHub
少し戻って、すべてが始まったところに行きましょう。 1つのWCFサービスからコードをネットワーク上で利用可能な複数のコンポーネントに配布する必要から始まりました。 その結果、1つのプロセスで数百のメソッドを呼び出すのではなく、特定のメッセージフローを取得し、さらに別のサーバーに移動します。 ただし、サービスのエンドユーザーの機能と動作は、理想的には同じままである必要があります。 つまり、以前にクライアントがサービスメソッドを同期的に呼び出し、応答を受信することを期待していた場合、このすべてのキノでクライアントの作業パターンが劇的に変化することはありません。 このすべてのメッセージフローから、クライアントへの回答を特定し、それを返送する必要があります。
MessageHubは、この問題を解決するように設計されています。 これを使用すると、応答を待たずにキノネットワークにメッセージを送信できます。
IPayload payload = message.GetPayload<MyMessage>(); IMessage message = Message.CreateFlowStartMessage(payload); messageHub.SendOneWay(message);
または、送信者が特定の応答を期待していることを示すこともできます。
// , IMessage request = Message.CreateFlowStartMessage(new StartMessage()); // , ICallbackPoint callbackPoint = CallbackPoint.Create<ResultMessage>(); // using(IPromise promise = messageHub.EnqueueRequest(request, callbackPoint)) { if(promise.GetResponse().Wait(timeout)) { // ResultMessage result = promise.GetResponse().Result.GetPayload<ResultMessage>(); } else { // … } }
5. MessageRouter
MessageRouterはキノネットワーク上のノードです。 他のコンポーネント、 ActorHostsおよびMessageHubsは 、メッセージングのためにそれに接続されます。 同様に、 MessageRoutersは独自の種類を見つけ、 Rendezvousサービスを使用して相互に接続し、 キノネットワークを形成します。
キノはトランスポートとしてNetMQライブラリを使用します。 実際には釘でフレームワークに押し込まれ、他の車両を使用する予定はありませんでした。
だから、メッセージのルーティング。 次のアルゴリズムに従って実行されます。
• ユニキャストメッセージ:
ActorHost MessageHub, MessageRouter , !
• ブロードキャストメッセージ:
ActorHosts MessageHubs, broadcast- Actor MessageRouter , !
• ダイレクトメッセージ:
unicast- MessageRouter , , (ReceiverNode), ! < broadcast->
6.ランデブー
Rendezvousサービスは、同じキノネットワークのすべてのノードにアドレスを設定する必要がある唯一の既知のサービスです。 次の機能を実行します。
- ブロードキャスト-ルーティングの変更に関するメッセージの送信:新規の追加と無効なルートの削除、
- ブロードキャスト -PINGメッセージを送信して、接続されたノードを監視します。
- ブロードキャスト-接続されたノードから応答PONGメッセージを送信します。
必要に応じて、 Rendezvousサービスをサーバークラスターにインストールできます。 コンセンサスによって選択されたリーダーは、上記のすべての機能を担当します。 クラスターが「落下」した場合、 キノネットワークは引き続き機能します。 ただし、ルーティングの変更に関する情報は利用できません。 Rendezvousサービスが復元されると、ノードはネットワーク構成の更新を受け取ります。
未解決の質問
- まあ、実際には、プロダクションで何かを見るために。 これになるまで......
- 同じネットワーク上で異なるワイヤ形式のメッセージを処理する方法
- Rendezvousサービスへの多数の接続で発生する可能性のある問題、 PONGメッセージのバッチ処理
- 複数のキノネットワークの組み合わせ、つまり、異なるRendezvousサーバー/クラスターに接続されたノード間のルーティング
Githubのキノプロジェクト: https : //github.com/iiwaasnet/kino
ウィキ: https : //github.com/iiwaasnet/kino/wiki