views:

328

answers:

1

I'm starting a JEE project that needs to be strongly scalable. So far, the concept was:

  • several Message Driven Beans, responsible for different parts of the architecture
  • each MDB has a Session Bean injected, handling the business logic
  • a couple of Entity Beans, providing access to the persistence layer
  • communication between the different parts of the architecture via Request/Reply concept via JMS messages:
    • MDB receives msg containing activity request
    • uses its session bean to execute necessary business logic
    • returns response object in msg to original requester

The idea was that by de-coupling parts of the architecture from each other via the message bus, there is no limit to the scalability. Simply start more components - as long as they are connected to the same bus, we can grow and grow.

Unfortunately, we're having massive problems with the request-reply concept. Transaction Mgmt seems to be in our way plenty. It seams that session beans are not supposed to consume messages?!

Reading http://blogs.sun.com/fkieviet/entry/request_reply_from_an_ejb and http://forums.sun.com/message.jspa?messageID=10338789, I get the feeling that people actually recommend against the request/reply concept for EJBs.

If that is the case, how do you communicate between your EJBs? (Remember, scalability is what I'm after)

Details of my current setup:

  • MDB 1 'TestController', uses (local) SLSB 1 'TestService' for business logic
  • TestController.onMessage() makes TestService send a message to queue XYZ and requests a reply
    • TestService uses Bean Managed Transactions
    • TestService establishes a connection & session to the JMS broker via a joint connection factory upon initialization (@PostConstruct)
    • TestService commits the transaction after sending, then begins another transaction and waits 10 sec for the response
  • Message gets to MDB 2 'LocationController', which uses (local) SLSB 2 'LocationService' for business logic
  • LocationController.onMessage() makes LocationService send a message back to the requested JMSReplyTo queue
    • Same BMT concept, same @PostConstruct concept
  • all use the same connection factory to access the broker

Problem: The first message gets send (by SLSB 1) and received (by MDB 2) ok. The sending of the returning message (by SLSB 2) is fine as well. However, SLSB 1 never receives anything - it just times out.

I tried without the messageSelector, no change, still no receiving message.

Is it not ok to consume message by a session bean?

SLSB 1 - TestService.java

@Resource(name = "jms/mvs.MVSControllerFactory")
private javax.jms.ConnectionFactory connectionFactory;

@PostConstruct
public void initialize() {
    try {
      jmsConnection = connectionFactory.createConnection();
      session = jmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      System.out.println("Connection to JMS Provider established");
    } catch (Exception e) { }
}

public Serializable sendMessageWithResponse(Destination reqDest, Destination respDest, Serializable request) {
    Serializable response = null;

    try {
        utx.begin();
        Random rand = new Random();
        String correlationId = rand.nextLong() + "-" + (new Date()).getTime();

        // prepare the sending message object
        ObjectMessage reqMsg = session.createObjectMessage();
        reqMsg.setObject(request);
        reqMsg.setJMSReplyTo(respDest);
        reqMsg.setJMSCorrelationID(correlationId);

        // prepare the publishers and subscribers
        MessageProducer producer = session.createProducer(reqDest);

        // send the message
        producer.send(reqMsg);
        System.out.println("Request Message has been sent!");
        utx.commit();

        // need to start second transaction, otherwise the first msg never gets sent
        utx.begin();
        MessageConsumer consumer = session.createConsumer(respDest, "JMSCorrelationID = '" + correlationId + "'");
        jmsConnection.start();
        ObjectMessage respMsg = (ObjectMessage) consumer.receive(10000L);
        utx.commit();

        if (respMsg != null) {
            response = respMsg.getObject();
            System.out.println("Response Message has been received!");
        } else {
            // timeout waiting for response
            System.out.println("Timeout waiting for response!");
        }

    } catch (Exception e) { }

    return response;
}

SLSB 2 - LocationService.Java (only the reply method, rest is same as above)

public boolean reply(Message origMsg, Serializable o) {
    boolean rc = false;

    try {
        // check if we have necessary correlationID and replyTo destination
        if (!origMsg.getJMSCorrelationID().equals("") && (origMsg.getJMSReplyTo() != null)) {
            // prepare the payload
            utx.begin();
            ObjectMessage msg = session.createObjectMessage();
            msg.setObject(o);

            // make it a response
            msg.setJMSCorrelationID(origMsg.getJMSCorrelationID());
            Destination dest = origMsg.getJMSReplyTo();

            // send it
            MessageProducer producer = session.createProducer(dest);
            producer.send(msg);
            producer.close();
            System.out.println("Reply Message has been sent");
            utx.commit();

            rc = true;
        }

    } catch (Exception e) {}

    return rc;
}

sun-resources.xml

<admin-object-resource enabled="true" jndi-name="jms/mvs.LocationControllerRequest"  res-type="javax.jms.Queue"  res-adapter="jmsra">
    <property name="Name" value="mvs.LocationControllerRequestQueue"/>
</admin-object-resource>
<admin-object-resource enabled="true" jndi-name="jms/mvs.LocationControllerResponse"  res-type="javax.jms.Queue"  res-adapter="jmsra">
    <property name="Name" value="mvs.LocationControllerResponseQueue"/>
</admin-object-resource>

<connector-connection-pool name="jms/mvs.MVSControllerFactoryPool"  connection-definition-name="javax.jms.QueueConnectionFactory"  resource-adapter-name="jmsra"/>
<connector-resource enabled="true" jndi-name="jms/mvs.MVSControllerFactory" pool-name="jms/mvs.MVSControllerFactoryPool"  />
A: 

The request/reply pattern, even if using JMS, is still synchronous in essence. The caller sends a message, and then waits for the reply. This is not only complicated because of the distributed transactions, but also means that while waiting for the reply, one or several resources (the thread at least in this case) are allocated and wasted. You can not scale this way: you are inherently limited by the number of threads.

To have a truly scalable JMS architecture everything must be asynchronous. In other term: you should never wait. The message sent and receive should pass the necessary information to trigger the next activity.

If the size of the message would be too big, you can store only an identifier and store the corresponding data in a database. But then the database becomes again a point of contention.

If different messages need to know in which long-running process they participate, you can also use correlation identifiers. When a message is received, the receive can "resume" the long-running activity using the correlation identifier. That's a traditional pattern with BPEL. The main difference between synchronous request/reply and asynchronous message with correlation identifier is that the resources can be freed between each step. You can scale with the later, but not with the first.

To be honest, I was confused with your long post and didn't understood if your design was rather asynchronous (and correct), or synchronous with request/reply (and problematic). But I hope I provided some element of answer.

In any case, go visit the website Enterprise Integration Patterns, it's a valuable source of information.

ewernli
Thanks, understood. And in that case, I would have a MDB receive the response message (and determine what the request was and continue from there), right? Not a session bean that is blocked until it receives the response...
Hank
That's indeed the idea.
ewernli