Serverless JMS 0.1
Implementation Notes
$Date: 2004/03/14 00:00:45 $
$Revision: 1.7 $
Introduction
Building Instructions
Running Examples
Design Notes
What's Next
Introduction
These notes document the first release of the Serverless JMS prototype,
a JMS 1.1 provider based on the JGroups toolkit. "Serverless JMS", or
SLJMS, is probably a temporary name, but it will be used throughout
this document to refer to the provider implementation.
The current release
includes support for topics and
queues. It does not includes persistence of any kind (persistent
delivery or persistent topics), transactional support and many other
advanced JMS topics. The implementation is still an early prototype.
The implementation is
accompanied by several examples that run out-of-the-box. There are two
very simple examples of
topic publisher/subscriber and queue sender/receiver; both use
the common JMS 1.1 interfaces. They have been
tested with SLJMS and also with the JMS provider included with the Sun
Microsystems'
SUNWappserver 1.4.
A more generic JMS client is also included. It offers a shell-like
command line interface that allows a user to initialize and
create different JMS components and test different combinations of JMS
API calls. Detailed
instructions on how to build and run the examples are provided in
the "Running Examples" section below.
Building Instructions
Requirements
- J2SDK version 1.4 or higher. The correct javac and java should be available in
your PATH. This applies both for Unix and Windows.
- ant version 1.5.4 or
higher. The ant
executable
should be either available in your PATH, or ANT_HOME environment
variable should
be set. On Windows, ANT_HOME takes precedence.
Optional Configuration Choices
The examples that are included with the SLJMS bundle could run with any
other JMS 1.1 compliant provider. The wrapper
scripts can be used directly with Sun Microsystems' SUNWappserver 1.4
JMS provider. If you intend to do that, please install
SUNWappserver following Sun's instructions and set SUNAPPSERV_HOME
environment variable to point to the installation directory. However,
setting SUNAPPSERV_HOME is not required for compilation.
By default, the build system uses the library jars provided with the
bundle, in the subdirectory lib
(jgroups-core-2.2.1.jar, log4j.jar,
etc). If you want to use your own classes or jars for compilation and
execution, create a file local/config
in the project's home and add to it a line similar to this one:
export
LOCAL_CLASSPATH=/my-own-JGroups/classes:/my-own-JGroups/conf:/opt/my-own-log4j.jar
How to Build
Untar the bundle in a work directory of your choice. tar extraction
will create a sljms
subdirectory. Build it by running ./build.sh.
For optional
targets,
consult build.xml.
gunzip < sljms-0.1.tar.gz | tar
xfv -
cd sljms
./build.sh
|
The building procedure has been tested on Linux RedHat 7.3. In
principle it should work without problems on other UNIX platforms, too.
On Windows, use build.bat
instead of build.sh.
Running Examples
The files used to configure the JMS
runtime are located in etc
subdirectory. In principle, the examples should work without any
configuration changes, but if you are inclined to tweak configuration
parameters, or you need to, here is a list of the
configuration files:
jndi.properties - It is a
symbolic link made at runtime to one of jndi.properties.jg, jndi.properties.sun or jndi.properties.jboss,
depending on the argument you use with your launcher script. It
is the standard JNDI property file.
log4j.properties - The
log4j configuration file that controls the verbosity of the logging
output. If you want a more verbose JGroups runtime, change log4j.category.org.jgroups to
INFO or even DEBUG. If you want more SLJMS logging information,
change log4j.category.sljms
to DEBUG.
sljms.xml - It is the
JGroups stack configuration file for the server control channel. If
something doesn't work with SLJMS, such as
clients not being able to connect to the "server", this is usually the
first place you should look. For more information on the channel
configuration file, please consult JGroups
documentation. If you want to try a different channel configuration,
you should place the corresponding xml file somewhere in the example's
classpath (the ./etc
directory is a good place) and you should modify jndi.properties.jg to list the
new file name as value of the
property sljms.jndi.connectionFactory.<ConnFactoryJNDIName>.
You can find more details about configuring a ConnectionFactory and its
associated stack file in JNDI
Implementation section.
corba.properties -
This file is relevant only if you plan to run the examples with the
Sun's
SUNWappserver JMS provider.
The first argument of the wrapper scripts must be one of 'jg', 'jboss'
or 'sun'. The argument is used by the script to build the correct
classpath.
JMS 1.1 Topic
Publisher/Subscriber example
This is a very simple JMS 1.1 Pub/Sub example that uses JMS 1.1 common
interfaces. One or more clients subscribe
to a known topic and wait for messages. Another client publishes a
variable number of text messages on the topic and then sends a special
end-of-communication message. The number of messages to be sent is
specified as a command line parameter. Upon arrival of the last
message,
the subscribers display statistics and exit. The example can be
run with SLJMS or any other JMS provider that
supports JMS 1.1 common interfaces. It has been tested with
SUNWappserver 1.4 reference implementation.
Running the example
with SLJMS
From the home directory of the project,
go to bin and start the
subscriber:
UNIX:
cd bin
./subscriber jg
Windows:
cd bin
subscriber.bat jg
|
After a while you should see the message "Connection started, waiting
for messages ...", which means the subscriber is ready.
Start the producer only when the subscriber is up and
running.
It can be started on the same machine or on a different machine that
has access to the project directory and can reach by multicast the
machine
with the subscriber.
UNIX:
cd bin
./publisher jg [number_of_messages]
Windows:
cd bin
publisher.bat jg [number_of_messages]
|
If not specified, the default number of messages is 10.
The subscriber displays
the number of messages received and time statistics, and then exits.
The publisher has to be explicitly killed with Ctrl-C.
Running the
example with SUNWappserver
Before running the examples, make sure
the server is up and running.
Use Sun's administrative interface to create a topic connection factory
named "ConnectionFactory" and a topic named "Topic1". A recipe on how
to do it is provided with the J2EE
tutorial:
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/index.html,
Chapter 29, The Java Message Service API.
Modify the file
etc/corba.properties
to contain a valid
ORBInitialHost
and
ORBInitialPort, which
are the
host running the application server and the port JNDI runtime is
listening on (usually 3700).
From the home directory of the project,
go to
bin and start the
subscriber:
cd bin
export SUNWAPPSERV_HOME=<your_SUNWappserver_home>
./subscriber sun
|
After a while you should see the message "Connection started, waiting
for messages ...".
Start the producer only when the subscriber is up and
running.
It can be started on the same machine or on a different machine that
has access to the project directory and the application server.
./publisher sun
[number_of_messages]
|
If not specified, the default number of messages is 10.
The subscriber displays
the number of messages received and time statistics, and then exits.
The publisher has to be explicitly killed with Ctrl-C.
JMS 1.1 Queue
Sender/Receiver example
This is a very simple JMS 1.1 P2P example that uses JMS 1.1 common
interfaces. A client creates two queue
receivers for a known queue and then waits for messages. Other client
sends a variable number
of text messages to the queue and stops. The number of messages to be
sent is specified on
command line. The example can be
run with SLJMS or any other JMS provider that
supports JMS 1.1 common interfaces. It has been tested with
SUNWappserver 1.4 reference implementation.
Running the example with
SLJMS
From the home directory of the project,
go to bin and start the
receiver:
UNIX:
cd bin
./queueReceiver jg
Windows:
cd bin
queueReceiver.bat jg
|
After a while you should see the message "Connection started, waiting
for messages ...".
Start the sender only when the receiver is up and
running (the queues don't persist anything yet).
It can be started on the same machine or on a different machine that
has access to the project directory, and can reach by multicast the
machine
with the receiver.
UNIX:
cd bin
./queueSender jg
[number_of_messages]
Windows:
cd bin
queueSender.bat jg
|
If not specified, the default number of messages is 10.
The receiver reports the messages it has received. Both the sender and
the receiver have to be explicitly killed with Ctrl-C.
A Dynamic JMS
command line client
A third example can be used to interact more dynamically the SLJMS
runtime. The launcher script name is interactive. This programs has
a shell-type command line interface that accepts commands very similar
to JMS 1.1 API calls. The program can be used to dynamically look up
ConnectionFactories and Destinations, create a Connection to the
server, create one or more Sessions, MessageProducers and
MessageConsumers and then send text messages and display received text
messages. It can be used to try and test different combinations of JMS
API calls.
The program should work with any JMS 1.1 compliant provider, according
to the same usage patterns presented in the previous examples.
Due to the limitations of the current version of the prototype, there
is only one hardcoded "ConnectionFactory" JNDI name that can be used to
lookup a ConnectionFactory, and only two destinations: "Topic1" and
"Queue1".
A list with the supported "API calls" can be obtained at any time by
typing help.
A dump of the current JMS runtime (the list of active connections,
sessions, producers and consumers) can be generated at any time by
typing runtime.
The complete documentation for this client is out of the scope of this
document. However, relatively self-explanatory screen dumps of two
different interactive sessions (one that configures and uses a
topic publisher, and the other one that configures a topic subscriber
to receives the messages sent by the publisher), are presented below.
To configure the topic subscriber:
UNIX:
cd bin
./interactive jg
Windows:
cd bin
interactive.bat jg
|
> lookupConnectionFactory
ConnectionFactory
> ok
> createConnection
> ok
> createSession false, AUTO_ACKNOWLEDGE
> ok
> createConsumer 0 Topic1
> ok
> setMessageListener 0.0
> start
> ok
|
To configure the topic publisher:
UNIX:
cd bin
./interactive jg
Windows:
cd bin
interactive.bat jg
|
> lookupConnectionFactory
ConnectionFactory
> ok
> createConnection
> ok
> createSession false, AUTO_ACKNOWLEDGE
> ok
> createConsumer 0 Topic1
> ok
> setMessageListener 0.0
> start
> ok
|
After sending the text message, the subscriber should display something
similar to:
Consumer 0.0: this is the content
of the message
The program can be used to mix and match different
runtime configurations and compare the behavior of different JMS
providers.
Design Notes
A SLJMS server instance is virtually a JGroups
group. The client runtime is built around a JChannel that connects to
the server's "control group". The main JGroups control
channel provides the
heavyweight support (sockets, threads) for communication with the
"server".
The current implementation uses one channel to both maintain the state
of the server and to exchange regular JMS messages. Performance
profiling
will probably show that only one JChannel
per client
runtime is not enough, so secondary channels used for message traffic
could be added.
A simplified diagram of the SLJMS runtime is presented below:
Fig. 1 The SLJMS runtime architecture.
A SLJMS runtime connects to the
server and starts exchange messages in the following sequence:
- Get a JNDI InitialContext.
Context
initialContext = new InitialContext();
JNDI Implementation
The prototype is designed to work
standalone, so far, so it doesn't
rely on the presence of an application server with its associated JNDI
namespace. To provide a consistent interface to the client code, the
runtime relies on its own
InitialContextFactory
that returns
GroupContexts.
GroupContexts are very
simple
Context
implementations that do not try to look up
names externally, but rely on local configuration files. While this
solution is good enough for a prototype, the next implementation of the
standalone JNDI runtime will possibly try to connect to the server
group and
lookup names using the group state. The group state (a common set of
data that is stored by all members of
the group and is kept synchronized) is a good candidate as the source
of the JNDI namespace, because even for the current implementation, the
group state keeps detailed information about the existing Queues, for
example.
JMS relies on JNDI to lookup two types of adminstered objects:
ConnectionFactories and Destinations. These two categories of objects
are handled differently.
A ConnectionFactory incapsulates the knowledge to connect to a certain
provider instance, in this case a server group. Since a group is
essentially uniquely identified by its group name and stack
configuration, a ConnectionFactory needs exactly these two elements to
be able to create connections to that "server".
For the standalone implementation, no centralized JNDI nampespace is
available to register the associations, so it has to be locally
specified. The current mechanism relies on
jndi.properties file and a set
of provider-specific properties. To "bind" a a certain
ConnectionFactroy in the JNDI namespace, specify a property
sljms.jndi.connectionFactory.<ConnectionFactoryJNDIName>=<stack_config_file>
A JMS client would then be able to lookup
<ConnectionFactoryJNDIName>
in JNDI and obtain a ConnectionFactory instace that produces
Connections based on the stack configuration specified in the is the
<stack_config_file>.
<stack_config_file> must
be the name of a JGroups stack configuration XML file, available
somewhere in the current classpath.
Destinations are similarily handled. To make a Topic/Queue "available"
in JNDI, add
sljms.jndi.destination.<TopicJNDIName>=topic
sljms.jndi.destination.<QueueJNDIName>=queue
- Look up the ConnectionFactory.
ConnectionFactory
connectionFactory = (ConnectionFactory)initialContext.lookup("ConnectionFactory");
The class that implements ConnectionFactory
interface is GroupConnectionFactory.
Its main role is to get the JGroups channel configuration from the JNDI
runtime and pass it to the Connection
instances it creates.
- Create a Connection,
one or more Sessions,MessageProducers,MessageConsumers and start
sending and receiving messages.
Connection
connection = connectionFactory.createConnection();
Session session =
connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(topic);
TextMessage message = session.createTextMessage();
message.setText("...");
producer.send(message);
The Connection
implementation is the central piece of the SLJMS runtime. The actual
class that implements the interface is GroupConnection. GroupConnection
controls the thread ("Connection Management Thread") that pulls JGroups
Events and Messages from the channel. It makes a first selection based
on the message/event semantics, either updating the group state or
forwarding the message to the delivery queue.
The messages are pulled out of the delivery queue by the SessionManager. There is only
one SessionManager
instance per
connection and it has two responsibilities: it synchronizes
access to the session list, making sure that only one thread operates
with the session list at a time, and handles message delivery to and
from its sessions. It uses
its own thread(s) to extract messages from the queue and distribute
them to the topic subscribers / queue receivers, according to their
destinations.
The "Connection Management Thread" is always on for the life time of
the connection, because it has to asynchronously handle JG events and
messages that are
essential to group management. A connection can be "stopped" by just
stopping the message delivery to the queue (the current implementation
just discards the messages arrived when the connection is stopped).
During this time, the SessionManager thread blocks on read on an empty
queue.
JMS specification mentions that a stopped connection can still be used
to send messages. Even if the connection is stopped, a message producer
can accept a message for delivery. It delegates the delivery to its
session, which in turn delegates it to the SessionManager, which
immediately submits the message to the connection. The GroupConnection puts the
message on the channel.
What's Next
Add support for:
- persistent delivery (topics and queues) (release 0.2)
- persistent topics (release 0.3)
- transactions (release 0.4)