Friday, 16 October 2009

Understanding Connectors & Acceptors

Connectors and accepors are concepts which often confuses new HornetQ users. Both connectors and acceptors are defined in HornetQ server configuration (hornetq-configuration.xml) but users are often confused about when and why they need to configure them. They are described in the user manual but I have a few drawings which could help the users understand them better.

An acceptor defines which type of connection are accepted by the HornetQ server.

A connector defines how to connect to a HornetQ server. The connector is used by a HornetQ client.

HornetQ defines 2 types of acceptor/connector

  • invm – this type can be used when both HornetQ client and server run in the same Virtual Machine (invm for Intra Virtual Machine)
  • netty – this type must be used when HornetQ client and server runs in different Virtual Machines (this connector type uses the netty project to handle the IO)

To communicate, a HornetQ client must use a connector compatible with the server's acceptor.

You can connect from a netty connector to a netty acceptor (if they are configured with the same host and port):

You can not connect from a invm connector to a netty acceptor:

You can not connect from a netty connector to a invm acceptor:

You can not connect from a netty connector on port 5445 to a netty acceptor on port 5446:

By default netty acceptors and connectors uses localhost as the server address. If the HornetQ client is not on the same machine than the server, it will not be able to connect to it.

One source of confusion is that HornetQ connectors are configured on the server. But I wrote that connectors are used by HornetQ clients, not servers! Why should I configure connectors on the server?

There are two reasons to configure connectors in the server:

  1. you want to use JMS & JNDI
  2. you want to communicate between HornetQ servers

Using JMS and JNDI

The standard way to use JMS is to lookup JMS resources (ConnectionFactory and Destination) from JNDI.

Context ctx = new InitialContext();
ConnectionFactory cf = ctx.lookup("/ConnectionFactory")
Connection = cf.createConnection();
// the client is now connected to the JMS server

The ConnectionFactory defines how the JMS client can connect to the JMS server. With HornetQ, this means that the ConnectionFactory implementation will use a connector to connect to the HornetQ Server.

First of all, we must define a "netty" acceptor (in hornetq-configuration.xmlk) so that clients can connect remotely to the server:

<acceptor name="netty">
   <factory-class>org.hornetq.integration.transports.netty.NettyAcceptorFactory</factory-class>
  <!-- by default will accept connection on localhost on port 5445 -->
</acceptor>

Then, we define a "netty" connector (in hornetq-configuration.xmlk)so that JMS clients will know how to connect to the server:

<connector name="netty">
   <factory-class>org.hornetq.integration.transports.netty.NettyConnectorFactory<
   <!-- by default will connect to localhost on port 5445 -->
</connector>

Final step is to configure the JMS ConnectionFactory (in hornetq-jms.xml) so that when it is looked up from JNDI, it uses the "netty" connector to connect to the server:

<connection-factory name="ConnectionFactory">
   <connector-ref connector-name="netty"/>
   <entries>
      <entry name="/ConnectionFactory"/>
   </entries>
</connection-factory>

When the HornetQ server is started, it looks like this:

In JNDI, the HornetQ server has stored the configuration associated to the netty connector with the "/ConnectionFactory" binding.

When the JMS client will look up "/ConnectionFactory", it will also retrieve the netty connector configuration and use it to create a netty connector to connect to the server:

To sum up: if you use JMS with JNDI, you MUST configure a connector to connect to the server itself.

Communication between HornetQ server

The other case when you need to define connectors is when HornetQ servers must communicate. For example, they use core bridges, JMS bridges, diverts, they are in the same cluster...

The important thing to remember is that when two HornetQ servers communicate, one server acts as the client of the other server. In that case, the server acting as the client of the other server MUST define a connector to connect to the other server.

Let's take the example of a JMS bridge: Server #1 will host a core bridge which takes messages from the "source" queue on Server #0 and forwards them to the "target" queue:

Server #0 configuration

Server #0 is a regular HornetQ server, its setup will looks like the "JMS & JNDI" case:

  • a "netty" acceptor to accept connections from remote clients (one of its clients will be the bridge on Server #1)
  • a "netty" connector so that clients can connect to it remotely and send messages to the source queue.

Server #1 configuration

Server #1 is a bit more complex. It acts as a HornetQ server with regards to clients consuming from the target queue but it its bridge is acts as a client of Server #0. Its setup requires:

  • a "netty" acceptor to accept connections from remote clients.
  • a "netty" connector so that clients can connect to it remotely and receives message from the target queue (as explained in the JMS & JNDI case)
  • a "source" connector so that the bridge can connect to the Server #0.

Server #1 defines two connectors, "netty" and "source", which serve different purposes: "netty" is used to connect to the server itself (and will be used by its JMS clients) while "source" connector is used to connect to the other server #0 so that the bridge can receive messages from the source queue.

A note on addresses

Both "netty" connectors and acceptors can be configured with a host parameter. However the meaning of this "host" value is not the same for both:

  • a connector will connect to a single server. Its host parameter must correspond to one of the server address (e.g. localhost or macbook.local or 192.168.0.10)
  • an acceptor can accept connections from one or many addresses. You can specify a single address (localhost or 192.168.0.10), a list of comma-separated addresses (e.g. 192.168.0.10, 10.211.55.2, 127.0.0.01), or 0.0.0.0 to bind to all the host network interfaces.

Conclusion

Connector configuration can be confusing at first glance but it becomes much more clearer when you follow these simple rules:

  • If you use JMS with JNDI, you MUST configure a connector to connect to the server itself
  • If a HornetQ server must communicate with another server, you MUST define a connector to connect to the other server

Tuesday, 13 October 2009

Welcome to two more committers

Welcome to two new committers who have earned their commit rights: Kenny MacLeod (skaffman) and Sergej Zizemski (netwater) Welcome!

HornetQ at the London cloud meetup tomorrow

I'm going to be popping down to the London Cloud Meetup tomorrow (Wednesday 14th Oct) and meeting up with Manik Surtani (Infinspan/JBoss Cache) and Mark Proctor (Drools).

Tuesday, 29 September 2009

Welcome to a new committer

We have a new committer on board! :) Diego Naya joins us from Plug Tree Labs where he is the CEO. Diego is based in Argentina. Welcome to Diego!

Friday, 25 September 2009

Friday, 11 September 2009

Back from JBossWorld and on with HornetQ

Finally I seem to be recovering from an extended period of jet lag after returning from JBossWorld/Red Hat summit in Chicago. The conference was a great success - it was the first time JBossWorld joined forces with the Red Hat guys to create a joint conference. The HornetQ presentation was received very well. We had a good attendance and lots of questions at the end. It's just a shame the presentations weren't video'd - otherwise we could have put it in youtube. Next year I think I will bring along my own video camera and tripod and do it myself! So, what's next? Next stop is HornetQ 2.0 GA. I'm guessing we have around 6 to 8 weeks work to get this out. But of course this depends on how many distractions we are getting. And right now there are a lot ;) Onwards to GA...

Tuesday, 1 September 2009

A HornetQ Simple Example using Maven

Since HornetQ can be run embedded and can be run with no little or no thirdparty dependencies, its easy to create a simple project that runs an embedded server or simple client. The example code can be downloaded from here and can be built and run as follows: To build both the embedded server and client run mvn package from the root directory. To run the server run mvn exec:java from the embedded-server directory. To run the client run mvn exec:java from the client directory. Now lets look more closely at both examples in more detail. embedded server Lets start by looking at the dependencies in the pom.xml found under the embedded-server directory. The dependencies needed are configured as follows:
<dependencies>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-core</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-jms</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-logging</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-transports</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.jboss.netty</groupId>
     <artifactId>netty</artifactId>
     <version>3.1.0.GA</version>
  </dependency>
  <dependency>
     <groupId>org.jboss.javaee</groupId>
     <artifactId>jboss-jms-api</artifactId>
     <version>1.1.0.GA</version>
     <scope>compile</scope>
  </dependency>
These dependencies are what you would need to run a HornetQ JMS server now lets look at the code needed to run an embedded server:
public class EmbeddedServer
{
public static void main(String[] args) throws Exception
{
  try
  {
     FileConfiguration configuration = new FileConfiguration();
     configuration.setConfigurationUrl("hornetq-configuration.xml");
     configuration.start();

     HornetQServer server = HornetQServers.newHornetQServer(configuration);
     JMSServerManager jmsServerManager = new JMSServerManagerImpl(server, "hornetq-jms.xml");
     //if you want to use JNDI, simple inject a context here or don't call this method and make sure the JNDI parameters are set.
     jmsServerManager.setContext(null);
     jmsServerManager.start();
     System.out.println("STARTED::");
  }
  catch (Throwable e)
  {
     System.out.println("FAILED::");
     e.printStackTrace();
  }
}
}
You can configure the server via the hornetq-configuration.xml file and configure any JMS objects via the hornetq-jms.xml file. Of course you can do much more than this with your server but this is a good starting point. Consult the HornetQ documentation for more information of what other features are available. client The client example shows how you can easily create a HornetQ JMS client using minimal jars. The dependencies this time are much smaller than the server and contain client jars that HornetQ provides for lightweight client apps.
   <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-core-client</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-jms-client</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.hornetq</groupId>
     <artifactId>hornetq-transports</artifactId>
     <version>2.0.0.GA</version>
     <scope>compile</scope>
  </dependency>
  <dependency>
     <groupId>org.jboss.netty</groupId>
     <artifactId>netty</artifactId>
     <version>3.1.0.GA</version>
  </dependency>
  <dependency>
     <groupId>org.jboss.javaee</groupId>
     <artifactId>jboss-jms-api</artifactId>
     <version>1.1.0.GA</version>
     <scope>compile</scope>
  </dependency>
The client code in this case is copied from one of the HornetQ examples, running this client will send a message to the server that was started earlier.
public static void main(String[] args) throws Exception
{
   Connection connection = null;
   try
   {
       // Step 1. Directly instantiate the JMS Queue object.
       Queue queue = HornetQJMSClient.createQueue("exampleQueue");

       // Step 2. Instantiate the TransportConfiguration object which contains the knowledge of what transport to use,
       // The server port etc.

       Map connectionParams = new HashMap();
       connectionParams.put(PORT_PROP_NAME, 5445);

       TransportConfiguration transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName(),
                                                                                  connectionParams);

       // Step 3 Directly instantiate the JMS ConnectionFactory object using that TransportConfiguration
       ConnectionFactory cf = HornetQJMSClient.createConnectionFactory(transportConfiguration);

       // Step 4.Create a JMS Connection
       connection = cf.createConnection();

       // Step 5. Create a JMS Session
       Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

       // Step 6. Create a JMS Message Producer
       MessageProducer producer = session.createProducer(queue);

       // Step 7. Create a Text Message
       TextMessage message = session.createTextMessage("This is a text message");

       System.out.println("Sent message: " + message.getText());

       // Step 8. Send the Message
       producer.send(message);

       // Step 9. Create a JMS Message Consumer
       MessageConsumer messageConsumer = session.createConsumer(queue);

       // Step 10. Start the Connection
       connection.start();

       // Step 11. Receive the message
       TextMessage messageReceived = (TextMessage)messageConsumer.receive(5000);

       System.out.println("Received message: " + messageReceived.getText());
   }
   finally
   {
      if (connection != null)
      {
         connection.close();
      }
   }
}
You can use this template as a starting point for any projects you may want to create that would use HornetQ as a client or as an embedded server.