Javaラウンドロビンスレッドセーフ実装

こんにちは、habrachitatel様。

ある晴れた営業日、当局は、複数のサーバーを使用してシステムの生産性を向上させる機能を追加するタスクを設定しました。 クラスターを拡張し、特殊な手段でバランスを取ることはできませんでした。 したがって、ラウンドロビンアルゴリズムを使用して独自の実装を考案する必要がありました。



アルゴリズム



アプリケーションのアーキテクチャに基づいて、構成で指定されたサーバーのリストを調べて、新しい要求で次のサーバーのアドレスを発行することにより、バランスをとることが決定されました。



この原則は、ラウンドロビンアルゴリズムの根底にあります。



実装



インターネットを検索した後、このアルゴリズムを実装するための多くのオプションを見つけました。これは、次のコードに要約されました。

public synchronized Entity getNext_Iterator(){ if (!it.hasNext()) { it = objectList.iterator(); } return it.next(); }
      
      







ここで、objectListは使用可能なサーバーのリストです。



この実装では、同期メソッドの使用が好きではなかったため、次の実装を提案しました。

  1. 初期化中に、使用可能なサーバーのリストが作成され、このリストの長さが保持されます。 AtomicIntegerデータ型と初期値0でグローバルカウンターが作成されます
  2. サーバーアドレスを要求すると、カウンターが1増加します。結果の値がリストの長さを超える場合、カウンターをゼロにします。 このカウンターの値をリストのインデックスとして使用します。




結果のコードを直接( relgamesのアドバイスに感謝します):



  private final AtomicInteger position = new AtomicInteger(0); private volatile int listSize; ... public synchronized void init(List<Entity> objects) { ... listSize = objectList.size(); ... } } public Entity getNext() { return objectList.get(getNextPosition()); } public final int getNextPosition() { for (;;) { int current = position.get(); int next = current + 1; if(next >= listSize){ next = 0; } if (position.compareAndSet(current, next)) return current; } }
      
      







この実装のパフォーマンスをテストするために、負荷のかかった小さなプログラムを作成しました。

テスト方法論は、異なるスレッド数(1〜991)でサーバーアドレスを取得するために1,000,000件の要求が行われ、アドレスを取得する平均時間が測定されたという事実に基づいていました。



結果はグラフで表示され、 ここで表示できます

ここで実験のソース。

グラフのベースとなるデータはこちらです。



ご清聴ありがとうございました。



PS提案された実装オプションは、シングルスレッドシステムで作業するときと、グラフではっきりと見えるマルチスレッドシステムで作業するときの両方で、同様に安定して動作します。 これは、この実装の主な利点です。



UPD:

古いスケジュールはこちらです。 その後、低速のコンピューターで研究が行われました。



All Articles