Overview
Jini is a distributed computing technology developed by Sun Microsystems. It is now being continued under the auspices of the Apache River project.
This page provides some background information about Jini, a description of one of the most common patterns used in Jini systems, and a simple introductory example. If you want more detail, I recommend Jan Newmarch's Jini Tutorial.
An Introduction to Jini
L. Peter Deutsch of Sun Labs is known for his list of Eight Fallacies of Distributed Computing:
- The network is reliable
- Latency is zero
- Bandwidth is infinite
- The network is secure
- Topology doesn't change
- There is one administrator
- Transport cost is zero
- The network is homogeneous
Jini directly addresses each of the fallacies Deutsch described. Jini supports the development and deployment of highly available, highly reliable distributed systems despite the unreliability of the underlying machines, operating systems, and networks. To do so, Jini relies on four core capabilities:
- Protocol Independence While RMI is the default, Jini services can use any communication protocol.
- Location Independence Components that use services do not need to be configured with explicit knowledge of the location of the service. Service implementations can be moved without impacting clients.
- Interface / Implementation Decoupling Service implementations can be modified dynamically without impact to clients. Multiple different implementations can run simultaneously.
- Automatic Performance Tuning Load balancing and other performance enhancements can be made dynamically by bringing up additional instances of services or moving instances to better hardware. Clients are not impacted by, or even aware of, these operations.
Jini Infrastructure
Jini comes with several standard infrastructure components out of the box. To achieve the non-functional requirements (NFRs) of performance, resiliency, and scalability, multiple instances of these components run simultaneously on different machines.
The standard Jini services are:
- Lookup Service The lookup service, named reggie, is the first among equals of Jini services. All services in a Jini architecture register with the lookup service to make themselves available to other services. All initial access to other services is via the lookup service, after which clients bind directly.
- Class Server The class server, a simple HTTP daemon, eliminates the coupling between clients and service implementations. Clients deal with interfaces. If a particular implementation class is required, it is downloaded transparently.
- Transaction Manager Distributed transactions support is provided by the transaction manager service called mahalo.
- JavaSpace Services with a requirement to share information with other services do so through a JavaSpace. The reference implementation of the JavaSpace is named outrigger.
The Jini reference implementation includes other services that are not required for this introductory example.
JavaSpaces
A JavaSpace is a Jini service that stores Java objects in memory. This makes it very useful for supporting collaboration among other services, in addition to its uses as a simple shared memory. The JavaSpaces API is very simple, consisting of seven methods in total, four of which are core:
- write Write an object to the JavaSpace.
- read Read an object from the JavaSpace. This returns a copy and leaves a copy in the JavaSpace.
- take Take an object from the JavaSpace. This returns a copy and removes the object from the JavaSpace.
- notify Register a callback to be notified when changes occur in the JavaSpace.
(Image courtesy of Sun Microsystems)
Those four methods suffice to provide the core features most often required in distributed systems:
write + read = caching
write + take = parallel processing
write + notify = messaging
Leasing
One feature of Jini worth separate mention is the concept of leasing. All resources in a Jini system are leased, including proxy records in the lookup service, transactions, and, of course, memory in a JavaSpace. When a lease expires, the resource is recovered and made available to other components. This prevents resource accretion, a common problem in distributed systems.
Leases can be renewed explicitly, or implicitly through a lease renewal manager. In the interests of simplicity, the example below uses leases that last "forever." This is obviously inappropriate for a production system.
The Entry Interface
All objects that can be stored in a JavaSpace must implement the
Entry interface. Entry is a simple tag
interface that does not add any methods but does extend
Serializable.
Like
JavaBeans,
all Entry implementations must provide a public
constructor that takes no arguments. Unlike JavaBeans, all of the
data members of an Entry must be public. This design
decision is explained by Ken Arnold in an
interview with Bill Venners:
So if you examine the contract description for an entry'sgetandsetmethods, you would see it describes a field.getandsetwould have to act exactly like a field. Therefore, we asked ourselves, why should we havegetandsetmethods whose behavior is exactly like this other language construct called a field? Why not just make it a field? If we make it a field, it will have the correct behavior. Nobody can accidentally screw up theirgetandsetmethods. Making it a field eliminates a source of error.
Now this sometimes makes people uncomfortable because they've been told not to have public fields; that public fields are bad. And often, people interpret those things religiously. But we're not a very religious bunch. Rules have reasons. And the reason for the private data rule doesn't apply in this particular case. It is a rare exception to the rule. I also tell people not to put public fields in their objects, but exceptions exist. This is an exception to the rule, because it is simpler and safer to just say it is a field. We sat back and asked: Why is the rule thus? Does it apply? In this case it doesn't.
In production Jini systems, the public data members are a non-issue
because of common techniques like the envelope-letter idiom.
The Entry implementation acts as an envelope or wrapper
around the "real" payload, which may be any serializable Java object.
The only public data members exposed are those required for the
template-based matching of the JavaSpaces API.
The Master-Worker Pattern
After simple object caching, the Master-Worker pattern is the most common use of JavaSpaces. This pattern is quite simple: a master process writes tasks into a JavaSpace, worker processes process those tasks, replacing them with results, and the master retrieves the results.
(Image courtesy of Sun Microsystems)
The master-worker pattern is used in a wide variety of problem domains where either a problem is highly parallelizable or there are a set of tasks that can be performed by any of a set of workers. One particularly nice feature of this pattern is that there is no need for complex scheduling algorithms. The workers take tasks at whatever rate they are able to process them, automatically load balancing the work across available resources.
A variant of the master-worker pattern takes advantage of the fact
that a JavaSpace holds full Java objects, not just data. This makes
it possible to pass behaviors as well as state between the master and
workers. This, in turn, allows the creation of generic workers
that can process any task. In this version of the pattern, tasks
either expose an execute() method or include a
strategy to
configure the generic worker.
Example
The purpose of this example is to be a walking skeleton of a Jini system. It consists of:
- The Jini lookup service
- The Jini transaction manager
- A JavaSpace
- The master, a JavaSpace client that writes tasks and reads results
- The worker, a JavaSpace client that takes tasks and writes results
The Task and Result
The SleepTask and SleepResult classes are
simple Entry implementations, not using the
envelope-letter idiom. Without comments,
SleepTask.java looks
like this:
Aside from generating random sleep times, it does nothing.
SleepResult.java is
even simpler:
public class SleepTask implements Entry
{
private static final long MAX_SLEEP_TIME = 5000;
private static Random generator_ = new Random();
public Long masterID;
public Long taskID;
public Long sleepTime;
public SleepTask(long masterID,long taskID)
{
this.masterID = new Long(masterID);
this.taskID = new Long(taskID);
sleepTime = new Long(generator_.nextLong(MAX_SLEEP_TIME));
}
public SleepTask()
{
}
}
public class SleepResult implements Entry
{
public Long masterID;
public Long taskID;
public Long timeSlept;
public SleepResult(long masterID,long taskID,long timeSlept)
{
this.masterID = new Long(masterID);
this.taskID = new Long(taskID);
this.timeSlept = new Long(timeSlept);
}
public SleepResult(SleepTask task)
{
masterID = task.masterID;
taskID = task.taskID;
timeSlept = task.sleepTime;
}
public SleepResult()
{
}
}
The Master
The SleepMaster class writes a specified number of
SleepTask objects to a JavaSpace and then loops until it
has retrieved the same number of SleepResult objects.
Before SleepMaster can write to a JavaSpace, it needs a
reference to one. For simplicity, this example uses my
Jini Lookup Utilities to connect
to available lookup services and find JavaSpace proxies.
The SleepMaster creates a random ID to uniquely identify
the tasks it has created. The tasks themselves are numbered
sequentially. This design allows multiple masters to run
simultaneously. Shorn of its exception handling, the code to write to
the space looks like this:
The
space().write(new SleepTask(masterID_,taskID++),null,Lease.FOREVER);
null indicates that no explicit transactional context
is specified. In a production system, the lease specified in the
final parameter would not typically be permanent.
The code to take results from the space is:
JavaSpaces use template based matching to find entries. In this case
I want to retrieve any result associated with the current master, so I
only specify the
SleepResult result = null;
SleepResult template = new SleepResult();
template.masterID = masterID_;
. . .
result = (SleepResult)space().take(template,null,Long.MAX_VALUE);
masterID. JavaSpaces ignores the fields
left null.
In a production environment, the timeout period specified in the last parameter would be much shorter.
The Worker
The SleepWorker class loops, performing a blocking take
for SleepTask objects. When a task is found, the worker
removes it from the space, sleeps for the specified duration, and
writes a corresponding SleepResult back to the space.
The take and write operations are performed
in a transaction to ensure that all tasks are processed and a result
is returned.
As with the SleepMaster, for simplicity
SleepWorker uses my
Jini Lookup Utilities to connect
to available lookup services and find JavaSpace proxies.
SleepWorker uses the same utilities to find transaction
manager proxies as well.
The code to take tasks from the space looks very similar to the
SleepMaster for retrieving results:
The primary difference is the use of a transaction, which is created
from the Jini transaction manager:
SleepTask task = null;
SleepTask template = new SleepTask();
. . .
task = (SleepTask)space().take(template,transaction,Long.MAX_VALUE);
The design of the Jini transaction mechanism is a little unusual, but
easy to figure out. One aspect that is not obvious is that, like all
Jini resources, transactions are leased. If a transaction is not
committed or aborted before the lease expires, it is rolled back. If
you leave the
Transaction.Created created
= TransactionFactory.create(transactionManager(),
TRANSACTION_LEASE_TIME);
return created.transaction;
SleepWorker running for longer than the
TRANSACTION_LEASE_TIME (thirty seconds by default)
without any tasks to process, you will see log messages related to the
lease timing out.
Writing the result back mirrors the initial task write, again with the
addition of a transactional context:
space().write(new SleepResult(task),transaction,Lease.FOREVER);
SleepWorker runs until it is killed.
Building
You will need the Jini 2.1 Starter Kit and J2SE 5.0 installed to build and run the example.
Building the example is straightforward. First, make sure our CLASSPATH includes:
$JINI_HOME/lib/jini-core.jar$JINI_HOME/lib/jini-ext.jar- The classes from my Jini Lookup Utilities
$JINI_HOME is the location where you installed the
starter kit.
Now just compile everything with javac *.java in the
MasterWorker directory.
Deploying Jini
Deploying the Jini infrastructure is considerably less straightforward
than building the example. In fact, deployment configuration is the
most difficult and error prone aspect of Jini, the first time you do
it. This example includes scripts to start the core Jini components.
These scripts are all located in the MasterWorker/bin
directory. The MasterWorker/config directory contains
the configuration details for each of these services. Many of those
derive from the defaults specified in
bin/set-environment.csh:
Before deploying the Jini components, you'll need to set
setenv JINI_HOME /usr/local/jini-2.1
setenv EXAMPLE_ROOT ..
setenv EXAMPLE_CONFIG $EXAMPLE_ROOT/config
setenv EXAMPLE_GROUP "Example.Master.Worker"
setenv EXAMPLE_CODEBASE_HOST "localhost"
setenv EXAMPLE_CODEBASE_PORT "8008"
setenv EXAMPLE_ARGS "-Djava.security.policy=${EXAMPLE_CONFIG}/security-off.policy \\
-DjiniHome=${JINI_HOME} -DexampleHome=${EXAMPLE_ROOT} \\
-DjiniGroup=${EXAMPLE_GROUP} \\
-DcodebasePort=${EXAMPLE_CODEBASE_PORT} \\
-DcodebaseURL=http://${EXAMPLE_CODEBASE_HOST}:${EXAMPLE_CODEBASE_PORT}"
JINI_HOME correctly for your environment. If you want to
run the scripts from somewhere other than bin, you'll
need to change EXAMPLE_ROOT to an absolute path. Before
running clients or services on different machines, change
EXAMPLE_CODEBASE_HOST to a real machine name.
The scripts should be run in this order:
-
start-httpd.csh
Start the class server. -
start-reggie.csh
Start the Jini lookup service. -
start-mahalo.csh
Start the Jini transaction service. -
start-outrigger.csh
Start a JavaSpace.
Running The Example
Any number of masters and workers can be started in any order. Obviously, the more workers running, the more tasks can be processed in a particular period of time. While the masters and workers can be started directly from the command line, a script is often more convenient.
The script to run the SleepWorker should look something
like this:
In a production environment, the host and port for the codebase would
not be hard coded. Also, the security policy would be considerably
less lax than the "anything goes" approach of
WORKER_CODEBASE="http://localhost:8008/reggie-dl.jar \\
http://localhost:8008/outrigger-dl.jar \\
http://localhost:8008/mahalo-dl.jar"
java -Djava.security.policy=config/security-off.policy \\
-Djava.rmi.server.codebase=$WORKER_CODEBASE \\
org.softwarematters.example.MasterWorker.SleepWorker Example.Master.Worker
security-off.policy.
The script to run the SleepMaster is very similar:
and is subject to the same caveats with respect to production. This
example writes ten
MASTER_CODEBASE="http://localhost:8008/reggie-dl.jar \\
http://localhost:8008/outrigger-dl.jar"
java -Djava.security.policy=config/security-off.policy \\
-Djava.rmi.server.codebase=$WORKER_CODEBASE \\
org.softwarematters.example.MasterWorker.SleepMaster 10 Example.Master.Worker
SleepTask objects into the space and
waits for ten SleepResult objects to be written back,
then terminates.
Summary
Jini provides some unique benefits for addressing the difficulty inherent in building, deploying, and managing distributed systems. Chief among these are the ability to self-heal, the prevention of resource accretion, and the simple programming model that comes from decoupling the logical components from the physical topology of the system.
The JavaSpaces model supports a number of design patterns, master-worker being probably the most common. The combination of Jini and JavaSpaces addresses a wide range of distributed systems requirements, from simple caching through to full-blown service oriented architecture (SOA).
Jini belongs in the virtual toolbox of anyone building distributed systems in Java.
Source Code
The full source code is available as a tarball. The lookup utilities are in a separate tarball.
Recommended Links
- Jini.org
- The Apache River Project.
- Jan Newmarch's Jini Tutorial.
- Jiniology, a series of articles on Jini and JavaSpaces hosted by Artima.
- Keith Edward's excellent discussion of Jini codebase issues.
- Dan Creswell's Blitz, an Open Source implementation of JavaSpaces.
-
GigaSpaces XAP, a
commercial product with its roots in Jini and JavaSpaces.
Full disclosure: I work for GigaSpaces. - OpenSpaces.org, GigaSpaces' community site with many practical examples of Space-Based Architecture.
Please contact me by email if you have any questions, comments, or suggestions.