最高のスループットで多数の要素と一致する Java 同時実行イディオムを探しています。
複数のスレッドから「人」が入ってくると考えてください。各「人」は一致を探しています。別の待機中の「人」が見つかると、それらは互いに割り当てられ、処理のために削除されます。
状態を変更するために大きな構造をロックしたくありません。Person に getMatch と setMatch があるとします。送信される前は、各人の #getMatch は null です。しかし、それらがブロック解除された (または釣り上げられた) 場合、一致を待ち望んでいたために有効期限が切れているか、#getMatch が null ではないかのいずれかです。
高いスループットを維持する際の問題は、PersonA が PersonB と同時に送信された場合です。それらは互いに一致しますが、PersonB も既に待機している PersonC と一致します。送信されると、PersonB の状態が「利用可能」に変わります。ただし、PersonB が PersonC と照合されている間に、PersonA が誤って PersonB を取得してはなりません。わかる?また、非同期で動作する方法でこれを行いたいと考えています。言い換えれば、各サブミッターが、waitForMatch タイプのスレッドで Person を保持する必要はありません。
繰り返しますが、リクエストを別のスレッドで実行する必要はありませんが、マッチ メーカー スレッドが 1 つ追加されても問題ありません。
これはかなり一般的なことのように見えるので、これにはいくつかのイディオムがあるはずです。しかし、私のグーグル検索はうまくいきませんでした(間違った用語を使用している可能性があります)。
アップデート
この問題を私にとって難しくしていることがいくつかあります。1 つは、オブジェクトをメモリに保持したくないということです。すべての待機中の候補を redis や memcache などに保持したいと考えています。もう 1 つは、どの人にも複数の一致候補がある可能性があるということです。次のようなインターフェースを検討してください。
person.getId(); // lets call this an Integer
person.getFriendIds(); // a collection of other person ids
次に、次のようなサーバーがあります。
MatchServer:
submit( personId, expiration ) -> void // non-blocking returns immediately
isDone( personId ) -> boolean // either expired or found a match
getMatch( personId ) -> matchId // also non-blocking
これは残りのインターフェイス用であり、結果が得られるまでリダイレクトを使用します。私が最初に考えたのは、Redis のようなものに支えられ、現在ロックされていて操作されているオブジェクトに対して同時に弱い値のハッシュ マップを持つ MatchServer に Cache を用意することでした。各 personId は、送信済み、一致、期限切れなどの状態を持つ永続的な状態オブジェクトによってラップされます。
ここまでフォロー?非常に単純です。送信コードが最初の作業を行いました。それは次のようなものでした。
public void submit( Person p, long expiration ) {
MatchStatus incoming = new MatchStatus( p.getId(), expiration );
if ( !tryMatch( incoming, p.getFriendIds() ) )
cache.put( p.getId(), incoming );
}
public boolean isDone( Integer personId ) {
MatchStatus status = cache.get( personId );
status.lock();
try {
return status.isMatched() || status.isExpired();
} finally {
status.unlock();
}
}
public boolean tryMatch( MatchStatus incoming, Iterable<Integer> friends ) {
for ( Integer friend : friends ) {
if ( match( incoming, friend ) )
return true;
}
return false;
}
private boolean match( MatchStatus incoming, Integer waitingId ) {
CallStatus waiting = cache.get( waitingId );
if ( waiting == null )
return false;
waiting.lock();
try {
if ( waiting.isMatched() )
return false;
waiting.setMatch( incoming.getId() );
incoming.setMatch( waiting.getId() );
return true
} finally {
waiting.unlock();
}
}
ここでの問題は、2 人が同時に入ってきて、2 人しか一致しない場合、お互いを見つけられないことです。競合状態ですよね?それを解決する唯一の方法は、「tryMatch()」を同期することでした。しかし、それは私のスループットを殺します。これらは非常に短い呼び出しにする必要があるため、tryMatch を無期限にループさせることはできません。
では、これにアプローチするより良い方法は何ですか? 私が思いつくすべてのソリューションは、一度に 1 人ずつ人を強制するものであり、スループットにはあまり適していません。たとえば、バックグラウンド スレッドを作成し、ブロッキング キューを使用して着信を 1 つずつ入れたり受け取ったりします。
ガイダンスをいただければ幸いです。