実行エンジン

Mule Runtime Engine は、非ブロックで非同期の実行用に調整されたリアクティブ実行エンジンを実装します。
リアクティブプログラミングの詳細は、​https://en.wikipedia.org/wiki/Reactive_programming を参照してください。

タスク指向の実行モデルでは、非ブロック IO を高い同時実行レベルで透過的に活用できます。つまり、ユーザはフローのスレッドや非同期性を考慮する必要がありません。Mule フロー内の各操作は実行に関するメタデータを提供するタスクで、Mule はそのメタデータに基づいて調整の決定を行います。

Mule イベントプロセッサは、CPU Intensive (CPU 負荷大)、CPU Light (CPU 負荷小)、IO Intensive (IO 負荷大) のいずれの操作を行うかを Mule に示します。これらのワークロード種別によって、Mule はさまざまなワークロードを調整することができ、最適なパフォーマンスを実現するためにユーザがスレッドプールを手動で管理する必要がなくなります。Mule は、システム内で使用可能なリソース (メモリや CPU コアなど) を調査し、スレッドプールを自動的に調整します。

処理種別

Mule イベントプロセッサは、実行する処理の種類を Mule に示します。これは、次のいずれかです。

  • CPU Light (CPU 負荷小)
    短時間の操作 (約 10 ミリ秒) または非ブロック IO 用。たとえば、ロガー (logger​) や HTTP 要求操作 (http:request​) など。これらのタスクではブロック IO を実行できません。ログで該当する文字列は CPU_LIGHT​ と CPU_LIGHT_ASYNC​ です。

  • Blocking IO (ブロック IO)
    コール元スレッドをブロックする IO 用。たとえば、Database Select 操作 (db:select​) や SFTP 読み取り (sftp:read​) など。ログで該当する文字列は BLOCKING​ と IO​ です。

  • CPU Intensive (CPU 負荷大)
    CPU の負荷が大きい計算用。通常は実行時間が 10 ミリ秒を超えます。これらのタスクでは IO アクティビティ を実行できません。たとえば Transform Message コンポーネント (ee:transform​) など。ログで該当する文字列は CPU_INTENSIVE​ です。

特定のコンポーネントやモジュールがサポートする処理種別については、それらのドキュメントを参照してください。指定しない場合、デフォルトは CPU Light (CPU 負荷小) です。

Mule SDK を使用して作成されたコネクタの場合は、コネクタの実装方法に基づいて SDK によって最適な処理種別が決定されます。そのメカニズムについての詳細は、Mule SDK のドキュメント​を参照してください。

スレッド

コンポーネントの処理種別に基づき Mule はそのコンポーネントをその種別の処理専用に調整したスレッドプールで実行します。これらのスレッドプールは Mule によって管理され、同じ Mule インスタンス内のすべてのアプリケーションで共有されます。 Mule は、起動時にシステム内で使用可能なリソース (メモリや CPU コアなど) を調査し、実行中の環境に合わせてスレッドプールを自動的に調整します。デフォルト設定は、パフォーマンステストによって確立されたもので、ほとんどの状況で最適な値になっています。

異なるスレッドプールを使用することで Mule のリソース管理効率が向上し、同じ量のワークロードを処理するために必要なスレッド数 (およびそれに伴うメモリフットプリント) が大幅に削減されます。

次に、各スレッドプールの主要な側面について説明します。

CPU Light (CPU 負荷小)

CPU Light (CPU 負荷小) は、比較的小さなスレッドプール用です (デフォルトでは使用可能なコアごとに 2 スレッド)。

CPU Light (CPU 負荷小) プロセッサの実行以外に、このプールはフロー内のプロセッサ (ルータを含む) 間のイベントのハンドオフと、非ブロック IO の応答処理も実行します。

アプリケーションでスループットが落ちたり応答がなくなったりした場合は、一部のコードが CPU Light (CPU 負荷小) スレッドプールを誤用している可能性があります。この問題は、ランタイムのスレッドダンプを取得し、WAITING​ または BLOCKED​ を探すか、CPU Light (CPU 負荷小) スレッド内で実行時間が長いプロセスを探すことで簡単にチェックできます。

CPU Intensive (CPU 負荷大)

CPU Intensive (CPU 負荷大) も小さなスレッドプール (デフォルトでは使用可能なコアごとに 2 スレッド) ですが、より多くのタスクを受け付けるためのキューがあります。

IO

IO は、必要に応じて大きくなる可変スレッドプールです。

このプールで実行するタスクは、ほとんどの時間、CPU で処理されるのではなく WAITING​ または BLOCKED​ の状態でいるため、他のプールと競合しません。

また、トランザクションがアクティブである場合にも (多くのトランザクションマネージャでは同じトランザクションのすべてのステップを同じスレッドによって処理する必要があるため) IO​ プールが使用されます。

カスタムプール

3 つの主要なスレッドプール以外に、Mule または一部のコンポーネントによって特定の目的のために次のような追加プールが作成される場合があります。

  • NIO セレクタ
    非ブロック IO を有効にします。各コネクタは、必要に応じて何個でも作成できます。

  • 繰り返しタスクプール
    一部のコネクタやコンポーネントによって、繰り返しタスク (有効期限監視、キューコンシューマなど) を実行するための特定のプールが作成される場合があります。

プロアクタパターン

プロアクタは非同期実行のための設計パターンです。プロアクタ設計パターンのしくみを理解するには、​https://en.wikipedia.org/wiki/Proactor_pattern を参照してください。

この設計パターンに従うと、すべてのタスクは各 Mule スレッドプールに対応するカテゴリに分類され、各タスクは対応するスレッドプールに送信されて実行されます。

次の例を考えてみます。フローは JSON 形式で記述された Person オブジェクトの JSON 配列を取り込み、HTTP 要求によってコンテンツをプッシュし、最初のエントリの名前を取得して追加処理を行います。

<flow>
  <sftp:read path="personsArray.json" /> (1)
  <http:request path="/persons" method="POST" /> (2)
  <set-variable variableName="firstEntryName" value="#[payload[0].name]" /> (3)
  <ee:transform ... /> (4)
  <logger message="#[vars.firstEntryName]" /> (5)
</flow>

プロアクタパターンに従うと、Mule はタスクを次のように送信します。

1 ブロック操作 (<sftp:read>​) が IO プールで実行されます。
2 <http:request>​ は非ブロック操作です。Mule は、現在のスレッドで要求を実行します。フローが応答を受信すると、Mule は CPU_LIGHT​ (CPU 負荷小) プールに切り替えます。
3 <set-variable>​ 操作は非常に短時間なので CPU_LIGHT​ (CPU 負荷小) のままで実行します。スレッドの切り替えは行いません。
4 <ee:transform>​ は計算負荷が高い変換である可能性があるため、Mule は CPU_INTENSIVE​ (CPU 負荷大) プールに切り替えます。
5 Logger は CPU_INTENSIVE​ (CPU 負荷大) のままで実行します。スレッドの切り替えは行いません。
遅延に関する最適化のため、IO または CPU_INTENSIVE​ (CPU 負荷大) タスクの後に CPU_LIGHT​ (CPU 負荷小) タスクを実行する場合にはスレッド切り替えが省略されます。この最適化を行う理由は、その CPU_LIGHT​ タスクを実行する方がスレッド切り替えよりも負荷が小さい可能性が高いということです。

設定

Mule Runtime Engine は、起動時に CPU やメモリなどの利用可能なリソースを考慮する数式を適用して自動的にスレッドプールを設定します。これらの数式は MULE_HOME/conf/schedulers-pools.conf​ ファイルを編集することで変更できます。ただし、スレッドプールの設定を変更することはお勧めしません。

この設定はグローバルであり、Mule インスタンス全体に影響することに注意してください。

アプリケーションレベルでの設定

アプリケーションコードに次のコードを追加することで、特定のアプリケーションで各プールの設定を定義して使用できます。

<ee:scheduler-pools gracefulShutdownTimeout="15000">
   <ee:cpu-light
           poolSize="2"
           queueSize="1024"/>
   <ee:io
           corePoolSize="1"
           maxPoolSize="2"
           queueSize="0"
           keepAlive="30000"/>
   <ee:cpu-intensive
           poolSize="4"
           queueSize="2048"/>
</ee:scheduler-pools>

考慮事項

アプリケーションレベルでプール設定を適用すると、Mule アプリケーション用の完全に新しいスレッドプールセットが作成されます。この設定によって scheduler-conf.properties​ ファイル内で設定されるデフォルト設定は変更されません。オンプレミスデプロイメントでは同じ Mule インスタンスに多くの Mule アプリケーションをデプロイできるため、特にこれが重要です。

デフォルトの設定で Mule を実行することをお勧めします。カスタムスレッドプール設定を使用する場合は、実際のシナリオに含まれるすべてのアプリケーションを使用して負荷テストとストレステストを実行することによって、スレッド設定への変更を検証し、Mule 4 でのスレッドプールの動作について理解することをお勧めします。

いずれの場合も、カスタマイズした設定を本番に移行する前にサポートに相談することをお勧めします。

バックプレッシャー

負荷が大きい場合、Mule が特定のイベントを処理するために使用可能なリソースがないことがあります。この問題は、スレッドがすべて使用中で新しく受信したイベントのハンドオフを実行できない場合や、現在のフローの maxConcurrency​ 値がすでに超過している場合に発生する可能性があります。

Mule がイベントを処理できない場合、その状況に関する次のメッセージが記録されます: ​Flow 'flowName' is unable to accept new events at this time​ (フロー「flowName」は、現在新しいイベントを受け付けられません)。また、フローソースには、必要なアクションを実行するように通知されます。 バックプレッシャーのために Mule が実行するアクションは、各コネクタの取得元に固有です。たとえば、http:listener​ は 503​ エラーコードを返す可能性があり、メッセージブローカーリスナはリソースが使用可能になるまで待つかメッセージを削除する可能性があります。

場合によっては、取得元は処理しきれないデータを取得しないようにリモートシステムから切断し、サーバが正常な状態に戻ったときに再接続することがあります。

Was this article helpful?

💙 Thanks for your feedback!