「一時キュー」を使用したJMS要求/応答メカニズムを使用する場合、そのコードはスケーラブルですか?
現時点では、毎秒100リクエストをサポートするのか、毎秒1000リクエストをサポートするのかはわかりません。
以下のコードは私が実装を考えているものです。 JMSを「同期」的に使用します。重要な部分は、このセッション用に作成された「一時キュー」を指す「コンシューマー」が作成される場所です。そのような一時キューの使用がスケーラブルな設計であるかどうか、私にはわかりません。
destination = session.createQueue("queue:///Q1");
producer = session.createProducer(destination);
tempDestination = session.createTemporaryQueue();
consumer = session.createConsumer(tempDestination);
long uniqueNumber = System.currentTimeMillis() % 1000;
TextMessage message = session
.createTextMessage("SimpleRequestor: Your lucky number today is " + uniqueNumber);
// Set the JMSReplyTo
message.setJMSReplyTo(tempDestination);
// Start the connection
connection.start();
// And, send the request
producer.send(message);
System.out.println("Sent message:\n" + message);
// Now, receive the reply
Message receivedMessage = consumer.receive(15000); // in ms or 15 seconds
System.out.println("\nReceived message:\n" + receivedMessage);
更新:
別のパターンに遭遇しました。 このブログを参照してください アイデアは、送信と受信の両方に「通常の」キューを使用することです。ただし、「同期」呼び出しの場合は、目的の応答を取得する(つまり、要求を一致させる)ために、「セレクタ」を使用して受信キューをリッスンするコンシューマを作成します。
手順:
// 1. Create Send and Receive Queue.
// 2. Create a msg with a specific ID
final String correlationId = UUID.randomUUID().toString();
final TextMessage textMessage = session.createTextMessage( msg );
textMessage.setJMSCorrelationID( correlationId );
// 3. Start a consumer that receives using a 'Selector'.
consumer = session.createConsumer( replyQueue, "JMSCorrelationID = '" + correlationId + "'" );
したがって、このパターンの違いは、新しいリクエストごとに新しい一時キューを作成しないことです。代わりに、すべての応答は1つのキューだけに送られますが、「セレクター」を使用して、各要求スレッドが関心のある応答のみを受信するようにします。
ここでの欠点は、「セレクター」を使用する必要があることです。先ほど述べたパターンよりも優先度が低いか、優先度が高いかはまだわかりません。考え?
ポストの更新に関して-相関IDで行っているように、メッセージヘッダーでセレクターを実行すると非常に効率的です。 Spring Integration 内部的にもこれを行います JMSアウトバウンドゲートウェイの実装 。
興味深いことに、これのスケーラビリティは、実際には他の応答で説明されているものとは逆の場合があります。
WebSphere MQは、可能な場合は動的キューオブジェクトを保存して再利用します。したがって、動的キューの使用はフリーではありませんが、キューが解放されるため、WMQが実行する必要があるのは、新しいキューインスタンスを要求する次のスレッドにハンドルを渡すだけなので、適切に拡張されます。ビジーなQMgrでは、ハンドルがスレッドからスレッドに渡される間、動的キューの数は比較的静的なままです。厳密に言うと、単一のキューを再利用するほど高速ではありませんが、悪くはありません。
一方、CORRELID
のインデックス作成は高速ですが、パフォーマンスはインデックス内のメッセージ数に反比例します。また、キューの深さが構築され始めると、違いが生じます。アプリが空のキューでGET
とWAIT
を使用する場合、遅延はありません。しかし、深いキューでは、QMgrは既存のメッセージのインデックスを検索して、応答メッセージがそれらの中にないことを確認する必要があります。あなたの例では、それは空のインデックスを検索することと大きなインデックスを毎秒数千回検索することの違いです。
その結果、アプリと負荷の特性によっては、メッセージが1つずつある1000個の動的キューは、CORRELID
を取得する1000個のスレッドを持つ単一のキューよりも実際に高速になる場合があります。特定の設計に取り組む前に、これを大規模にテストすることをお勧めします。
共有キューの相関IDでセレクターを使用すると、複数のコンシューマーで非常に適切にスケーリングされます。
ただし、1000リクエスト/秒は大量になります。パフォーマンスに問題があることが判明した場合は、負荷を異なるインスタンス間で少し分割することをお勧めします。
リクエスト数とクライアント数を詳しく説明する必要がある場合があります。クライアントの数が10未満で比較的静的なままで、リクエスト数が非常に多い場合、最も回復力があり高速なソリューションは、各クライアントに静的な応答キューを用意することです。
一時的なキューの作成は無料ではありません。結局のところ、それはブローカーにリソースを割り当てています。そうは言っても、(事前に)潜在的にバインドされていない可能性のあるクライアント数が複数ある場合(複数のJVM、JVMごとの複数の同時スレッドなど)、選択肢がない可能性があります。クライアントキューごとに割り当てて、クライアントキューに割り当てると、すぐに手に負えなくなります。
確かに、あなたがスケッチしたのは、可能な最も簡単な解決策です。また、トランザクション量の実数を取得でき、十分にスケーリングできる場合は問題ありません。
一時的なキューの回避について説明する前に、クライアントの数を制限し、クライアントの寿命を延ばす方法について詳しく説明します。つまり、クライアント側でクライアントプールを作成し、プール内のクライアントに、起動時に一時キュー、セッション、接続などを作成させ、後続のリクエストでそれらを再利用し、シャットダウン時にそれらを破棄します。次に、チューニングの問題は、プールの最大/最小サイズ、プールをプルーニングするためのアイドル時間、プールが最大になったときの動作(失敗とブロック)のいずれかになります。任意の数の一時的なJVMを作成しているのでない限り(この場合は、JVMの起動オーバーヘッドだけでスケーリングの問題が大きくなります)、それだけでなく何でもスケーリングする必要があります。結局のところ、その時点で、割り当てているリソースはシステムの実際の使用状況を反映しています。それより少ないものを使用する機会は本当にありません。
避けるべきことは、大量の不必要な数のキュー、セッション、接続などを作成して破棄することです。最初からストリーミングできるようにサーバー側を設計します。その後、必要に応じてプールします。そうではないように、重要なことは何でも必要です。
一時キューを使用すると、毎回relyToProducerを作成するコストがかかります。静的なreplyToQueueにキャッシュされたプロデューサーを使用する代わりに、メソッドcreateProducerはコストが高くなり、高度に同時実行される呼び出し環境でのパフォーマンスに影響します。
私は同じ問題に直面しており、接続を自分でステートレスBeanにプールすることにしました。 1つのクライアント接続には1つのtempQueueがあり、1つのBeanインスタンスにバインドされているJMSMessageExchangerオブジェクト(connectionFactory、Queue、tempQueueを含む)内に配置されます。 JSE/EE環境でテストしました。しかし、Glassfish JMSプールの動作についてはよくわかりません。 Beanメソッドの終了後に「手動で」取得されたJMS接続を実際に閉じますか?何かひどく悪いことをしていますか?
また、クライアントBeanのトランザクション(TransactionAttributeType.NOT_SUPPORTED)をオフにして、要求メッセージを要求キューにすぐに送信しました。
package net.sf.selibs.utils.amq;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import lombok.Getter;
import lombok.Setter;
import net.sf.selibs.utils.misc.UHelper;
public class JMSMessageExchanger {
@Setter
@Getter
protected long timeout = 60 * 1000;
public JMSMessageExchanger(ConnectionFactory cf) {
this.cf = cf;
}
public JMSMessageExchanger(ConnectionFactory cf, Queue queue) {
this.cf = cf;
this.queue = queue;
}
//work
protected ConnectionFactory cf;
protected Queue queue;
protected TemporaryQueue tempQueue;
protected Connection connection;
protected Session session;
protected MessageProducer producer;
protected MessageConsumer consumer;
//status
protected boolean started = false;
protected int mid = 0;
public Message makeRequest(RequestProducer producer) throws Exception {
try {
if (!this.started) {
this.init();
this.tempQueue = this.session.createTemporaryQueue();
this.consumer = this.session.createConsumer(tempQueue);
}
//send request
Message requestM = producer.produce(this.session);
mid++;
requestM.setJMSCorrelationID(String.valueOf(mid));
requestM.setJMSReplyTo(this.tempQueue);
this.producer.send(this.queue, requestM);
//get response
while (true) {
Message responseM = this.consumer.receive(this.timeout);
if (responseM == null) {
return null;
}
int midResp = Integer.parseInt(responseM.getJMSCorrelationID());
if (mid == midResp) {
return responseM;
} else {
//just get other message
}
}
} catch (Exception ex) {
this.close();
throw ex;
}
}
public void makeResponse(ResponseProducer producer) throws Exception {
try {
if (!this.started) {
this.init();
}
Message response = producer.produce(this.session);
response.setJMSCorrelationID(producer.getRequest().getJMSCorrelationID());
this.producer.send(producer.getRequest().getJMSReplyTo(), response);
} catch (Exception ex) {
this.close();
throw ex;
}
}
protected void init() throws Exception {
this.connection = cf.createConnection();
this.session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
this.producer = this.session.createProducer(null);
this.producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
this.connection.start();
this.started = true;
}
public void close() {
UHelper.close(producer);
UHelper.close(consumer);
UHelper.close(session);
UHelper.close(connection);
this.started = false;
}
}
同じクラスがクライアント(ステートレスBean)とサーバー(@MessageDriven)で使用されています。 RequestProducerとResponseProducerはインターフェースです:
package net.sf.selibs.utils.amq;
import javax.jms.Message;
import javax.jms.Session;
public interface RequestProducer {
Message produce(Session session) throws Exception;
}
package net.sf.selibs.utils.amq;
import javax.jms.Message;
public interface ResponseProducer extends RequestProducer{
void setRequest(Message request);
Message getRequest();
}
また、AMQを介した要求と応答の実装に関するAMQの記事を読みました。 http://activemq.Apache.org/how-should-i-implement-request-response-with-jms.html