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:

  1. The network is reliable
  2. Latency is zero
  3. Bandwidth is infinite
  4. The network is secure
  5. Topology doesn't change
  6. There is one administrator
  7. Transport cost is zero
  8. The network is homogeneous
Deutsch noted "All prove to be false in the long run and all cause big trouble and painful learning experiences."

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:

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:

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:

All of these methods take a transaction context as an optional parameter. The JavaSpaces05 specification adds support for batch operations.

(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's get and set methods, you would see it describes a field. get and set would have to act exactly like a field. Therefore, we asked ourselves, why should we have get and set methods 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 their get and set methods. 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:

Functionally, the example is uninteresting — the workers just sleep for random periods of time.

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:

  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()
      {
      }
  }
Aside from generating random sleep times, it does nothing. SleepResult.java is even simpler:
  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:

  space().write(new SleepTask(masterID_,taskID++),null,Lease.FOREVER);
The 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:

  SleepResult result = null;
  SleepResult template = new SleepResult();
  template.masterID = masterID_;
  . . .
  result = (SleepResult)space().take(template,null,Long.MAX_VALUE);
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 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:

  SleepTask task = null;
  SleepTask template = new SleepTask();
  . . .
  task = (SleepTask)space().take(template,transaction,Long.MAX_VALUE);
The primary difference is the use of a transaction, which is created from the Jini transaction manager:
  Transaction.Created created
    = TransactionFactory.create(transactionManager(),
                                TRANSACTION_LEASE_TIME);
  return created.transaction;
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 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:

where $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:

  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}"
Before deploying the Jini components, you'll need to set 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:

  1. start-httpd.csh
    Start the class server.
  2. start-reggie.csh
    Start the Jini lookup service.
  3. start-mahalo.csh
    Start the Jini transaction service.
  4. start-outrigger.csh
    Start a JavaSpace.
With these core Jini services running, you can try out the example.

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:

  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
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 security-off.policy.

The script to run the SleepMaster is very similar:

  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
and is subject to the same caveats with respect to production. This example writes ten 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.

Please contact me by email if you have any questions, comments, or suggestions.

www.softwarematters.org