42

「恥ずかしいほど並列」の問題を解決するErlang/OTPベースのシステムを構築したいと思います。

私はすでに読んだ/スキミングしました:

  • いくつかのErlangを学びましょう。
  • プログラミングErlang(アームストロング);
  • Erlangプログラミング(Cesarini);
  • Erlang/OTPの動作中。

プロセス、メッセージング、スーパーバイザー、gen_servers、ロギングなどの要点があります。

特定のアーキテクチャの選択が関係するアプリケーションに依存することは理解していますが、それでもERlang/OTPシステム設計のいくつかの一般的な原則を知りたいと思います。

スーパーバイザーを備えたいくつかのgen_serversから始めて、それを段階的に構築する必要がありますか?

スーパーバイザーは何人必要ですか?システムのどの部分をプロセスベースにするかを決定するにはどうすればよいですか?ボトルネックを回避するにはどうすればよいですか?

後でログを追加する必要がありますか?

Erlang / OTP分散型フォールトトレラントマルチプロセッサシステムアーキテクチャへの一般的なアプローチは何ですか?

4

1 に答える 1

114

スーパーバイザーを備えたいくつかの gen_servers から始めて、それを段階的に構築する必要がありますか?

ここでは、Erlang アーキテクチャの重要なコンポーネントが 1 つ欠けています: アプリケーションです! (つまり、ソフトウェア アプリケーションではなく、OTP アプリケーションの概念です)。

アプリケーションをコンポーネントと考えてください。システム内のコンポーネントは、特定の問題を解決したり、リソースの一貫したセットを担当したり、システムから重要または複雑なものを抽象化したりします。

Erlang システムを設計する際の最初のステップは、必要なアプリケーションを決定することです。一部は Web からそのまま取得でき、これらをライブラリと呼ぶことができます。それ以外は自分で書く必要があります (そうしないと、この特定のシステムは必要ありません)。通常、これらのアプリケーションをビジネス ロジックと呼びます (多くの場合、いくつかのライブラリも自分で作成する必要がありますが、ライブラリと、すべてを結び付けるコア ビジネス アプリケーションとを区別しておくと便利です)。

スーパーバイザーは何人必要ですか?

監視するプロセスの種類ごとに 1 人のスーパーバイザーが必要です。

同一の臨時労働者の束?それらすべてを支配する1人の監督者。

異なる責任と再起動戦略を持つ異なるプロセス? 適切な階層での、さまざまなタイプのプロセスごとのスーパーバイザー (いつ再起動する必要があるか、および他のどのプロセスを停止する必要があるかによって異なります)。

場合によっては、同じスーパーバイザーの下にさまざまな種類のプロセスをまとめて配置しても問題ありません。これは通常、常に実行されるいくつかのシングルトン プロセス (たとえば、1 つの HTTP サーバー スーパーバイザー、1 つの ETS テーブル オーナー プロセス、1 つの統計コレクター) がある場合です。その場合、それぞれに 1 人のスーパーバイザーを配置するのは面倒なので、1 つ下のスーパーバイザーを追加するのが一般的です。これを行うときに特定の再起動戦略を使用することの影響に注意してください。たとえば、Web サーバーがクラッシュした場合に統計プロセスを停止しないようにします (one_for_oneこのような場合に使用する最も一般的な戦略です)。プロセス間に依存関係がないように注意してください。one_for_oneスーパーバイザー。プロセスが別のクラッシュしたプロセスに依存している場合、そのプロセスもクラッシュする可能性があり、スーパーバイザーの再起動強度が頻繁にトリガーされ、スーパーバイザー自体がすぐにクラッシュします。これは、構成された強度と期間によって再起動を完全に制御する 2 つの異なるスーパーバイザーを使用することで回避できます (長い説明)。

システムのどの部分をプロセスベースにするかを決定するにはどうすればよいですか?

システム内のすべての同時アクティビティは、独自のプロセスにある必要があります。並行性の間違った抽象化は、初期の Erlang システム設計者が最もよく犯す間違いです。

並行処理に慣れていない人もいます。彼らのシステムには、それが少なすぎる傾向があります。すべてを順番に実行する 1 つのプロセス、またはいくつかの巨大なプロセス。これらのシステムは、通常、コードの匂いに満ちており、コードは非常に厳格でリファクタリングが困難です。また、Erlang で利用可能なすべてのコアを使用しない可能性があるため、処理が遅くなります。

並行性の概念をすぐに理解しても、それを最適に適用できない人もいます。彼らのシステムはプロセスの概念を過度に使用する傾向があり、多くのプロセスが他の作業を行っているのを待ってアイドル状態になります。これらのシステムは、不必要に複雑になり、デバッグが困難になる傾向があります。

本質的に、両方のバリアントで同じ問題が発生し、利用可能なすべての同時実行を使用せず、システムから最大のパフォーマンスを引き出すことができません。

単一の責任の原則に固執し、システム内のすべてのに同時のアクティビティに対してプロセスを持つというルールを順守する場合は、問題ありません。

アイドル状態のプロセスを持つ正当な理由があります。重要な状態を保持する場合もあれば、一部のデータを一時的に保持して後でプロセスを破棄する場合もあり、外部イベントを待機する場合もあります。より大きな落とし穴は、重要なメッセージを大部分が非アクティブなプロセスの長いチェーンに渡すことです。これは、大量のコピーでシステムの速度が低下し、より多くのメモリを使用するためです。

ボトルネックを回避するにはどうすればよいですか?

言うのは難しいですが、システムとそれが何をしているかに大きく依存します。ただし、一般的には、アプリケーション間で適切に責任を分担している場合は、システムの残りの部分とは別に、ボトルネックと思われるアプリケーションをスケーリングできるはずです。

ここでの黄金律は、測定、測定、測定です。測定するまで、何か改善すべき点があるとは思わないでください。

Erlang は、並行性をインターフェースの背後に隠すことができるという点で優れています (暗黙の並行性として知られています)。たとえば、通常のmodule:function(Arguments)インターフェイスである機能モジュール API を使用すると、呼び出し元がそれを知らなくても、何千ものプロセスを生成できます。抽象化と API が適切であれば、ライブラリの使用を開始した後でも、いつでもライブラリを並列化または最適化できます。

そうは言っても、ここにいくつかの一般的なガイドラインがあります:

  • メッセージを受信者に直接送信するようにしてください。中間プロセスを介してメッセージをチャネリングまたはルーティングすることは避けてください。そうしないと、システムは実際には機能せずに、メッセージ (データ) の移動に時間を費やしてしまいます。
  • gen_servers などの OTP 設計パターンを過度に使用しないでください。多くの場合、プロセスを開始し、コードを実行して終了するだけで済みます。このため、gen_server はやり過ぎです。

おまけのアドバイスとして、プロセスを再利用しないでください。Erlang でのプロセスのスポーンは非常に安価で迅速であるため、プロセスの寿命が過ぎたプロセスを再利用する意味はありません。場合によっては、状態を再利用することが理にかなっている場合があります (ファイルの複雑な解析など) が、別の場所 (ETS テーブル、データベースなど) に標準的に保存する方が適切です。

後でログを追加する必要がありますか?

今すぐロギングを追加する必要があります。バージョン 21 から Erlang/OTP に付属するLoggerと呼ばれる優れた組み込み API があります。

logger:error("The file does not exist: ~ts",[Filename]),
logger:notice("Something strange happened!"),
logger:debug(#{got => connection_request, id => Id, state => State},
             #{report_cb => fun(R) -> {"~p",[R]} end}),

この新しい API にはいくつかの高度な機能があり、ログが必要なほとんどのケースに対応するはずです。古いが、まだ広く使用されているサードパーティ ライブラリLagerもあります。

Erlang/OTP 分散フォールト トレラント マルチプロセッサ システム アーキテクチャへの一般的なアプローチは何ですか?

上記の内容を要約すると、次のようになります。

  • システムをアプリケーションに分割する
  • プロセスのニーズと依存関係に応じて、プロセスを正しい監督階層に配置します
  • システム内のすべての真に同時のアクティビティに対してプロセスを用意する
  • システム内の他のコンポーネントに対して機能的な API を維持します。これにより、次のことが可能になります。
    • コードを使用しているコードを変更せずにコードをリファクタリングする
    • 後でコードを最適化する
    • 必要に応じてシステムを配布します (API の背後にある別のノードを呼び出すだけです! 呼び出し元は気付かないでしょう!)
    • コードをより簡単にテストできます (テスト ハーネスを設定する手間が減り、使用方法が理解しやすくなります)。
  • 別の何かが必要になるまで、OTP で利用可能なライブラリの使用を開始します (時が来ればわかります)。

よくある落とし穴:

  • プロセスが多すぎる
  • プロセスが少なすぎる
  • ルーティングが多すぎる (転送されたメッセージ、チェーンされたプロセス)
  • 応募が少なすぎる(実際、逆のケースは見たことがない)
  • 抽象化が不十分です (リファクタリングと推論が難しくなります。また、テストも難しくなります!)
于 2011-09-05T12:38:52.787 に答える