観光プラットフォームの問題
旅行プラットフォームの問題は何ですか?旅行プラットフォームを構築したい場合、多くの場合、ユーザーは複数の航空会社のフライト情報を同時に取得したいという要求があります。たとえば、北京から上海への航空券はいくらですか?このようなフライト情報を持っている航空会社はたくさんあるので、すべての航空会社のフライト、運賃、その他の情報を取得して集計する必要があります。次の図に示すように、各航空会社には独自のサーバーがあるため、エアチャイナ、海南航空、イースタン航空などをリクエストするなど、サーバーを個別にリクエストするだけで十分です。
シリアル
より原始的な方法は、シリアルを使用して解決することです。この問題。
たとえば、価格を取得する場合は、最初にAir China(ここではWebサイト1と呼ばれます)にアクセスしてから、HNAWebサイト2にアクセスする必要があります。各リクエストが送信された後、応答後に次のWebサイトをリクエストできます。これはシリアル方式です。
これは非常に非効率的です。たとえば、より多くの航空会社があります。各航空会社が1秒かかると仮定すると、ユーザーは待つことができないはずなので、この方法はお勧めできません。
平行
次に、今すぐアイデアを改善します。主なアイデアは、次の図に示すように、シリアルをパラレルに変更
することです。チケット情報を並列に取得してから、チケット情報を集約できます。この場合、効率は次のようになります。倍増する。
この種の並列処理は効率を向上させますが、「すべての要求が返されるまで待機する」という欠点もあります。特に遅いウェブサイトがある場合は、そのウェブサイトにドラッグダウンしないでください。たとえば、特定のウェブサイトを開くのに20秒かかります。それほど長く待たないでください。つまり、機能が必要です。タイムアウトアクセス。
タイムアウト付きの並列取得
次のタイムアウト付きの並列取得を見てみましょう。
この場合、タイムアウト付きの並列取得に属し、各Webサイトの情報も並行して要求しています。ただし、タイムアウトを設定しました。たとえば、3秒です。すべてが3秒戻った場合は、もちろんそれらを収集するのが最善です。ただし、時間内に戻らないWebサイトがまだある場合は、これらのリクエストを無視します。ユーザーエクスペリエンスを向上させるために、情報を取得するのに最大で3秒待つだけで済みます。情報は完全ではないかもしれませんが、待つよりはましです。
この目標を達成するにはいくつかの方法があります。それらを1つずつ見ていきましょう。
スレッドプールの実装
最初の解決策はスレッドプールを使用することです。コードを見てみましょう。
public class ThreadPoolDemo {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws InterruptedException {
ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
System.out.println(threadPoolDemo.getPrices());
}
private Set<Integer> getPrices() throws InterruptedException {
Set<Integer> prices = Collections.synchronizedSet(new HashSet<Integer>());
threadPool.submit(new Task(123, prices));
threadPool.submit(new Task(456, prices));
threadPool.submit(new Task(789, prices));
Thread.sleep(3000);
return prices;
}
private class Task implements Runnable {
Integer productId;
Set<Integer> prices;
public Task(Integer productId, Set<Integer> prices) {
this.productId = productId;
this.prices = prices;
}
@Override
public void run() {
int price=0;
try {
Thread.sleep((long) (Math.random() * 4000));
price= (int) (Math.random() * 4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
prices.add(price);
}
}
}
コードでは、スレッドセーフなセットが作成されます。これは、さまざまな価格情報を格納するために使用され、Pricesという名前が付けられ、タスクがスレッドプールに配置されます。スレッドプールはクラスの最初に作成され、3つのスレッドが固定されたスレッドプールです。このタスクについては、以下のTaskクラスで説明します。このタスクでは、runメソッドを確認します。このメソッドでは、ランダムな時間を使用してさまざまな航空Webサイトの応答時間をシミュレートし、ランダムに返します。運賃を示すには、そして最後にこの運賃をセットに入れます。これが私たちのrunメソッドが行うことです。
getPrices関数に戻ると、3つの新しいタスクを作成しました。productIdは123、456、789です。ここでのproductIdは重要ではありません。返される価格はランダムであるため、残業待機の機能を実現するために、ここではこれを呼び出します。 Threadのsleepメソッドは3秒間スリープします。その場合、ここで3秒間待機してから、直接価格に戻ります。
この時点で、以前の応答速度が速い場合、価格には最大3つの値がありますが、各応答時間が非常に遅い場合、価格に値がない可能性があります。いくつ持っていても、スリープが終了した直後、つまりThreadのスリープを実行した後、価格を返し、最後に結果をmain関数に出力します。
可能な実行結果を見てみましょう。1つの可能性として、[3815、3609、3819](数値はランダム)の3つの値があり、1 [3496]または2 [1701、2730]]があります。 、各応答速度が特に遅い場合は、値がない可能性があります。
これは、スレッドプールで実装される最も基本的なソリューションです。
CountDownLatch
最適化の余地があります。たとえば、ネットワークが特に良好な場合、各航空会社は非常に迅速に応答します。3秒待つ必要はありません。一部の航空会社は数百ミリ秒以内に戻る可能性があるため、ユーザーは3秒待つように求められるはずです。したがって、そのような改善を行う必要があります。次のコードを見てください。
public class CountDownLatchDemo {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws InterruptedException {
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
System.out.println(countDownLatchDemo.getPrices());
}
private Set<Integer> getPrices() throws InterruptedException {
Set<Integer> prices = Collections.synchronizedSet(new HashSet<Integer>());
CountDownLatch countDownLatch = new CountDownLatch(3);
threadPool.submit(new Task(123, prices, countDownLatch));
threadPool.submit(new Task(456, prices, countDownLatch));
threadPool.submit(new Task(789, prices, countDownLatch));
countDownLatch.await(3, TimeUnit.SECONDS);
return prices;
}
private class Task implements Runnable {
Integer productId;
Set<Integer> prices;
CountDownLatch countDownLatch;
public Task(Integer productId, Set<Integer> prices,
CountDownLatch countDownLatch) {
this.productId = productId;
this.prices = prices;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
int price = 0;
try {
Thread.sleep((long) (Math.random() * 4000));
price = (int) (Math.random() * 4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
prices.add(price);
countDownLatch.countDown();
}
}
}
このコードは、CountDownLatchを使用してこの関数を実現します。全体的な考え方は以前と同じです。違いは、CountDownLatchを追加して、タスクに渡した点です。タスクでは、チケット情報を取得してセットに追加した後、countDownメソッドが呼び出されます。これは、カウントを1つ減らすことに相当します。
このように、countDownLatch.await(3、
TimeUnit.SECONDS)関数が実行されて待機する場合、3つのタスクが非常に高速に実行されると、3つのスレッドがcountDownメソッドを実行し、awaitメソッドはすぐに戻ります。 3秒待つ必要はありません。
リクエストが特に遅い場合、countDownメソッドを実行しないスレッドと同等であり、3秒以内に実行するには遅すぎる場合、timeoutパラメーターを指定したawaitメソッドも、3秒後にこの待機を放棄します。秒。、だから価格が返されます。したがって、このように、CountDownLatchを使用してこの要件を達成します。つまり、最大3秒待機しますが、すべてが3秒以内に戻る場合は、愚かな待機ではなく、迅速に戻ることもでき、効果が向上します。
CompletableFuture
この機能を実現するためにCompletableFutureを使用する使用法を見てみましょう。コードは次のとおりです。
public class CompletableFutureDemo {
public static void main(String[] args)
throws Exception {
CompletableFutureDemo completableFutureDemo = new CompletableFutureDemo();
System.out.println(completableFutureDemo.getPrices());
}
private Set<Integer> getPrices() {
Set<Integer> prices = Collections.synchronizedSet(new HashSet<Integer>());
CompletableFuture<Void> task1 = CompletableFuture.runAsync(new Task(123, prices));
CompletableFuture<Void> task2 = CompletableFuture.runAsync(new Task(456, prices));
CompletableFuture<Void> task3 = CompletableFuture.runAsync(new Task(789, prices));
CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3);
try {
allTasks.get(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
} catch (ExecutionException e) {
} catch (TimeoutException e) {
}
return prices;
}
private class Task implements Runnable {
Integer productId;
Set<Integer> prices;
public Task(Integer productId, Set<Integer> prices) {
this.productId = productId;
this.prices = prices;
}
@Override
public void run() {
int price = 0;
try {
Thread.sleep((long) (Math.random() * 4000));
price = (int) (Math.random() * 4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
prices.add(price);
}
}
}
ここでは、スレッドプールを使用しなくなりました。getPricesメソッドが表示されます。このメソッドでは、CompletableFutureのrunAsyncメソッドを使用します。このメソッドは、タスクを非同期で実行します。
3つのタスクがあり、このコードを実行すると、それぞれCompletableFutureオブジェクトが返されます。タスク1、タスク2、タスク3という名前を付け、CompletableFutureのallOfメソッドを実行して、タスク1、タスク2、タスク3をに渡します。 。このメソッドの機能は、複数のタスクを集約することです。その後、必要に応じてパラメーターで渡されたこれらのタスクの戻り結果を取得するか、タスクが実行されるのを待つことができます。この戻り値allTasksを呼び出し、以下のタイムアウトでgetメソッドを呼び出し、同時に3秒のタイムアウトパラメーターを渡します。
このように、これら3つのタスクが3秒以内にスムーズに返される場合、つまり、このタスクに含まれる3つのタスクのそれぞれが実行される場合、getメソッドは時間内に正常になります。これは、価格を返すために実行するのと同じです。以下のタスクのrunメソッドでは、メソッドが実行されると、CompletableFutureのタスクの終了を意味し、タスクが実行されたかどうかを判断するためのマークとして使用されます。ただし、3秒以内に戻らないタスクがある場合、タイムアウトパラメーターを指定したこのgetメソッドはTimeoutExceptionをスローし、それもキャッチします。このようにして、すべてのタスクが完了するのを待機しようとしますが、最大3秒しか待機せず、その間に時間内に完了すると時間内に戻ります。そこで、旅行プラットフォームの問題も解決できるCompletableFutureを使用します。その実行結果は以前と同じであり、多くの可能性があります。
最後に、要約を作成します。今回は、最初に旅行プラットフォームの問題を提起しました。各航空会社のチケット情報を取得してから、シリアルからパラレル、タイムアウト付きのパラレル、最後にパラレルのタイムアウトだけでなく、コードの進化を取得する必要があります。誰もが速いので、タイムアウトが切れるまで待つ必要はありません。私たちはそのような段階的な反復を実行しました。
もちろん、これらのいくつかの実装スキームに加えて、他の実装スキームもありますが、どれを思いつくことができますか?以下にコメントを残して、私に知らせてください、ありがとう。