RMI via JMS is a small java library that allows you to do Java Remote Method Invocations (RMI) via a Java Message Service (JMS) provider like ActiveMQ. RMI is still the simplest and most accessible distributed programing models available to Application developers. When you use RMI via JMS, all remote RMI objects are bound to destinations on the JMS message bus. This allows you to leverage the message bus to achieve better availability and scaleability.
1. Introduction
RMI provides a easy to use distributed computing API that is built into every JVM. There are few distributed technologies which provide and easier use distributed model. Furthermore, RMI is lightweight. Since it’s built into the JVM you don’t need to run in sever containers. A simple Main will do, thank you.
The problem is eventually folks start avoiding RMI due to it being point to point client server architecture. Point to point architectures bind a client to a specific sever node. In todays day and age, server outages have to be part of the standard operating parameters. In RMI land, this mean the clients tend have a tremendous amount of code to detect and handle request failures. There is no built in mechanism to fail over to another node in a server cluster.
It’s a well known fact that system administrators HATE RMI. That hate is due to RMI based servers tending to use random TCP ports for their services. It makes it nearly impossible for them to properly lock down the servers with a firewall configuration.
By using RMI via JMS, this project aims to eliminate those problems. Don’t assume this is a silver bullet for RMI woes. This project does not try to address some of the other problems that RMI has like, fragile service versioning. But, it does throw in some nice bonus features like Oneway remote invocations which can provide nice performance boosts for some use cases.
2. Getting Started
You first need to add the rmiviajms-1.1.jar file to the classpath of your clients and server applications.
Next you need to modify the existing RMI applications to export remote objects using RMI via JMS instead of exporting over the standard RMI protocol. Generally is just a simple search and replace of UnicastRemoteObject with JMSRemoteObject. The Developer Guide section goes into more details on how to do this.
Finally, when you start your applications you will need to pass some configuration settings via system properties so that RMI via JMS runtime knows how to connect to your JMS provider. The default settings try to connect to an ActiveMQ server at tcp://localhost:61616.
3. Developer Guide
3.1. Converting From Standard RMI
3.1.1. Subclasses of UnicastRemoteObject
RMI server objects commonly extend UnicastRemoteObject. Objects which extend UnicastRemoteObject are bound in RMI as soon as they are constructed. The following listing shows an example of this use case:
import java.rmi.server.UnicastRemoteObject; ... public class HelloWorld extends UnicastRemoteObject // <1> implements IHelloWorld { HelloWorld() throws RemoteException { // <2> } HelloWorld(int port) throws RemoteException { super(port); // <3> } ...
-
The class extends UnicastRemoteObject.
-
Even the default constructor must throw a RemoteException.
-
There is an optional constructor to bind the object to specific TCP port.
The following listing shows you how to convert that server object to use RMI via JMS.
import org.fusesource.rmiviajms.JMSRemoteObject; import javax.jms.Destination; ... public class HelloWorld extends JMSRemoteObject // <1> implements IHelloWorld { HelloWorld() throws RemoteException { // <2> } HelloWorld(Destination dest) throws RemoteException { super(dest); // <3> } ...
-
Extend the JMSRemoteObject class
-
Default constructors remain the same.
-
Binding to TCP port does not make sense in the JMS world. Instead you bind to a JMS Destination object.
3.1.2. Classes exported by UnicastRemoteObject
Remote server objects don’t have to subclass UnicastRemoteObject. Since they don’t subclass UnicastRemoteObject, then they will not be bound in RMI until you explicitly bind them using the UnicastRemoteObject.exportObject static method. For example, if you have the following class:
public class HelloWorld implements IHelloWorld { ... }
Then then following listing shows two ways to export RMI objects which do not extend UnicastRemoteObject.
public void simple() { HelloWorld hw = new HelloWorld(); IHelloWorld proxy = (IHelloWorld)UnicastRemoteObject.exportObject(hw); // <1> ... } public void advanced() { HelloWorld hw = new HelloWorld(); int port = 1021; IHelloWorld proxy = (IHelloWorld)UnicastRemoteObject.exportObject(hw, port); // <2> ... }
-
Binds the object on an anonymous port
-
Binds the object to a specific TCP port
|
|
The exportObject method call returns a proxy object. This object can be serialized and sent to clients so that they perform remote invocations on that instance of the HelloWorld object. Typically RMI applications register this proxy in an RMI Registry. |
The following listing shows you how to convert that code to bind the object using RMI via JMS.
public void simple() { HelloWorld hw = new HelloWorld(); IHelloWorld proxy = (IHelloWorld)JMSRemoteObject.exportObject(hw); // <1> ... } public void advanced() { HelloWorld hw = new HelloWorld(); Destination dest = ... IHelloWorld proxy = (IHelloWorld)JMSRemoteObject.exportObject(hw, dest); // <2> ... }
-
A simple class name change is all that is need
-
Once again, binding to a TCP does not makes sense in JMS, instead you bind to a specific JMS Destination.
3.2. Additional Features
3.2.1. The Oneway Annotation
RMI via JMS supports asynchronous remote method invocations. What this means is that the client issues a remote method invocation and does not wait for the server to complete it. This feature can be leverage in several use cases. The obvious one is when the server method take a long time to complete, but the client is not really interested waiting for that completion. The other less obvious use case is to increase remote method invocation throughput.
Standard remote method invocations are synchronous and there is built in wait since the client has to wait for the response of the sever. The amount of time you wait is based on response latency/network latency. Even if you are running on a 1000Mb Ethernet, your latency will probably be about the same as if you were running 100Mb Ethernet. But if you use asynchronous remote method invocations, the client can issue requests as fast as the network can accept them. So can issue requests much faster, network bandwidth is your main limiter.
To configure a method to use asynchronous remote method invocations, you just need to annotate the Remote method interface with the @Oneway annotation. For example:
import org.fusesource.rmiviajms.Oneway; ... public static interface ITest extends Remote { @Oneway void slowOp(int value) throws RemoteException; }
|
|
Methods annotated wiht @Oneway must return void. It does not make sense to return a value if you are not going to wait for it’s result. |
4. System Properties
|
Default
|
org.fusesource.rmiviajms.internal.ActiveMQRemoteSystem |
|
Description
|
Set this property to the right class implementation for your JMS provider. |
|
Default
|
Long.MAX_VALUE |
|
Description
|
The maximum about of time to wait for remote invocation response in milliseconds. |
|
Default
|
tcp://localhost:61616 |
|
Description
|
The URL used to connect to the JMS provider. |
|
Default
|
rmiviajms. |
|
Description
|
Destination queues automatically be prefixed with this value. |