JAXenter.de

Das Portal für Java, Architektur, Cloud & Agile
X

Schon abgestimmt im Quickvote? Docker versus Rocket - wer hat Recht?

Das Modell zum Objektaustausch: JavaSpaces vorgestellt

JavaSpaces und ihr Platz im Enterprise Java Universum

Dr. Gerald Löffler

JavaSpaces definiert ein Modell zum Austausch von Objekten in verteilten Java-Applikationen. Wir stellen JavaSpaces kurz und praxisbezogen vor und diskutieren die architektonischen Gemeinsamkeiten, Unterschiede und Berührungspunkte von JavaSpaces und anderen Enterprise Java-Technologien, insbesondere solchen aus dem J2EE-Umfeld.

Die derzeitige Praxis von Enterprise Java Computing ist eindeutig durch J2EE dominiert, das mit EJBs (Enterprise JavaBeans) und JMS (Java Messaging Service) Distributed Computing als Remote Method Calls (bei EJB Session Beans) bzw. Verschicken von Messages (bei JMS und EJB Message Driven Beans) interpretiert. Im Gegensatz dazu propagiert ein vergleichsweise kleiner Kreis von Softwareexperten seit vielen Jahren hartnäckig ein Modell, bei dem verteilte Applikationen dadurch kommunizieren, dass sie Objekte in einen gemeinsamen Bereich " eben den JavaSpace " stellen bzw. von dort entnehmen. Der einst strikte Gegensatz zwischen diesen beiden Ansätzen beginnt sich gerade dadurch aufzulösen, dass JavaSpaces-Implementierungen via J2EE Connector Architecture in J2EE-Applikationen eingebunden werden können. Diese wichtige neue Entwicklung öffnet JavaSpaces zusehends für den Mainstream der Softwareentwicklung in Java. Wir wollen hier JavaSpaces kurz vorstellen und beschreiben, welche Vor- und Nachteile die Verwendung einer JavaSpaces-Implementierung in einer verteilten Java-Applikation hat. Darüber hinaus beschreiben wir ausführlich, wie sich alternative Technologien mit JavaSpaces vergleichen.

JavaSpaces [1] wurde von Sun Microsystems basierend auf den Konzepten von Linda
[2] spezifiziert. JavaSpaces wurde im Rahmen von Jini [3], [4], [5], [6] definiert,
doch wir wollen uns hier auf JavaSpaces als solche konzentrieren und werden deshalb
nur jene Jini-Technologien wie etwa Lookup und Transaktionen, erwähnen, die für
die Verwendung von JavaSpaces relevant sind. Sun stellt eine Referenz-Implementierung
von JavaSpaces und allen anderen Jini-Technologien inkl. Sourcecode zur Verfügung
(siehe Kasten: Jini 2.0 versus 1.2.1). Wichtig ist auch, dass es unabhängige,
kommerzielle Anbieter von JavaSpaces-Implementierungen gibt. Unseres Wissens nach
werden, neben der Referenzimplementierung von Sun mit Namen Outrigger [7], ein
freies Produkt von JPower [8] sowie kommerzielle Produkte von den Firmen GigaSpaces
[9] und Intamission [10] angeboten. Dieser Artikel stellt ganz bewusst keinen
Vergleich zwischen diesen JavaSpaces-Implementierungen dar, sondern erwähnt nur
allgemein, welche erweiterten Features in kommerziellen Implementierungen zu finden
sind. Es bleibt dem Leser überlassen, zu evaluieren, ob ein bestimmtes Produkt
ein gewünschtes Feature in der benötigten Form anbietet.

Jini 2.0 versus 1.2.1
Mitte 2003 wurde von Sun Microsystems
das Release 2.0 der Jini-Technologien veröffentlicht. Jinis Weiterentwicklung
von Version 1.2.1 auf 2.0 ist wesentlich und fundamental, betrifft aber nicht
die JavaSpaces-Spezifikation als solches. Die wesentlichen Neuerungen in Jini
2.0 sind die Abstraktion des Kommunikationsprotokolls und die Einführung einer
erweiterten Security-Architektur. Das Kommunikationsprotokoll zwischen Jini Services
bis Jini 1.2.1 war ausschließlich JavaRMI, ab 2.0 ist mit Jini Extensible Remote
Invocation (Jini ERI) das API vom Kommunikationsprotokoll abstrahiert, sodass
Jini nun auch auf anderen Protokollen wie z.B. SOAP aufsetzen kann. Die Erweiterung
der Security-Architektur in Jini 2.0 geht über simple Authentication und Authorization/Access
Control hinaus und adressiert Fragen wie: Wie kann ein Client sicherstellen, dass
er einem Proxy - d.h. einer Referenz auf einen Jini Service wie z.B. einem JavaSpace
- vertrauen kann? Wie kann ein Client einem Proxy gezielt Permissions geben, etwa
eine AuthenticationPermission? Diese erweiterte Security-Architektur ist ein weiterer
wesentlicher Schritt in Richtung einer Tauglichkeit von Jini für Mainstream Enterprise
Applikationen. Diese Erweiterungen in Jini 2.0 basieren auf der Arbeit die für
die JSRs (Java Specification Requests) 76 und 78 gemacht wurde. Kommerzielle JavaSpaces-Implementierungen,
die bereits die Features von Jini 2.0 unterstützen, sind zur Zeit noch nicht erhältlich,
d.h. die einzige 2.0-konforme JavaSpaces-Implementierung ist derzeit die Referenz-Implementierung
von Sun.



Ein JavaSpace ist ein JavaRMI-basierter Service, der
es seinen Clients erlaubt, Objekte in den JavaSpace zu schreiben (write()), aus
dem JavaSpace zu lesen (read()) und aus dem JavaSpace zu entfernen (take()). (Ab
Jini 2.0 ist das Protokoll zwischen Jini Services nicht mehr auf JavaRMI beschränkt.)
Des Weiteren kann der Client einen Event Listener im JavaSpace registrieren (notify()),
der bei allen zukünftigen Schreibvorgängen, die einem Auswahlkriterium entsprechen,
vom JavaSpace aufgerufen wird. Ein JavaSpace kann als eine Form von Virtual Shared
Memory gesehen werden. Oder einfach als schwarzes Brett, an das jeder seine Nachrichten
heften kann und von dem sie auch jeder wieder entfernen kann. Abpictureung 1 illustriert
diese fundamentalen Operationen an einem JavaSpace.



Abb. 1: Fundamentale Operationen durch verteilte Applikationen an einem JavaSpace (Quelle: Sun Microsystems)

Wir wollen zum Einstieg ein typisches Szenario für den Einsatz von JavaSpaces
beschreiben, das wir an ein reales Projekt eines Kunden von Sun Software Services
angelehnt haben, in das wir involviert waren. Es handelt sich hierbei um die Implementierung
eines Hausüberwachungssystems: In einem Gebäude sind zahlreiche Sensoren verteilt,
die in ihrer Umgebung die Luft-Temperatur messen oder Bewegung registrieren. Jeder
Sensor ist an das LAN des Hauses angeschlossen, zu dem auch ein JavaSpaces-Server
und zwei Compute-Server gehören. Die Compute-Server sind Clients des JavaSpace.
Des Weiteren sind elektronisch steuerbare Türschlösser und Sirenen ebenfalls an
dieses LAN angeschlossen. Zehn mal pro Sekunde registriert jeder Sensor seinen
Messwert (Temperatur, Bewegung) und schreibt ihn als Objekt in den JavaSpace.
Auf den Compute-Servern laufen identische Java-Applikationen, die in einer Endlosschleife
innerhalb einer Transaktion:

  • Messwert-Objekte aus dem JavaSpace lesen und entfernen,
  • Statistiken und Heuristiken auf Basis der Messwerte berechnen, die anzeigen sollen, ob ein Alarmfall wie Brand, Einbruch etc. eingetreten ist,
  • im Alarmfall Steuerungsobjekte in den JavaSpace schreiben, die letztlich dazu führen, dass Türschlösser versperrt und Sirenen betätigt werden (weil jedes Türschloss und jede Sirene einen Event Listener auf es selbst betreffende Steuerungsobjekte im JavaSpace registriert hat),
  • und eine Objekt-Repräsentation des gesamten Gebäudes aktualisieren, die ebenfalls im JavaSpace abgelegt ist, sodass der gesamte momentane und historische Zustand des Gebäudes jederzeit aus dem JavaSpace ausgelesen werden kann.

In diesem Beispiel wird der JavaSpace
also als transaktionales Virtual Shared Memory verwendet und erhöhter Durchsatz
(Dies ist ein Beispiel für Parallel Processing in Form von Master/Worker Parallelismus)
und Ausfallssicherheit durch den Einsatz von mehreren Compute-Servern erreicht.
Der Einsatz einer geeigneten JavaSpaces-Implementierung macht den JavaSpace selbst
auch ausfallssicher.

Die Grundlagen

Das Interface JavaSpace ist wie in Listing
1 definiert.



Listing 1

package net.jini.space;

// imports...

public interface JavaSpace {
Lease write(Entry entry, Transaction tx, long lease)
throws TransactionException, RemoteException;

Entry read(Entry template, Transaction tx, long timeout)
throws UnusableEntryException, TransactionException, InterruptedException,
RemoteException;

Entry take(Entry template, Transaction tx, long timeout)
throws UnusableEntryException, TransactionException, InterruptedException,
RemoteException;

EventRegistration notify(Entry entry, Transaction tx, RemoteEventListener remoteeventlistener, long lease, MarshalledObject marshalledobject)
throws TransactionException, RemoteException;

//...
}

Alle JavaSpace-Methoden können RemoteException werfen, weil ein Objekt, welches das Interface JavaSpace implementiert, ja im Allgemeinen eine Referenz auf einen in einer anderen JVM laufenden Service darstellt. Manche kommerziellen JavaSpaces-Implementierungen können in der gleichen JVM wie die Applikation gestartet werden, sodass der Overhead der Remote Calls in den JavaSpace entfällt.


Objekte die im JavaSpace abgelegt werden sollen - im Folgenden Entries genannt " müssen das Marker Interface (Ein Marker Interface definiert keine Methoden) net.jini.core.entry.Entry implementieren und serialisierbar sein. Die Felder von Entries müssen außerdem public sein, dürfen nur auf Objekte verweisen und die Entries benötigen einen public Default Constructor. All dies stellt eine ziemliche Einschränkung dar und macht Entries zu nur sehr eingeschränkt nutzbaren Objekten. Mit anderen Worten: Objekte des Domain Models einer Applikation können im Allgemeinen nicht direkt an einen JavaSpace übergeben werden, sondern Entries sind als spezialisierte Kommunikationsobjekte zu verstehen, ähnlich zu JMS-Messages, aber anders als JDO persistence-capable Classes (Listing 2).



Listing 2

// imports...

public class ObservedObject implements Entry {
public Integer id;
public String  description;
public Integer roomID;
public Date    lastUpdate;

public ObservedObject() {} // required by spec

public ObservedObject(int id) {
this.id = new Integer(id);
}
}

Es hängt von der JavaSpaces-Implementierung ab, welchen Grad an Persistenz Entries erlangen, die in einen JavaSpace geschrieben werden - die Spezifikation schweigt hierzu. Übliche Möglichkeiten sind:

  • Halten in Memory in einer JVM: bei der Referenz-Implementierung und allen kommerziellen Implementierungen,
  • Halten in Memory in einem Cluster von mehreren JVMs, entweder partitioniert oder repliziert: bei manchen kommerziellen Implementierungen,
  • Synchrones oder asynchrones Schreiben in persistenten Speicher wie z.B. ein File oder eine relationale Datenbank und meist zusätzliches Halten in Memory, das dann als Cache dient: bei manchen kommerziellen Implementierungen.

Da die Kenntnis des exakten Verhaltens einer JavaSpaces-Implementierung bei der Entwicklung einer Applikation naturgemäß essentiell ist und die JavaSpaces-Spezifikation dieses breite Spektrum an Möglichkeiten zulässt, müssen die zur Auswahl stehenden Implementierungen einem genauen Vergleich mit den Anforderungen eines Projektes unterzogen werden.


Wenn ein Entry mit write() in einen JavaSpace geschrieben wird, so werden alle Felder des Objekts separat serialisiert, wobei, gemäss den Regeln der Java-Serialisierung, für jedes Feld ein isolierter Objekt-Graph entsteht. Das so entstandene Abpicture des Entry wird im JavaSpace abgelegt. Dieses wichtige und nicht-intuitive Verhalten muss beim Umgang mit dem JavaSpace berücksichtigt werden, soll hier aber nicht weiter diskutiert werden, weil es in der Praxis auch kein besonderes Problem darstellt. Das folgende Code-Beispiel schreibt ein ObservedObject (außerhalb einer Transaktion, mit unbegrenzter Lease - siehe unten) in einen JavaSpace (Listing 3).



Listing 3

// ...
JavaSpace js = getJavaSpace();
Entry     oo = new ObservedObject(43);
try {
js.write(oo, null, Lease.FOREVER);
} catch (Exception e) {
// couldn't write to JavaSpace
}
// ...

JavaSpaces unterstützen immer Transaktionen, wobei die Definition der entsprechenden Interfaces für die Transaktion selber, alle Teilnehmer (Resources) an der Transaktion, den Transaction Manager etc. durch Jini vorgegeben ist. Diese Jini-Transaktionen sind deshalb unabhängig von den auf XA und TX basierenden J2EE-Standards JTS und JTA ist. In der Welt der verteilten Transaktionen standardisiert X/Open DTP mit XA das Interface zwischen Transaction Manager und Resource Manager und mit TX das Interface zwischen Applikation und Transaction Manager. Corba OTS definiert zusätzlich ein objektorientiertes Interface für XA und TX. JTS ist die Java-Variante von OTS. JTA (javax.transaction) ist das für den Applikationsprogrammierer relevante API für begin/commit/rollback von Transaktionen.


(Die Wechselwirkung zwischen J2EE und JavaSpaces wird an späterer Stelle noch genauer beleuchtet.) Jini-Transaktionen sind verteilte Transaktionen, inkl. Two-Phase Commit, an denen jedoch in erster Linie nur solche Ressourcen teilnehmen können, die explizit dieses Jini-Transaktions-System unterstützen. Letzteres ist für alle JavaSpaces-Implementierungen zwingend der Fall, aber für andere gängige Ressourcen, wie z.B. Datenbanken, natürlich nicht. Eine Referenz auf das Transaktions-Objekt muss an alle Methoden des JavaSpace-Interface explizit übergeben werden. Wenn diese Referenz null ist, läuft die Methode als eigenständige atomare Einheit. Der Client, der die Transaktion beginnt, muss dies unter expliziter Angabe eines Transaction Managers tun. Wie man eine Referenz auf einen Transaction Manager erhält, wird weiter unten erläutert. Die ACID-Properties - Atomicity, Consistency, Isolation, Durability - von Jini-Transaktionen mit JavaSpace-Operationen gelten wie erwartet, allerdings unter den vorhin genannten Einschränkungen bezüglich der Durability bei manchen JavaSpaces-Implementierungen (Memory versus persistenter Speicher). Des Weiteren entspricht der Transaction Isolation Level einer JavaSpace-Operation am Ehesten dem, was man bei Datenbanken Serializable nennen würde " und er kann auch nicht verändert werden. Der Code in Listing 4 schreibt (mit unbegrenzter Lease - siehe unten) innerhalb einer Transaktion (mit 10 Sekunden Lease) einen Entry in entweder zwei JavaSpaces oder keinen einzigen.



Listing 4

// ...
JavaSpace   js1 = getJavaSpace();
JavaSpace   js2 = getJavaSpaceFallback();
Entry       oo  = new ObservedObject(43);
Transaction tx  = null;
try {
TransactionManager tm  = getTransactionManager();
tx = TransactionFactory.create(tm, 1000*10).transaction; // 10s TX lease
} catch (Exception e) {
// couldn't create/begin transaction
}
try {
js1.write(oo, tx, Lease.FOREVER);
js2.write(oo, tx, Lease.FOREVER);
tx.commit();
} catch (Exception e) {
// coudln't write to both JavaSpaces - rollback
try {
tx.abort();
} catch (Exception e) {
// couldn't rollback transaction
}
}
// ...

Eine ungewöhnliche Eigenschaft von JavaSpaces ist die Verwendung von Leases wie sie in der Jini-Welt üblich sind: Die Methode write() nimmt einen Parameter vom Typ long, der die gewünschte Überlebensdauer des Entry im JavaSpace angibt. Ähnliches gilt für die Methode notify(). Auch der Wunsch nach einer unendlichen Lebensdauer kann mit net.jini.core.lease.Lease.FOREVER ausgedrückt werden. Nach Ablauf der Lease-Zeit darf der JavaSpace den Entry löschen. Jede JavaSpaces-Methode gibt ein Objekt des Typs net.jini.core.lease.Lease zurück, dessen Methoden vor Allem zum Verlängern (renew()) und vorzeitigen Löschen (cancel()) des Lease verwendet werden können. Das Lease-Konzept ist ein inhärenter Bestandteil von Jini, der dazu führen soll, sich der zeitlichen Beschränkung des Zugangs zu Ressourcen in verteilten Applikationen explizit anzunehmen. Gerade im Umgang mit JavaSpaces wird allerdings in der Praxis sehr gerne eine lange oder unendliche Lease-Zeit verwendet, weil dies eher der Idee eines deterministischen Virtual Shared Memory entspricht.


Leases finden auch bei der Erzeugung von Transaktionen, wie in obigem Code-Beispiel im Aufruf von TransactionFactory.create() zu sehen, Verwendung und steuern dort ähnlich einem Transaction Timeout die Lebenszeit einer Transaktion. Aus Gründen der Skalierbarkeit und der Liveness einer Applikation sollte diese Zeit so kurz wie möglich gehalten werden.


Das Finden von Entries mit read() oder take() im JavaSpace erfolgt nicht durch eine Query, wie etwa in SQL, sondern im Sinne von Query by Example durch Angabe eines Template, das ebenfalls vom Typ Entry ist. Ein Match zwischen einem Entry und dem Template liegt dann vor, wenn erstens der Typ des Entry dem Typ des Template zugewiesen werden kann (der Entry muss die selbe Klasse wie das Template oder eine Sub-Klasse des Template haben) und wenn zweitens alle nicht-null Felder des Template mit denen des Entry identisch sind. Hierbei ist identisch tückischer aber logischer Weise nicht über equals(), sondern als identische serialisierte Repräsentation definiert. Eine Suche mit einem Template liefert immer maximal einen einzigen, beliebigen, passenden Entry, wobei die Return Values von aufeinanderfolgenden Suchen in keiner Beziehung zueinander stehen. Mache kommerziellen Implementierungen bieten deshalb erweiterte Methoden an, die alle zu einem Template passenden Entries zurückliefern. Dass dieser Template-Mechanismus kein Ersatz für eine richtige Query-Sprache ist, liegt auf der Hand. Listing 5 liest (außerhalb einer Transaktion; mit timeout 0, d.h. ohne zu warten) ein beliebiges ObservedObject mit ID = 43 aus dem JavaSpace.



Listing 5

// ...
JavaSpace js       = getJavaSpace();
Entry     template = new ObservedObject(43);
try {
ObservedObject oo = (ObservedObject) js.read(template, null, 0); // no wait
if (oo != null) {
// found ObservedObject with ID = 43
}
} catch (Exception e) {
// couldn't read from JavaSpace
}
// ...

JavaSpaces kennen das Konzept eines Object Identifiers oder Primary Keys nicht. Das bedeutet, dass mit write() beliebig viele identische Entries in einen JavaSpace geschrieben werden können. Natürlich kann den Entry-Klassen ein ID-Feld hinzugefügt werden, so wie z.B. der Klasse ObservedObject oben, doch der Constraint, dass immer maximal ein Objekt in einem JavaSpace mit einer bestimmten ID existieren darf, muss explizit programmiert werden. Dies stellt eine in der Praxis lästige Erschwernis im Umgang mit JavaSpaces dar.


Die notify()-Methode registriert einen Event Listener, also eine Art Callback Handler, mit dem JavaSpace, der in Folge vom JavaSpace immer dann aufgerufen wird, wenn ein zu dem bei notify() angegebenen Template passender Entry in den JavaSpace geschrieben wird. Der Aufruf des Event Listener ist im Allgemeinen ein Remote Call und deshalb, obwohl oft sehr nützlich, ein potentielles Performance- und Skalierbarkeits-Risiko.


Das Verändern eines Entry im JavaSpace wird nicht durch eine eigene Methode unterstützt, sondern muss durch ein take() und ein darauffolgendes write() simuliert werden, wobei letzteres natürlich eine Event Notification auslösen kann. Manche kommerziellen JavaSpaces-Produkte bieten Methoden für das Verändern eines bestehenden Entry als Erweiterung an.

Jini Lookup Service und andere Registries

Um mit einem JavaSpace arbeiten zu können muss dem Client eine Referenz auf einen JavaSpace, d.h. ein Objekt welches das Interface JavaSpace implementiert, zur Verfügung stehen. Gleiches gilt für den Transaction Manager um Transaktionen verwenden zu können. Zum Registrieren und Auffinden von Services wie JavaSpace und Transaction Manager definiert Jini eine eigene Registry, den Lookup Service, der üblicher Weise Teil jeder JavaSpace-Installation ist. In einer Jini-Umgebung ist die Verwendung des Lookup Service eine Selbstverständlichkeit und JavaSpace und Transaction Manager sind dort nur zwei von vielen Services, die sich im Lookup Service registrieren. In einer Nicht-Jini-Umgebung muss eine Referenz auf ein Service aber nicht zwangsläufig im Jini Lookup Service abgelegt werden, sondern kann z.B. auch in eine RMI-Registry oder ein LDAP-Directory geschrieben werden. Dies ist vor allem dann wichtig, wenn JavaSpaces in einem J2EE-Umfeld verwendet werden, weil jeder J2EE Application Server bereits seine, über JNDI anzusprechende, Registry enthält und die Einführung einer zusätzlichen Registry nur für die JavaSpaces-Umgebung architektonisch unerwünscht wäre. Ein Jini-Service registriert sich beim Startup bei einer Registry - und diese ist per Default jedenfalls der Jini Lookup Service. Manche kommerziellen JavaSpaces-Implementierungen können sich auch in einer anderen Registry, insbesondere der RMI-Registry, eintragen, doch die Registrierung in einer von der Implementierung nicht unterstützten Registry, wie z.B. ein LDAP-Directory, müsste explizit programmiert werden. Kommerzielle JavaSpaces-Implementierungen unterstützen in der Regel ein stark vereinfachtes, aber proprietäres API für den Lookup eines Service in einer Registry. Z.B. wird bei GigaSpaces die gesamte notwendige Information für das Auffinden eines JavaSpace in einer URL zusammengefasst, die der Helfer-Klasse SpaceFinder übergeben wird. Diese URL enthält im Wesentlichen den Registry-Typ (Jini Lookup Service oder RMI-Registry), den Registry-Host (Hostname oder Multicast) und den Namen des JavaSpace (Listing 6).
Ähnlich einfach ist bei GigaSpaces das Auffinden eines Transaction Manager gelöst " hier via Jini Lookup Service, mit Timeout nach 10 Sekunden (Listing 7)



Listing 6

// ...
private JavaSpace getJavaSpace() {
try {
return (JavaSpace) SpaceFinder.find(
"jini://lookup-service-host.local.com/*/space-name");
} catch (Exception e) {
// couldn't find JavaSpace
return null;
}
}
// ...

Listing 7

// ...
private TransactionManager getTransactionManager() {
try {
return (TransactionManager) LookupFinder.find(null,
TransactionManager.class, "lookup-service-host.local.com", 1000*10);
} catch (Exception e) {
// couldn't find Transaction Manager
return null;
}
}
// ...

Bei entsprechender Kapselung und Lokalisierung dieser produktspezifischen API-Calls, kann damit das Auffinden von Services sehr vereinfacht werden ohne die Applikation als Gesamtes zu produktabhängig zu machen.

JavaSpaces und J2EE

Vom Standpunkt einer J2EE-Applikation, wie etwa einer Java Web-Applikation, gesehen ist ein JavaSpace eine besondere Art von transaktionaler Ressource, auf welche die J2EE-Applikation aus dem Application Server heraus zugreift. Für manche Arten von Ressourcen, allen voran relationale Datenbanken, ist dieser Zugriff in den J2EE-Spezifikationen explizit geregelt. Für die übrigen Ressourcen, wie z.B. ERP-Systeme (SAP R/3) oder eben auch JavaSpaces, sieht J2EE den Zugriff via J2EE Connector Architecture vor. Gemäss J2EE Connector Architecture muss der Hersteller einer JavaSpaces-Implementierung eine sogenannten Resource Adapter implementieren, der es dann jedem Application Server erlaubt, den Zugriff auf den JavaSpace zu steuern. Die J2EE Connector Architecture befasst sich im Wesentlichen mit Connection Pooling, Transaction Management und dem Security Context. All diese Aspekte der Verwendung einer externen Ressource sollten dann aus Sicht des Entwicklers wie in J2EE üblich funktionieren.


GigaSpaces 3.0 ist unseres Wissens nach die erste JavaSpaces-Implementierung, die einen Resource Adapter (gemäss J2EE Connector Architecture 1.0) zur Verfügung stellt. Da GigaSpaces keine XA-Transaktionen, sondern nur Jini-Transaktionen unterstützt, sind verteilte Transaktionen über den JavaSpace und andere transaktionale Ressourcen, die von der J2EE-Applikation verwendet werden, nicht möglich. Im Deployment Descriptor des JavaSpace Ressource Adapters werden alle Details zum Auffinden des JavaSpace angegeben (vor Allem die Space-URL, die in obigem Code-Beispiel an die SpaceFinder Helfer-Klasse übergeben wurde). Eine J2EE-Komponente, die Zugriff auf den JavaSpace benötigt, braucht dann nur über einen in J2EE üblichen JNDI-Lookup eine Referenz auf den JavaSpace beziehen und muss sich nicht um JavaSpace-spezifische Aspekte wie das Nachsehen im Jini Lookup Service kümmern. Der Resource Adapter verwendet intern in Folge allerdings die üblichen Mechanismen zum Lookup des JavaSpace im Jini Lookup Service oder einer RMI-Registry, d.h. diese Services müssen trotzdem verfügbar sein. Da in J2EE Referenzen auf die momentan aktive Transaktion implizit als Thread-lokale Variable weitergereicht werden, bei den JavaSpace-Methoden die Transaktion aber immer explizit übergeben wird, musste GigaSpaces das JavaSpaces-API diesbezüglich anpassen. Mit anderen Worten: Wenn man auf einen JavaSpace über J2EE Connector Architecture zugreift, programmiert man (zwangsläufig) gegen eine proprietäre Variante des JavaSpaces-API. Nach unserem Wissen finden die Security Context Aspekte der Connector Architecture im GigaSpaces Resource Adapter keine Verwendung.


Listing 8 ist an ein weiter oben vorgestellte Beispiel angelehnt und schreibt ein ObservedObject mit unbegrenzter Lease in einen JavaSpace. Transaktionsklammern werden hier zu Illustrationszwecken explizit via JTA gesetzt, aber auch Container-Managed Transaction Demarcation (d.h. deklarative Transaktionssteuerung) wäre natürlich möglich. Dieser Code könnte in dieser Form z.B. in einem Servlet oder einer Session Bean mit Bean-Managed Transaction Demarcation verwendet werden:



Listing 8

// ...
Context           ct = new InitialContext();
UserTransaction   tx = (UserTransaction) ct.lookup("java:comp/UserTransaction");
ConnectionFactory cf = (ConnectionFactory) ct.lookup("java:eis/JavaSpace");
Connection        c  = cf.getConnection();
JSInteraction     js = (JSInteraction) c.createInteraction();
Entry             oo = new ObservedObject(43);
try {
tx.begin();
js.write(oo, Lease.FOREVER); // TX implicitly passed
tx.commit();
} catch (Exception e) {
// couldn't write to JavaSpace - rollback
try {
tx.rollback();
} catch (Exception e) {
// couldn't rollback transaction
}
}
// ...

Hierbei sind die Klassen ConnectionFactory und Connection durch die J2EE Connector Architecture definiert, UserTransaction durch JTA und JSInteraction ist eine GigaSpaces-spezifische Klasse.

Alternative Technologien

Im Folgenden wollen wir JavaSpaces mit gängigen Enterprise Java Technologien vergleichen, nicht nur um JavaSpaces besser zu verstehen und mögliche Einsatzgebiete von JavaSpaces aufzuzeigen, sondern auch, um etwaige Alternativen zu JavaSpaces zu nennen. Notwendiger weise ist diese Übersicht ungenau, weil jede der genannten Produktkategorien für sich inhomogen ist. Trotzdem ist es sinnvoll, die wesentlichen Unterschiede und Gemeinsamkeiten zwischen JavaSpaces-Implementierungen und konkurrierenden Technologien verallgemeinernd aufzuzeigen. Die folgende Tabelle fasst diesen Vergleich in Stichworten zusammen. Für jede alternative Technologie wollen wir kurz Skizieren, wie das oben eingeführte Beispiel einer Hausüberwachung umgesetzt werden könnte (Tab. 1).

Feature JavaSpaces Klassische Datenbanken In-Memory Datenbanken Message-Oriented Middleware Cluster-fähige Java Caches
Persistenz produktabhängig (grobgranular) ja nein ja (feingranular) produktabhängig
lokale Transaktionen Standard: nein, manche Produkte: ja ja ja ja nein (meist nur Locking)
verteilte Transaktionen ja (nur Jini-Trans.) ja (XA) ja (XA) ja (XA) nein
Abfragen Template-basiert SQL SQL SQL-basierte Selektoren nein
Zeitaufwand für Zugriff Erst Remote Call (manche Produkte: auch Local Call),
dann schnell
Erst meist Netzwerkzugriff, dann relativ langsam Erst meist Netzwerkzugriff, dann schnell Optimierter Netzwerkverkehr, relativ schnell stark produkt- und konfigurationsabhängig, meist schnell
Mapping von Objekten erforderlich ja bei RDBMS ja, bei ODBMS nein bei RDBMS ja, bei ODBMS nein ja nein
Asynchrone Notification ja (Event Notification) mit starken Einschränkungen ja (Triggers) mit starken Einschränkungen ja (Triggers) ja ja
Standardisierung ja, aber nicht ganz ausreichend ja, meist ausreichend ja, meist ausreichend ja, meist ausreichend nein
Clustering möglich möglich möglich möglich per definitionem: ja
verfügbare Implementierungen wenige viele wenige viele wenige (aber steigend)

Datenbanken (relationale und objekt-orientierte) bieten garantierte Persistenz, bei JavaSpaces ist dies nur bei manchen Implementierungen der Fall. Beide Technologien unterstützen Transaktionen, allerdings halten sich Datenbanken hierbei normaler weise an gängigere Standards (XA/JTS/JTA) als JavaSpaces (Jini-Transaktionen), was die Interoperabilität mit anderen transaktionalen Ressourcen beeinflusst. Datenbanken unterstützen richtige Abfragesprachen, JavaSpaces nur Query by Example. Zugriffe auf Datenbanken dauern üblicher Weise deutlich länger als auf Memory-basierte JavaSpaces-Implementierungen. Clustering wird von Produkten in beiden Kategorien unterstützt. Das Mapping von Objekten in relationale Datenbanken und in einen JavaSpace bedarf expliziten - aber vollkommen unterschiedlichen - Aufwand, der bei objekt-orientierten Datenbanken entfällt. Manche JavaSpaces-Implementierungen können in derselben JVM wie die Applikation gestartet werden, was bei Datenbanken unüblich (aber bei manchen Produkten möglich) ist. Die Event Notification bei JavaSpaces erinnert an Datenbank-Triggers, ist aber deutlich davon zu unterscheiden. Die Zahl an kommerziellen und freien Datenbank-Produkten ist enorm, die Zahl an JavaSpaces-Implementierungen überschaubar. Für das Hausüberwachungssystem bedeutet dies:

  • Das Ablegen der Messwerte durch die Sensoren erfolgt in eine Datenbank anstelle des JavaSpaces. Solange die Datenbank den anfallenden Datenfluss bewältigen kann, ist dies eine gute Alternative.
  • Die Compute-Server Lesen aus der Datenbank, ziemlich sicher mittels Polling, was eventuell zu einer hohen Zahl unnötiger Datenbankzugriffe und zu einem wenig eleganten Programmiermodell führt.
    Für die Steuerung der Türschlösser und Sirenen ist die Datenbank nicht hilfreich, es sei denn man akzeptiert das permanente Polling von vielen Türschlössern und Sirenen gegen die Datenbank.
  • Die Objekt-Repräsentation des Gebäudes kann leicht in einer Datenbank persistent gehalten werden.

Message-Oriented Middleware (MOM, z.B. [12], [13], [14]), d.h. durch JMS (Java Messaging Service) angesprochene Queues und Topics, stellt eine reife, hoch-performante und -skalierbare Technologie dar. Messages werden vom Message Producer an eine Destination (Queue oder Topic) in der MOM geschickt, die diese dann selbsttätig an den Message Consumer weiterleitet. Ähnlich zu JavaSpaces findet eine vollkommene Entkopplung zwischen verteilten Applikationen statt. MOM unterstützt in der Regel Transaktionen via XA/JTS/JTA und fügt sich damit gut in das J2EE-Umfeld. Die Verteilung von Messages über MOM kann einem ähnlichen Zweck dienen wie die Notification über einen neuen Entry in einem JavaSpace, jedoch ist die Reihung der Zustellung von Messages über MOM wohl-definiert (die Reihenfolge ist für jene Messages garantiert, die von einem Message Procucer in einer Session verschickt wurden) und in der Praxis brauchbarer als die Reihenfolge in der ein JavaSpace passende Event Listeners aufruft. Darüber hinaus ist eine JavaSpaces Event Notification per definitionem ein Remote Call via JavaRMI, während einer MOM bei der Zustellung von Messages einige Optimierungsmöglichkeiten offen stehen, die von den Implementierungen in der Regel auch genutzt werden. Bei MOM kann man auf der Ebene von Destinations oder auch einzelnen Messages den Grad an Persistenz auswählen, während bei jenen JavaSpaces-Implementierungen, die Persistenz unterstützen, diese für den gesamten JavaSpace aktiviert wird. MOM dient allerdings nur dem Verteilen von Messages und ist keine allgemeine Persistenz-Technologie: eine persistente Message wird von der MOM nur so lange in persistentem Speicher gehalten, bis sie zugestellt werden konnte. Hat ein MOM-Client eine Message erhalten und den Erhalt bestätigt, dann kann er diese Message nicht erneut von der MOM anfordern. Auf Grund der Reife von MOM-Produkten ist im Allgemeinen sowohl Performance als auch Skalierbarkeit mindestens so gut wie bei JavaSpaces-Implementierungen. MOM-Clients können die ihnen zugestellten Messages durch die Angabe von SQL-ähnlichen Queries (Message Selector) einschränken, während bei JavaSpaces ein ähnlicher Effekt über Templates bei der Registrierung von Event Listeners erreicht werden muss. Sowohl bei JavaSpaces als auch bei MOM müssen maßgeschneiderte Objekte, d.h. Instanzen von Sub-Klassen von net.jini.core.entry.Entry bzw. javax.jms.Message, an das System übergeben werden. Für das Hausüberwachungssystem bedeutet all dies:

  • Sensoren agieren als Message Producer die ihre Messwerte als Messages an eine Queue schicken. Der Datenfluss kann durch die richtige Produkt-Wahl und die Entscheidung zwischen persistenten und nicht-persistenten Messages sicher bewältigt werden.
  • Die Compute-Server pollen die MOM nach Messwert-Messages oder bekommen dies asynchron von der MOM zugestellt. Dies ist eine Standardanwendung von MOM und in jeder Hinsicht gut unterstützt.
  • Nachdem die Compute-Server ihre Logik wie üblich abgearbeitet haben, schicken sie Steuerungsobjekte an eine Queue, von der sie an die entsprechenden Türschlösser und Sirenen verteilt werden.
  • Für die Persistierung der Objekt-Repräsentation des Gebäudes kann MOM nicht verwendet werden.

Caches (z.B. [15], [16], [17], [18]) sind eine wenig standardisierte Produktgruppe, die in der Regel dazu dient, Objekte in Memory zu halten, um ihre Erzeugung, z.B. durch Lesen aus einer Datenbank oder Berechnung, zu vermeiden. Den einzigen, ungenügenden und unfertigen Ansatz zur Standardisierung des APIs zu Caches stellt JCache (JSR 107, [19]) dar, das manche dieser Produkte implementieren. Die uns hier interessierenden Vertreter dieser Gruppe haben ausgefeilte Cluster-Features, die in meist konfigurierbarer Weise z.B. jedes Objekt im Cache auf mehrere Maschinen verteilen (zwecks Ausfallssicherheit; replizierter Cache), oder global nur eine Kopie jedes Objekts zulassen (partitionierter Cache) und deshalb Änderungen propagieren. Die Kommunikation zwischen den Nodes eines Cluster ist wie bei JavaSpaces-Implementierungen stark produktabhängig. Caches speichern Name-Value-Pairs, wobei bei verteilten Caches der Value ein serialisierbares Objekt sein muss. Für JavaSpaces gelten die oben ausgeführten zusätzlichen Einschränkungen für Entries. Darüber hinaus bieten manche Caches eine Form von Predicate-basierter Query an und bieten die Möglichkeit, Event Listeners zu registrieren, die beim Schreiben/Ändern/Lesen eines Eintrages im Cache aufgerufen werden - dies auch bei einem verteilten Cache. Die Fähigkeiten im Bereich Clustering und Event Listener eines Caches sind jenen einer fortgeschrittenen JavaSpaces-Implementierung recht ähnlich, wenn sie sich auch im Detail unterscheiden mögen. Predicate-basierte Queries, bei denen beliebiger Java-Code ausgeführt wird, um einen Match festzustellen, gehen über die Möglichkeiten der Templates bei JavaSpaces weit hinaus, sind jedoch auch potentiell langsamer. Caches bieten meist die Möglichkeit, die Eviction Policy, d.h. die Entscheidung wann ein Eintrag aus dem Cache entfernt wird, im Detail zu konfigurieren. Dies stellt eine Verallgemeinerung des Lease-Konzeptes von JavaSpaces dar und wird ansatzweise auch von manchen kommerziellen JavaSpaces-Implementierungen angeboten. Das Hausüberwachungssystem würde mit einem Cache wie folgt umgesetzt werden:

  • Sensoren, Compute-Server, Türschlösser und Sirenen würden alle am gleichen verteilten Cache arbeiten, der auf Grund des Datenaufkommens vermutlich als partitionierter Cache zu konfigurieren wäre.
  • Die Sensoren schreiben Messwerte in den Cache.
  • Die Compute-Server pollen den Cache nach neu geschriebenen Messwert-Objekten und entfernen diese aus dem Cache. Alternativ kann der Compute-Server auch einen Event Listener auf das Erzeugen von Messwert-Objekten im Cache registrieren.
  • Nachdem die Compute-Server ihre Logik wie üblich abgearbeitet haben, schreiben sie Steuerungsobjekte in den Cache.
  • Die Steuerungsobjekte werden von den Türschlösser und Sirenen in regelmäßigen Abständen aus dem Cache gelesen und entfernt (Polling), oder die Türschlösser und Sirenen haben Event Listener auf das Erzeugen von sie betreffenden Steuerungsobjekten im Cache registriert.
  • Die Persistierung der Objekt-Repräsentation des Gebäudes erfolgt ebenfalls durch Schreiben in den Cache, wobei allerdings die Komplikationen eines auf Serialisierung basierenden Verteilungs-Mechanismus beim Umgang mit Objekt-Graphen - ganz ähnlich wie bei JavaSpaces - berücksichtigt werden müssen.

Ein JavaSpace ist ein simpler Service zum Austausch von Objekten in verteilten Applikationen im Sinne eines Virtual Shared Memory. Moderne JavaSpaces-Implementierungen bieten ausgefeilte Clustering-Features und können via J2EE Connector Architecture in J2EE-Applikationen eingebunden werden. Allerdings haben JavaSpaces manche Eigenarten, wie z.B. die Verwendung von Jini-Transaktionen, das Fehlen von Primary Key Constraints und einen Template-basierten Query-Mechanismus, über die man informiert sein muss, bevor man JavaSpaces in einem Projekt einsetzt. JavaSpaces stellen ein weiteres Tool im Werkzeugkasten des Java Architekten dar, das man kennen und bei Bedarf und Eignung unvoreingenommen einsetzen sollte.



Links und Literatur

 

Kommentare

Ihr Kommentar zum Thema

Als Gast kommentieren:

Gastkommentare werden nach redaktioneller Prüfung freigegeben (bitte Policy beachten).