Javaにおけるファンアウト/ファンインパターン:効率的なデータ処理のための並行性の最大化
別名
- スキャッター/ギャザー
ファンアウト/ファンインデザインパターンの目的
Javaにおけるファンアウト/ファンインデザインパターンは、タスクを並行処理できる複数のサブタスクに分割(ファンアウト)し、これらのサブタスクの結果を単一の結果に結合(ファンイン)することで、並行性を向上させ、処理時間を最適化することを目的としています。
ファンアウト/ファンインパターンの詳細な説明と実際の例
実世界の例
Javaにおけるファンアウト/ファンインパターンの実世界の例は、UberEatsやDoorDashのようなフードデリバリーサービスです。顧客が注文をすると、サービス(ファンアウト)はさまざまな品目を準備するために、個々のタスクを異なるレストランに送ります。各レストランは、注文の一部を準備するために独立して作業します。すべてのレストランがタスクを完了すると、デリバリーサービス(ファンイン)は、異なるレストランからの品目を単一の注文に集約し、すべてが顧客にまとめて配達されるようにします。この並行処理により効率が向上し、タイムリーな配達が保証されます。
平易な言葉で言うと
ファンアウト/ファンインパターンは、タスクを複数の同時プロセスまたはスレッドに分散し、結果を集約します。
Wikipediaによると
メッセージ指向ミドルウェアでは、ファンアウトパターンは、応答を待たずに、メッセージを1つまたは複数の宛先に並行して配信することで、情報交換をモデル化します。これにより、プロセスはタスクをさまざまな受信者に同時に分散できます。
一方、ファンインの概念は、通常、複数の入力を集約することを指します。デジタルエレクトロニクスでは、論理ゲートが処理できる入力数を表します。これらの概念を組み合わせると、ソフトウェアエンジニアリングにおけるファンアウト/ファンインパターンは、タスクを分散(ファンアウト)してから結果を集約(ファンイン)することを意味します。
Javaでのファンアウト/ファンインパターンのプログラム例
提供された実装には、数値を2乗し、結果を集約することを目的とした数値のリストが含まれています。FanOutFanIn
クラスは、数値のリストをSquareNumberRequest
オブジェクトとして受け取り、リクエストが完了したときに2乗された結果を収集するConsumer
インスタンスを受け取ります。各SquareNumberRequest
は、ランダムな遅延で数値を2乗し、予測不可能な時間に完了する長時間実行プロセスをシミュレートします。Consumer
インスタンスは、さまざまなSquareNumberRequest
オブジェクトから、異なる時間に利用可能になった結果を収集します。
これは、リクエストを非同期で分散することにより、ファンアウト/ファンインパターンを示すJavaのFanOutFanIn
クラスです。
public class FanOutFanIn {
public static Long fanOutFanIn(final List<SquareNumberRequest> requests, final Consumer consumer) {
ExecutorService service = Executors.newFixedThreadPool(requests.size());
// fanning out
List<CompletableFuture<Void>> futures = requests
.stream()
.map(request -> CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
return consumer.getSumOfSquaredNumbers().get();
}
}
Consumer
は、リクエストが完了したときに呼び出されるコールバッククラスとして使用されます。これは、すべてのリクエストの結果を集約します。
public class Consumer {
private final AtomicLong sumOfSquaredNumbers;
Consumer(Long init) {
sumOfSquaredNumbers = new AtomicLong(init);
}
public Long add(final Long num) {
return sumOfSquaredNumbers.addAndGet(num);
}
}
リクエストは、ランダムな遅延で数値を2乗し、2乗されるとConsumer
を呼び出すSquareNumberRequest
として表されます。
public class SquareNumberRequest {
private final Long number;
public void delayedSquaring(final Consumer consumer) {
var minTimeOut = 5000L;
SecureRandom secureRandom = new SecureRandom();
var randomTimeOut = secureRandom.nextInt(2000);
try {
// this will make the thread sleep from 5-7s.
Thread.sleep(minTimeOut + randomTimeOut);
} catch (InterruptedException e) {
LOGGER.error("Exception while sleep ", e);
Thread.currentThread().interrupt();
} finally {
consumer.add(number * number);
}
}
}
以下は、例を実行するメインメソッドを備えたAppクラスです。
public static void main(String[] args) {
final List<Long> numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L);
LOGGER.info("Numbers to be squared and get sum --> {}", numbers);
final List<SquareNumberRequest> requests =
numbers.stream().map(SquareNumberRequest::new).toList();
var consumer = new Consumer(0L);
// Pass the request and the consumer to fanOutFanIn or sometimes referred as Orchestrator
// function
final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer);
LOGGER.info("Sum of all squared numbers --> {}", sumOfSquaredNumbers);
}
この例を実行すると、次のコンソール出力が生成されます。
06:52:04.622 [main] INFO com.iluwatar.fanout.fanin.App -- Numbers to be squared and get sum --> [1, 3, 4, 7, 8]
06:52:11.465 [main] INFO com.iluwatar.fanout.fanin.App -- Sum of all squared numbers --> 139
Javaでファンアウト/ファンインパターンを使用するタイミング
Javaにおけるファンアウト/ファンインデザインパターンは、特にデータ処理、バッチ処理、さまざまなソースからの結果の集約が必要な状況で、タスクを分割して並行して実行できるシナリオに適しています。
ファンアウト/ファンインパターンのJavaチュートリアル
- Durable Functionsのファンアウト/ファンインシナリオ - クラウドバックアップの例(Microsoft)
- Azure Durable Functionsの理解 - パート8:ファンアウト/ファンインパターン(Don't Code Tired)
- ファンアウト/ファンインAPI統合パターンの理解(DZone)
Javaにおけるファンアウト/ファンインパターンの実世界での応用
- Javaにおけるファンアウト/ファンインパターンは、大規模なデータ処理アプリケーションで広く使用されています。
- 分散キャッシングやロードバランシングシステムなど、応答を配信する前に複数のソースからの集約を必要とするサービス。
ファンアウト/ファンインパターンの利点とトレードオフ
利点
- 並行処理によるパフォーマンスの向上。
- システムの応答性の向上。
- マルチコアプロセッサアーキテクチャの効率的な活用。
トレードオフ
- エラー処理の複雑さの増加。
- タスクの同期と結果の集約によるオーバーヘッドが増加する可能性。
- 並行実行をサポートする基盤となるインフラストラクチャの能力への依存。
関連するJavaデザインパターン
- MapReduce:ファンアウト/ファンインと同様に、MapReduceも多数のワーカー(マップ)にタスクを分散し、結果を集約(リデュース)するため、特に大規模なデータセットの処理に役立ちます。
- コマンド:コマンドパターンは、ファンアウト/ファンインがタスクの送信とタスク処理を分離するのと同じように、送信側と受信側の分離を容易にします。
- プロデューサー/コンシューマー:プロデューサーがタスクを分散し、複数のコンシューマーが処理し、結果を結合して、データ処理のスループットと効率を向上させるタスクの実行を整理することで、ファンアウト/ファンインと相乗的に機能します。