In this recipe we look at how we can create more robust JMS applications by making use of ActiveMQ's Failover transport to automatically reconnect a client to a broker in case of a connection failure. We will also discuss some general error-handling tips when using JMS in our applications.
For this recipe we will use two examples; the first is failover-producer and the second is failover-consumer that demonstrate how a lost connection doesn't have to affect our client applications.
To run the sample for this recipe, you will need to perform the following steps:
Open a terminal and start a broker.
Open a second terminal, change the path to the directory where failover-consumer is located, and run it by typing
mvn compile exec:java
.Open a third terminal and change the path to point to the directory where failover-producer is located and run it by typing
mvn compile exec:java
.While the producer and consumer applications are running, shut down the running broker instance and restart it.
In the terminal where you started the consumer example, you will see output similar to the following snippet, indicating that the application is running:
Starting example Failover Consumer now... Failover Consumer received Message #1 Failover Consumer received Message #2 Failover Consumer received Message #3 Failover Consumer received Message #4 Failover Consumer received Message #5 Failover Consumer received Message #6 2013-03-06 14:15:23,731 [0.1:61616@59602] - WARN FailoverTransport - Transport (tcp://127.0.0.1:61616) failed, reason: java.io.EOFException, attempting to automatically reconnect Failover Consumer received Message #7 Failover Consumer received Message #8
In the terminal windows where you started the producer example you will see output like following indicating that the application is running.
Starting example Failover Producer now... Failover Producer sent Message #1 Failover Producer sent Message #2 Failover Producer sent Message #3 Failover Producer sent Message #4 Failover Producer sent Message #5 Failover Producer sent Message #6 2013-03-06 14:15:23,733 [0.1:61616@59606] - WARN FailoverTransport- Transport (tcp://127.0.0.1:61616) failed, reason: java.io.EOFException, attempting to automatically reconnect Failover Producer sent Message #7
You can start and stop the broker as many times as you wish during the run of the examples; they will each detect and recover from the connection loss. The producer will finish after it has sent 1000 messages successfully to the queue.
Up to this point in the book, we haven't really talked much about what happens when a broker fails while a client is producing or consuming messages. JMS, on its own, doesn't specify any sort of failover or loss of connection-handling behavior for the JMS provider. If your client connects to ActiveMQ Broker using the standard TCP-based transport protocol we've been using in our examples so far, when the broker fails, the client connection breaks and an exception will be generated as soon as the client notices that the connection is gone.
There are two ways your client can get notified of a connection error. The first is by an exception being thrown from a method call such as a send()
operation, and the second is from an asynchronous exception listener registered on the Connection
object. The asynchronous exception interface is shown here:
class ExceptionListener { void onException(JMSException ex); }
In order to receive the asynchronous exceptions, your code implements the ExceptionListener
interface and does something to react to the error. Reacting to the error is where things get complicated. There are a number of exception types defined in the JMS API. In spite of this, when an error occurs but your connection is still usable, it's tricky to tell whether you need to shut everything down and rebuild the JMS resources from scratch. Doing so is usually not a trivial bit of work inside your application. Fortunately, ActiveMQ provides us with an elegant solution to dealing with connection failures in our client applications.
ActiveMQ provides a Transport protocol known as Failover transport that can do the work of dealing with connection problems in our applications, leaving us to focus on our business logic and not worry about how we deal with every failure case. We used Failover transport in the examples for the recipe so that you could start and stop your broker as much as you wanted without the examples failing before they had sent and received all their messages.
Using Failover transport in our examples, or any of the other examples in this book, requires only one change to the way we we've implemented our code so far. That change is in the connection URI we passed to our ConnectionFactory
instance; here's what the URI looks like in this recipe's examples:
failover:tcp://localhost:61616
That's it, we just added one little word, "failover", to the connection URI and the client becomes fault tolerant. That's pretty great right?
Failover transport isn't limited to just keeping our client connected to only one broker. We can specify the address of several brokers on our client's connection URI and Failover transport will work its way through them as brokers fail, allowing us to ensure that our client can quickly recover when it loses contact with the connected broker. If we knew that there were two brokers on our network and we wanted our client to failover to another when needed, we could use a URI similar to the following one:
failover:(tcp://host1:61616,tcp://host2:61616)
You can add an entry for every broker on your network in this manner, and the client will connect to the next available broker whenever one goes down. By default, the transport selects a random host from the set of available hosts each time it tries to connect. You can alter the URI to make this process non-random with the following change.
failover:(tcp://host1:61616,tcp://host2:61616)?randomize=false
When a client's connection is down and Failover transport is working to reconnect to another broker, any call that your client makes that involves sending information to the broker will be blocked until the connection is restored. It's possible to monitor what Failover transport is doing in your application by using the TransportListener
interface provided by the ActiveMQ client API.
The TransportListener
interface provides methods for being notified of connection interruption as well as resumption of connection. You can also listen in on the exceptions that are thrown and other commands sent from the broker. You create and add a TransportListener
interface to your connection with the following code:
Class MyTransportListener implements TransportListener { public void onCommand(Object command) {} public void onException(IOException error) { public void transportInterupted() { // app logic } public void transportResumed() { // app logic } } MyTransportListener listener = new MyTransportListener(); ((ActiveMQConnection) connection).addTransportListener(listener);
Implementing a TransportListener
interface is a good way to add logging to your application that reports on the connection state of your client as well as logging exceptions.
We said earlier that when you configure your client connection URI for failover, you can specify the address of several different brokers that the client can try connecting to when things go wrong. This is a great feature, but imagine that your client operates in a large cluster of ActiveMQ Brokers where broker instances can be added or removed at any time. It would be quite cumbersome to stop all the running clients, update their connection URIs, and restart them every time the broker cluster changes. Fortunately, there's a solution for this problem.
By enabling the update cluster clients option in your ActiveMQ Broker-side configuration file, you can have the broker send information to the clients that keeps their Failover transports updated on the brokers currently running in a cluster. Enabling this feature is simple; you just update your configuration to look like the following snippet:
<broker> ... <transportConnectors> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616" updateClusterClients="true" updateClusterFilter="*A*,*B*" /> </<transportConnectors> ... </broker>
Now, in your client code, you only need to specify the address of the first broker in your cluster and the client will be updated as other brokers are added.
It's also possible to have the brokers in a cluster rebalance the connected clients to more evenly distribute the load across the cluster using this update cluster clients feature. You can read more about this as well as other options available for configuring your failover-enabled clients on the ActiveMQ site, http://activemq.apache.org/failover-transport-reference.html.
Failover transport has a number of configuration options that allow your application to specify exactly how it wants the transport to behave. For instance, you can specify that the transport only attempts to connect to the broker a set number of times before giving up. The complete reference for Failover transport options can be found on the ActiveMQ website, http://activemq.apache.org/failover-transport-reference.html.