web-dev-qa-db-ger.com

Ist es empfehlenswert, die temporäre JMS-Warteschlange für die synchrone Verwendung zu verwenden?

Wenn wir den JMS-Anforderungs-/Antwortmechanismus mit "Temporary Queue" verwenden, ist dieser Code skalierbar?

Derzeit wissen wir nicht, ob wir 100 Anfragen pro Sekunde oder 1000 Anfragen pro Sekunde unterstützen werden.

Der folgende Code ist, was ich von der Implementierung denke. JMS wird synchron verwendet. Die wichtigsten Teile sind, wo der "Verbraucher" erstellt wird, um auf eine "temporäre Warteschlange" zu verweisen, die für diese Sitzung erstellt wurde. Ich kann einfach nicht herausfinden, ob die Verwendung solcher temporärer Warteschlangen ein skalierbares Design ist.

  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);

Update:

Ich bin auf ein anderes Muster gestoßen, siehe dieses Blog Die Idee ist, "normale" Warteschlangen für Senden und Empfangen zu verwenden. Für "synchrone" Anrufe erstellen Sie jedoch einen Consumer, der die Empfangswarteschlange mithilfe eines "Selektors" abhört, um die gewünschte Antwort zu erhalten (d. H. Der Anforderung zu entsprechen).

Schritte:

    // 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 + "'" );

Der Unterschied in diesem Muster besteht also darin, dass wir nicht für jede neue Anforderung eine neue temporäre Warteschlange erstellen. Stattdessen werden alle Antworten nur in eine Warteschlange eingereiht. Verwenden Sie jedoch einen 'Selektor', um sicherzustellen, dass jeder Anfragethread die einzige Antwort erhält, um die es geht.

Ich denke, der Nachteil hier ist, dass Sie einen "Selektor" verwenden müssen. Ich weiß noch nicht, ob das weniger bevorzugt oder mehr bevorzugt ist als das zuvor erwähnte Muster. Gedanken?

24
rk2010

Was die Aktualisierung in Ihren Post-Selectors anbelangt, sind Selektoren sehr effizient, wenn sie für die Nachrichtenköpfe ausgeführt werden, wie Sie es mit der Korrelations-ID tun. Spring Integration tut dies auch intern für die Implementierung eines JMS Outbound Gateways .

6
Biju Kunjummen

Interessanterweise ist die Skalierbarkeit tatsächlich das Gegenteil von dem, was die anderen Antworten beschrieben haben. 

WebSphere MQ speichert und verwendet dynamische Warteschlangenobjekte, sofern möglich. Obwohl die Verwendung einer dynamischen Warteschlange nicht frei ist, lässt sie sich zwar gut skalieren, da WMQ bei der Freigabe von Warteschlangen lediglich den Handle an den nächsten Thread weitergibt, der eine neue Warteschlangeninstanz anfordert. In einem ausgelasteten QMgr bleibt die Anzahl der dynamischen Warteschlangen relativ statisch, während die Handles von Thread zu Thread übergeben werden. Streng genommen ist es nicht so schnell wie das Wiederverwenden einer einzelnen Warteschlange, aber es ist nicht schlecht.

Auf der anderen Seite, obwohl die Indexierung für CORRELID schnell ist, ist die Performance umgekehrt zu der Anzahl der Nachrichten im Index. Es macht auch einen Unterschied, ob die Tiefe der Warteschlange beginnt. Wenn die App eine GET mit WAIT in einer leeren Warteschlange durchläuft, gibt es keine Verzögerung. In einer tiefen Warteschlange muss der QMgr jedoch den Index der vorhandenen Nachrichten durchsuchen, um festzustellen, dass die Antwortnachricht nicht darunter ist. In Ihrem Beispiel ist das der Unterschied zwischen dem Durchsuchen eines leeren Indexes und eines Tausendfachen eines großen Index pro Sekunde.

Das Ergebnis ist, dass 1000 dynamische Warteschlangen mit jeweils einer Nachricht tatsächlich schneller sein können als eine einzelne Warteschlange mit 1000 Threads, die an CORRELID gelangen, abhängig von den Eigenschaften der App und der Last. Ich würde empfehlen, dies im Maßstab zu testen, bevor Sie sich auf ein bestimmtes Design festlegen.

4
T.Rob

Die Verwendung des Selektors für die Korrelations-ID in einer gemeinsam genutzten Warteschlange lässt sich sehr gut für mehrere Verbraucher skalieren.

1000 Anfragen/s werden jedoch viel sein. Möglicherweise möchten Sie die Last ein wenig auf verschiedene Instanzen verteilen, wenn sich die Leistung als problematisch herausstellt. 

Möglicherweise möchten Sie die Anforderungen und Kundennummern näher erläutern. Wenn die Anzahl der Clients <10 ist und eher statisch bleibt und die Anforderungsnummern sehr hoch sind, besteht die sicherste und schnellste Lösung darin, für jeden Client statische Antwortwarteschlangen zu haben.

2

Das Erstellen temporärer Warteschlangen ist nicht kostenlos. Schließlich werden Ressourcen für die Broker zugewiesen. Wenn Sie jedoch eine unbekannte (vorhergehende) potenziell ungebundene Anzahl von Clients haben (mehrere JVMs, mehrere gleichzeitige Threads pro JVM usw.), haben Sie möglicherweise keine Wahl. Das Zuweisen von Client-Warteschlangen und deren Zuweisung an Clients würde schnell aus dem Ruder laufen.

Das, was Sie entworfen haben, ist sicherlich die einfachste mögliche Lösung. Und wenn Sie reale Zahlen für das Transaktionsvolumen erhalten können, reicht die Skalierung aus.

Bevor ich vorübergehende Warteschlangen vermeiden wollte, habe ich mich eher mit der Begrenzung der Anzahl der Kunden und der Langlebigkeit der Kunden befasst. Das heißt, Sie erstellen einen Client-Pool auf der Clientseite und lassen die Clients im Pool die temporäre Warteschlange, Sitzung, Verbindung usw. beim Start erstellen, bei nachfolgenden Anforderungen wiederverwenden und beim Herunterfahren herunterfahren. Dann werden die Abstimmungsprobleme zu einem Wert von max/min im Pool, der Leerlaufzeit zum Bereinigen des Pools und dem Verhalten (Fail vs. Block), wenn der Pool maximal ist. Wenn Sie nicht eine beliebig große Anzahl von transienten JVMs erstellen (in diesem Fall haben Sie Probleme mit der Skalierung, nur durch den Start der JVM-Systemstartkosten), sollte dies genauso skalierbar sein wie alles andere. Zu diesem Zeitpunkt spiegeln die Ressourcen, die Sie zuweisen, die tatsächliche Nutzung des Systems wider. Es gibt wirklich keine Gelegenheit, weniger als das zu verwenden.

Die zu vermeidende Sache besteht darin, eine große Anzahl von Warteschlangen, Sitzungen, Verbindungen usw. zu erstellen und zu vernichten. Gestalten Sie die Serverseite so, dass Streaming von Anfang an möglich ist. Dann Pool, wenn/wann Sie müssen. Wie nicht für alles, was nicht trivial ist, müssen Sie es tun.

1
asudell

Die Verwendung einer temporären Warteschlange erfordert jedes Mal die Erstellung von trustToProducers. Anstatt einen zwischengespeicherten Producer für eine statische ReplyToQueue zu verwenden, ist die Methode createProducer teurer und wirkt sich auf die Leistung in einer Umgebung mit starkem gleichzeitigen Aufruf aus.

0
davy_wei

Ich war mit demselben Problem konfrontiert und beschloss, die Verbindungen innerhalb einer zustandslosen Bohne zusammenzulegen. Eine Clientverbindung verfügt über eine temporäre Queue und befindet sich innerhalb des JMSMessageExchanger-Objekts (das connectionFactory, Queue und tempQueue enthält), das an eine Bean-Instanz gebunden ist. Ich habe es in JSE/EE-Umgebungen getestet. Aber ich bin nicht wirklich sicher über das Verhalten von Glassfish JMS-Pools. Schließt es tatsächlich JMS-Verbindungen, die "von Hand" nach Beendigung der Bean-Methode erhalten werden? Mache ich etwas schrecklich falsch?

Außerdem habe ich die Transaktion in der Client-Bean (TransactionAttributeType.NOT_SUPPORTED) deaktiviert, um Anforderungsnachrichten sofort an die Anforderungswarteschlange zu senden.

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;
    }

}

Dieselbe Klasse wird in Client (Stateless Bean) und Server (@MessageDriven) verwendet. RequestProducer und ResponseProducer sind Schnittstellen:

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();
}

Ich habe auch einen AMQ-Artikel über die Implementierung von Request-Response über AMQ gelesen: http://activemq.Apache.org/how-should-i-implement-request-response-with-jms.html

0
arbocdi