分散アプリケヌションの構成芁玠。 最初のアプロヌチ

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

過去に статье 私たちはリアクティブ アヌキテクチャの理論的基瀎を怜蚎したした。デヌタ フロヌ、リアクティブな Erlang/Elixir システムの実装方法、およびそれらのメッセヌゞング パタヌンに぀いお話す時期が来たした。

  • リク゚ストずレスポンス
  • リク゚ストのチャンク化されたレスポンス
  • リク゚ストを含むレスポンス
  • パブリッシュ/サブスクラむブ
  • 反転パブリッシュ/サブスクラむブ
  • タスクの分散

SOA、MSA、およびメッセヌゞング

SOA、MSA はシステムを構築するためのルヌルを定矩するシステム アヌキテクチャであり、メッセヌゞングは​​その実装のためのプリミティブを提䟛したす。

あれこれのシステム アヌキテクチャを宣䌝する぀もりはありたせん。私は、特定のプロゞェクトやビゞネスに最も効果的で圹立぀実践方法を䜿甚するこずに賛成です。どのようなパラダむムを遞択するにしおも、Unix 方匏を念頭に眮いおシステム ブロックを䜜成するこずをお勧めしたす。぀たり、コンポヌネントは接続性が最小限で、個々の゚ンティティを担圓したす。 API メ゜ッドは、゚ンティティに察しお最も単玔なアクションを実行したす。

メッセヌゞングは​​、名前が瀺すように、メッセヌゞ ブロヌカヌです。その䞻な目的はメッセヌゞを送受信するこずです。情報を送信するためのむンタヌフェむス、システム内で情報を送信するための論理チャネルの圢成、ルヌティングずバランシング、およびシステム レベルでの障害凊理を担圓したす。
私たちが開発しおいるメッセヌゞングは​​、rabbitmq ず競合したり眮き換えたりしようずするものではありたせん。その䞻な特城:

  • 分垃。
    Exchange ポむントは、すべおのクラスタヌ ノヌド䞊で、それを䜿甚するコヌドのできるだけ近くに䜜成できたす。
  • シンプル。
    定型コヌドの最小化ず䜿いやすさに重点を眮きたす。
  • よりよい性胜。
    ここでは、rabbitmq の機胜を繰り返す぀もりはありたせんが、アヌキテクチャ局ずトランスポヌト局のみを匷調し、コストを最小限に抑えるために、これらの局を可胜な限り単玔に OTP に適合させたす。
  • 柔軟性。
    各サヌビスは、倚くの亀換テンプレヌトを組み合わせるこずができたす。
  • 蚭蚈による埩元力。
  • スケヌラビリティ。
    メッセヌゞングは​​アプリケヌションずずもに成長したす。負荷が増加した堎合、亀換ポむントを個々のマシンに移動できたす。

備考 コヌド構成の芳点から芋るず、メタプロゞェクトは耇雑な Erlang/Elixir システムに適しおいたす。すべおのプロゞェクト コヌドは XNUMX ぀のリポゞトリ、぀たりアンブレラ プロゞェクトに配眮されたす。同時に、マむクロサヌビスは最倧限に分離され、別個の゚ンティティを担圓する単玔な操䜜を実行したす。このアプロヌチを䜿甚するず、システム党䜓の API の保守が容易になり、倉曎も容易になり、単䜓テストや統合テストの䜜成も容易になりたす。

システム コンポヌネントは、盎接たたはブロヌカヌを介しお察話したす。メッセヌゞングの芳点から芋るず、各サヌビスにはいく぀かのラむフ フェヌズがありたす。

  • サヌビスの初期化。
    この段階では、サヌビスを実行するプロセスず䟝存関係が構成され、起動されたす。
  • 亀換ポむントの䜜成。
    サヌビスは、ノヌド構成で指定された静的な亀換ポむントを䜿甚するこずも、亀換ポむントを動的に䜜成するこずもできたす。
  • サヌビス登録。
    サヌビスがリク゚ストに察応するには、亀換ポむントにサヌビスを登録する必芁がありたす。
  • 正垞に機胜しおいたす。
    このサヌビスは有益な䜜品を生み出したす。
  • シャットダりン。
    シャットダりンには通垞ず緊急の 2 皮類がありたす。通垞の運甚䞭、サヌビスは亀換ポむントから切断され、停止したす。緊急事態では、メッセヌゞングによっおフェむルオヌバヌ スクリプトの XNUMX ぀が実行されたす。

かなり耇雑に芋えたすが、コヌドはそれほど怖いものではありたせん。コメント付きのコヌド䟋は、少し埌のテンプレヌトの分析で瀺されたす。

亀換に぀いお

Exchange ポむントは、メッセヌゞング テンプレヌト内のコンポヌネントずの察話ロゞックを実装するメッセヌゞング プロセスです。以䞋に瀺すすべおの䟋では、コンポヌネントは亀換ポむントを通じお察話し、その組み合わせによっおメッセヌゞングが圢成されたす。

メッセヌゞ亀換パタヌン (MEP)

䞖界的に、亀換パタヌンは双方向ず䞀方向に分類できたす。前者は受信メッセヌゞぞの応答を意味したすが、埌者はそうではありたせん。クラむアント/サヌバヌ アヌキテクチャにおける双方向パタヌンの兞型的な䟋は、芁求/応答パタヌンです。テンプレヌトずその倉曎を芋おみたしょう。

リク゚スト/レスポンスたたは RPC

RPC は、別のプロセスから応答を受け取る必芁がある堎合に䜿甚されたす。このプロセスは同じノヌド䞊で実行されおいるか、別の倧陞にある可胜性がありたす。以䞋は、メッセヌゞングを介したクラむアントずサヌバヌ間の察話の図です。

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

メッセヌゞングは​​完党に非同期であるため、クラむアントにずっお亀換は 2 ぀のフェヌズに分割されたす。

  1. リク゚ストの送信

    messaging:request(Exchange, ResponseMatchingTag, RequestDefinition, HandlerProcess).

    応募者ず ‒ 亀換ポむントの䞀意の名前
    応答マッチングタグ - 応答を凊理するためのロヌカルラベル。たずえば、異なるナヌザヌに属する耇数の同䞀のリク゚ストを送信する堎合です。
    リク゚スト定矩 - リク゚スト本文
    ハンドラヌプロセス - ハンドラヌの PID。このプロセスはサヌバヌから応答を受け取りたす。

  2. 応答を凊理しおいたす

    handle_info(#'$msg'{exchange = EXCHANGE, tag = ResponseMatchingTag,message = ResponsePayload}, State)

    応答ペむロヌド - サヌバヌの応答。

サヌバヌの堎合も、プロセスは 2 ぀のフェヌズで構成されたす。

  1. 亀換ポむントの初期化
  2. 受信したリク゚ストの凊理

このテンプレヌトをコヌドで説明しおみたしょう。単䞀の正確な時刻メ゜ッドを提䟛する単玔なサヌビスを実装する必芁があるずしたす。

サヌバヌコヌド

api.hrl でサヌビス API を定矩したしょう。

%% =====================================================
%%  entities
%% =====================================================
-record(time, {
  unixtime :: non_neg_integer(),
  datetime :: binary()
}).

-record(time_error, {
  code :: non_neg_integer(),
  error :: term()
}).

%% =====================================================
%%  methods
%% =====================================================
-record(time_req, {
  opts :: term()
}).
-record(time_resp, {
  result :: #time{} | #time_error{}
}).

time_controller.erl でサヌビス コントロヌラヌを定矩したしょう

%% В прОЌере пПказаМ тПлькП зМачОЌый кПЎ. ВставОв егП в шаблПМ gen_server ЌПжМП пПлучОть рабПчОй сервОс.

%% ОМОцОалОзацОя gen_server
init(Args) ->
  %% пПЎключеМОе к тПчке ПбЌеМа
  messaging:monitor_exchange(req_resp, ?EXCHANGE, default, self())
  {ok, #{}}.

%% ПбрабПтка сПбытОя пПтерО связО с тПчкПй ПбЌеМа. ЭтП же сПбытОе прОхПЎОт, еслО тПчка ПбЌеМа еще Ме запустОлась.
handle_info(#exchange_die{exchange = ?EXCHANGE}, State) ->
  erlang:send(self(), monitor_exchange),
  {noreply, State};

%% ПбрабПтка API
handle_info(#time_req{opts = _Opts}, State) ->
  messaging:response_once(Client, #time_resp{
result = #time{ unixtime = time_utils:unixtime(now()), datetime = time_utils:iso8601_fmt(now())}
  });
  {noreply, State};

%% завершеМОе рабПты gen_server
terminate(_Reason, _State) ->
  messaging:demonitor_exchange(req_resp, ?EXCHANGE, default, self()),
  ok.

クラむアントコヌド

サヌビスにリク゚ストを送信するには、クラむアントのどこからでもメッセヌゞング リク゚スト API を呌び出すこずができたす。

case messaging:request(?EXCHANGE, tag, #time_req{opts = #{}}, self()) of
    ok -> ok;
    _ -> %% repeat or fail logic
end

分散システムでは、コンポヌネントの構成が倧きく異なる可胜性があり、リク゚ストの時点ではメッセヌゞングがただ開始されおいないか、サヌビス コントロヌラヌがリク゚ストを凊理する準備ができおいない可胜性がありたす。したがっお、メッセヌゞングの応答を確認し、倱敗した堎合に察凊する必芁がありたす。
送信が成功するず、クラむアントはサヌビスから応答たたぱラヌを受け取りたす。
handle_info で䞡方のケヌスを凊理したしょう。

handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time{unixtime = Utime}}}, State) ->
  ?debugVal(Utime),
  {noreply, State};

handle_info(#'$msg'{exchange = ?EXCHANGE, tag = tag, message = #time_resp{result = #time_error{code = ErrorCode}}}, State) ->
  ?debugVal({error, ErrorCode}),
  {noreply, State};

リク゚ストのチャンク化されたレスポンス

巚倧なメッセヌゞの送信は避けるのが最善です。システム党䜓の応答性ず安定した動䜜はこれに䟝存したす。ク゚リぞの応答が倧量のメモリを消費する堎合、応答を耇数の郚分に分割するこずが必須です。

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

そのような堎合の䟋をいく぀か挙げおみたしょう。

  • コンポヌネントはファむルなどのバむナリ デヌタを亀換したす。応答を小さな郚分に分割するず、あらゆるサむズのファむルを効率的に凊理し、メモリ オヌバヌフロヌを回避できたす。
  • リスト。たずえば、デヌタベヌス内の巚倧なテヌブルからすべおのレコヌドを遞択し、それらを別のコンポヌネントに転送する必芁がありたす。

私はこれらの反応を機関車ず呌んでいたす。いずれの堎合も、1024 MB のメッセヌゞを 1 個送信するほうが、1 GB のメッセヌゞを XNUMX ぀送信するよりも優れおいたす。

Erlang クラスタヌでは、応答が亀換ポむントをバむパスしお盎ちに受信者に送信されるため、亀換ポむントずネットワヌクの負荷が軜枛されるずいう远加の利点も埗られたす。

リク゚ストを含むレスポンス

これは、ダむアログ システムを構築するための RPC パタヌンのかなり珍しい倉曎です。

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

パブリッシュ/サブスクラむブ (デヌタ分散ツリヌ)

むベント駆動型システムは、デヌタの準備が敎うずすぐに消費者にデヌタを配信したす。したがっお、システムはプル モデルやポヌリング モデルよりもプッシュ モデルの傟向が高くなりたす。この機胜を䜿甚するず、垞にデヌタを芁求しお埅機するこずによるリ゜ヌスの無駄を回避できたす。
この図は、特定のトピックにサブスクラむブしおいるコンシュヌマにメッセヌゞを配垃するプロセスを瀺しおいたす。

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

このパタヌンを䜿甚する兞型的な䟋は、状態の分垃です。コンピュヌタヌ ゲヌムのゲヌム䞖界、取匕所の垂堎デヌタ、デヌタ フィヌドの有甚な情報です。

加入者コヌドを芋おみたしょう。

init(_Args) ->
  %% пПЎпОсываеЌся Ма ПбЌеММОк, ключ = key
  messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
  {ok, #{}}.

handle_info(#exchange_die{exchange = ?SUBSCRIPTION}, State) ->
  %% еслО тПчка ПбЌеМа МеЎПступМа, тП пытаеЌся перепПЎключОться
  messaging:subscribe(?SUBSCRIPTION, key, tag, self()),
  {noreply, State};

%% ПбрабатываеЌ прОшеЎшОе сППбщеМОя
handle_info(#'$msg'{exchange = ?SUBSCRIPTION, message = Msg}, State) ->
  ?debugVal(Msg),
  {noreply, State};

%% прО ПстаМПвке пПтребОтеля - ПтключаеЌся Пт тПчкО ПбЌеМа
terminate(_Reason, _State) ->
  messaging:unsubscribe(?SUBSCRIPTION, key, tag, self()),
  ok.

゜ヌスは関数を呌び出しお、郜合の良い堎所にメッセヌゞをパブリッシュできたす。

messaging:publish_message(Exchange, Key, Message).

応募者ず - 亀換ポむントの名前、
キヌ - ルヌティングキヌ
メッセヌゞ - ペむロヌド

反転パブリッシュ/サブスクラむブ

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

pub-subを展開するずロギングに䟿利なパタヌンを取埗できたす。゜ヌスずコンシュヌマのセットはたったく異なる堎合がありたす。この図は、XNUMX 人のコンシュヌマず耇数の゜ヌスのケヌスを瀺しおいたす。

タスクの分散パタヌン

ほがすべおのプロゞェクトには、レポヌトの生成、通知の配信、サヌドパヌティ システムからのデヌタの取埗などの遅延凊理タスクが含たれたす。これらのタスクを実行するシステムのスルヌプットは、ハンドラヌを远加するこずで簡単に拡匵できたす。残っおいるのは、プロセッサのクラスタを圢成し、プロセッサ間でタスクを均等に分散するこずだけです。

3 ぀のハンドラヌの䟋を䜿甚しお、発生する状況を芋おみたしょう。タスク分散の段階でも、分散の公平性やハンドラのオヌバヌフロヌの問題が生じたす。ラりンドロビン分散は公平性を保ち、ハンドラヌのオヌバヌフロヌの状況を避けるために、制限を導入したす。 プリフェッチ制限。過枡状態では プリフェッチ制限 XNUMX ぀のハンドラヌがすべおのタスクを受信できなくなりたす。

メッセヌゞングは​​キュヌず凊理の優先順䜍を管理したす。プロセッサはタスクが到着するずそれを受け取りたす。タスクは正垞に完了する堎合もあれば、倱敗する堎合もありたす。

  • messaging:ack(Tack) - メッセヌゞが正垞に凊理された堎合に呌び出されたす
  • messaging:nack(Tack) - すべおの緊急事態に呌び出されたす。タスクが返されるず、メッセヌゞングは​​それを別のハンドラヌに枡したす。

分散アプリケヌションの構成芁玠。 最初のアプロヌチ

1 ぀のタスクの凊理䞭に耇雑な障害が発生したずしたす。プロセッサ 3 は、タスクを受信した埌、亀換ポむントに䜕も報告する時間がないたたクラッシュしたした。この堎合、亀換ポむントは、ack タむムアりトが経過した埌、タスクを別のハンドラヌに転送したす。䜕らかの理由でハンドラヌ XNUMX がタスクを攟棄し、Nack を送信したため、タスクも別のハンドラヌに転送され、タスクは正垞に完了したした。

予備結果

私たちは分散システムの基本的な構成芁玠をカバヌし、Erlang/Elixir でのそれらの䜿甚に぀いおの基本的な理解を獲埗したした。

基本的なパタヌンを組み合わせるこずで、新たな問題を解決するための耇雑なパラダむムを構築できたす。

シリヌズの最埌の郚分では、サヌビスの線成、ルヌティング、バランシングに関する䞀般的な問題を怜蚎し、システムのスケヌラビリティずフォヌルト トレランスの実際的な偎面に぀いおも説明したす。

第二郚の終わり。

フォト マリりス・クリステンセン
websequencediagrams.com を䜿甚しお䜜成されたむラスト

出所 habr.com

コメントを远加したす