3

RESTful スタイルの RPC (リモート プロシージャ コール) API を tomcat サーバーで実行しており、N ユーザーのデータを K スレッドで M タスクで処理しています。ほとんどの場合、1 人のユーザーが約 20 から 500 のタスクを持っています (ただし、M は 1 から 5000 の間である可能性があります)。1 つのタスクが完了するまでに約 10 秒から 20 秒かかりますが、1 秒から 20 分かかる場合もあります。現在、ほとんどのシステムには 1 人のユーザーがいて、時には 3 人までいますが、近い将来には同時に約 10 人のユーザーに増加します。サーバーには 10 個のコアがあるため、10 個のスレッドを使用したいと考えています。現時点では、すべてのユーザーが処理用に 5 つのスレッドを取得していますが、これは正常に機能しています。しかし、a) ほとんどの場合、マシンは 50% しか使用されず (針が「30 分」の範囲で待機することになります)、サーバーの負荷が最大 150% になることもあります。

ソリューションの要件:

  1. 常にサーバーが 100% 使用されている (タスクがある場合)
  2. スレッドの実行に関して、すべてのユーザーが同じように扱われる (他のすべてのユーザーと同じ量のスレッドが終了する)
  3. 新しいユーザーは、以前のユーザーのすべてのタスクが完了するまで待つ必要はありません (特に、user1 に 5000 のタスクがあり、user2 に 1 つのタスクがある場合、これは重要です)。

頭に浮かぶ解決策:

  1. 10 個のスレッドで FixedThreadPoolExecutor を使用するだけで、条件 3 に違反します

  2. PriorityBlockingQueue を使用し、タスクに compareTo メソッドを実装します -> threadpoolExecutors submit メソッドを使用できません (したがって、送信されたタスクがいつ終了するかわかりません)

  3. ブロッキング キューのような「ラウンド ロビン」を実装します。この場合、K 個のスレッド (この場合は 10 個) が N 個の内部キューからラウンド ロビン方式で新しいタスクを取得します -> タスクを適切なキューに配置できるようにするには、複数のパラメーターを取る「送信」メソッド (ThreadPoolExecutor も実装する必要があります)

ブロッキング キューのようなラウンド ロビンの意味を説明しようとしました (役に立たない場合は、自由に編集してください)。

  --                       --
  --        --        --   --             queue task load, 
  --   --   --   --   --   --        --   one task denoted by --
  --   --   --   --   --   --   --   -- 
| Q1 | Q2 | Q3 | Q4 | Q5 | Q6 | Q7 | QN |
|                      *   ^            |
|                  last|   |next        |
|                           -------------
\                          /
 \    |    |    |    |    |
 | T1 | T2 | T3 | T4 | TK |

この種の処理動作を実現するために、主に Java 標準 API (または他の広く普及している Java API) を使用するエレガントなソリューションはありますか? または、この問題に取り組む方法について他に何かヒントはありますか?

4

3 に答える 3

0

要件への対応:

1) スレッド使用量の最大化: ThreadPoolExecutor がこれを処理します。
2) すべてのユーザーが同じように扱われます。基本的にラウンドロビン設定が必要です。
3) 新しいユーザーが FIFO 順で待機するのを避ける: #2 と同じ。

また、送信して結果を取得する機能についても言及しました。

PriorityBlockingQueue<Job>ラッパー オブジェクトを使用してスタンドアロンを検討することもできます。たとえば、次のようになります。

class Job implements Comparable<Job> {
    private int priority;
    private YourCallable task;

    public Job(int priority, YourCallable task) {
        this.priority = priority;
        this.task = task;
    }

    @Override
    public int compareTo(Job job) {
        // use whatever order you prefer, based on the priority int
    }
}

プロデューサは、(ラウンドロビン ルールなどに基づいて) 優先度が割り当てられた PriorityBlockingQueue に Job を提供し、Callable を実装するタスクを提供します。次に、コンシューマーは Job に対して queue.poll を実行します。

それを手に入れたら、その Job オブジェクト内に含まれるタスクを取得し、選択した ThreadPoolExecutor で処理するために送信できます。

于 2014-12-01T18:49:09.950 に答える
0

タスク全体のレイテンシを最小化することが要件 2 と 3 の適切な代替手段であることに同意し、十分なタスク実行時間の見積もりがある場合は、答えがあるかもしれません。

各タスクでタスクの送信時間を保存して、後でいつでも推定レイテンシを計算できるようにします。次に、新しいタスクを挿入するときに、ある程度の公平性を提供し、全体的なレイテンシを最小限に抑えようとするキューの位置に常にタスクを挿入する PriorityBlockingQueue を構築できます。これにより、最初は長時間実行されるタスクが不利になります。私は自分で試したことはありませんが、推定実行時間に基づいてタスクの優先度を割り当て、estimatedRuntime-waitingTime を優先度として使用します (最も優先度の低いジョブを最初に実行します)。これにより、負荷の高いタスクが負の優先度になるのを待った後、チャンスが与えられます。それまでは、たとえ提出されたばかりであっても、軽いタスクが 1 位になる可能性が高くなります。ただし、このスケジューリングは、見積もりが許す範囲でのみ公平になります。

ラウンド ロビンの要件については、それが本当に重要な場合は、キューでも処理できます。基本的に、スレッド プールを使用すると、新しいジョブをキューのどこに挿入するかという観点から、独自のスケジューリング戦略を実装できます。ジョブの待ち時間を見積もることができれば、適切な位置に挿入することで、ユーザー間でバランスを取ることもできます。

于 2014-12-01T18:14:19.667 に答える