04.10.2013 Aufrufe

Strategien zur automatischen Objektmigration auf Grundlage ...

Strategien zur automatischen Objektmigration auf Grundlage ...

Strategien zur automatischen Objektmigration auf Grundlage ...

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

DIPLOMARBEIT<br />

<strong>Strategien</strong> <strong>zur</strong> <strong>automatischen</strong><br />

<strong>Objektmigration</strong> <strong>auf</strong> <strong>Grundlage</strong><br />

statischer und dynamischer Analyse<br />

Marco Weißenborn<br />

marco.weissenborn@t-online.de<br />

November 2003<br />

Fakultät für Elektrotechnik, Informatik und Mathematik<br />

Institut für Informatik<br />

Arbeitsgruppe<br />

Programmiersprachen und Übersetzer<br />

vorgelegt bei:<br />

Prof. Dr. Uwe Kastens<br />

und<br />

Prof. Dr. Odej Kao


VORWORT<br />

An dieser Stelle bedanke ich mich bei all den lieben Menschen, die mich<br />

während meines Studiums unterstützt haben.<br />

Des Weiteren danke ich allen Menschen, die zum Gelingen dieser Arbeit einen<br />

Teil beigetragen haben und möchte einige von Ihnen namentlich erwähnen.<br />

Zunächst gilt mein Dank Prof. Dr. UWE KASTENS, der durch seine konstruktive<br />

Kritik mir eine große Hilfe bei der Strukturierung der Arbeit war. Besonders<br />

danke ich meinem Betreuer DINH KHOI LE, der mein Interesse an dieser<br />

Diplomarbeit geweckt und mir immer mit Hinweisen und Ratschlägen<br />

weitergeholfen hat. Für die vielen konstruktiven Diskussionen möchte ich mich<br />

bei KARSTEN KLOHS bedanken.<br />

Ein spezieller Dank geht an meine Freundin MARTINA EIBEN, die mich während<br />

der Diplomarbeit motiviert und unterstützt hat.<br />

Diese Diplomarbeit widme ich meinem Großvater Schmiedemeister ERICH<br />

WEISSENBORN, der sehr gern studiert hätte, jedoch wegen der bescheidenen<br />

Verhältnisse nach dem I. Weltkrieg nicht die Gelegenheit bekam. So begann er<br />

eine Schmiedelehre und übernahm nach dem II. Weltkrieg den Handwerksbetrieb<br />

seines Vaters.<br />

ERKLÄRUNG<br />

Hiermit versichere ich, dass ich diese Diplomarbeit selbständig und nur unter<br />

Zuhilfenahme der angegebenen Quellen und Hilfsmittel angefertigt habe. Alle<br />

wörtlich oder inhaltlich zitierten Stellen sind als solche kenntlich gemacht.<br />

_______________________________<br />

Paderborn, den 26. November 2003


KURZFASSUNG<br />

Die verteilte Ausführung rechenintensiver Java-Anwendung<br />

ist mit einer Steigerung der Ausführungsgeschwindigkeit<br />

gegenüber der zentralen Ausführung<br />

verbunden.<br />

Die Steigerung der Ausführungsgeschwindigkeit kann<br />

durch Einsatz von Migration und/oder Replikation in<br />

Abhängigkeit von der Anwendung nochmals gesteigert<br />

werden. Zur Berechnung von Situationen, in denen die<br />

Migration und/oder Replikation von Objekten eine<br />

Steigerung der Ausführungsgeschwindigkeit einbringt,<br />

dienen Migrationsstrategien.<br />

Herkömmliche Migrationsstrategien treffen ihre Entscheidung<br />

über Migration, Replikation oder die Beibehaltung<br />

der Platzierung eines Objektes <strong>auf</strong> <strong>Grundlage</strong><br />

dynamischer Programmanalyse-Ergebnisse.<br />

Diese Diplomarbeit stellt ein Modell vor, das zusätzlich<br />

auch noch die Nutzung von Ergebnissen aus statischer<br />

Programmanalyse ermöglicht. Erste Messungen zeigen,<br />

dass schon bei kleinen Anwendungen durch Migration<br />

und/oder Replikation von Objekten L<strong>auf</strong>zeitvorteile<br />

eintreten.


INHALTSVERZEICHNIS<br />

1 Einleitung........................................................................................................ 1<br />

2 Verwandte Arbeiten....................................................................................... 3<br />

2.1 Verteilte Systeme......................................................................................3<br />

2.1.1 JavaParty........................................................................................... 3<br />

2.1.2 Doorastha.......................................................................................... 4<br />

2.1.3 Juggle................................................................................................ 5<br />

2.1.4 JOchestra...........................................................................................7<br />

2.2 Migrationstrategien...................................................................................8<br />

2.2.1 RepliXa............................................................................................. 8<br />

2.2.2 Replikationsheuristiken für Verteilte Systeme............................... 11<br />

2.3.3 <strong>Objektmigration</strong>sstrategien in Verteilten Systemen....................... 13<br />

2.3.4 Adaptive <strong>Objektmigration</strong> mit Neuronalen Netzen........................ 15<br />

2.3 Capture-time based Thread Serialization................................................17<br />

3 Vorüberlegungen.......................................................................................... 19<br />

3.1 Migration und Replikation......................................................................19<br />

3.2 Objektserialisierung................................................................................20<br />

3.3 Parameter von Migrationsstrategien.......................................................22<br />

3.4 Statische Programmanalyse.................................................................... 23<br />

3.5 Dynamische Programmanalyse...............................................................25<br />

4 Das JScatter-System.....................................................................................27<br />

4.1 Transformation einer Java-Klasse.......................................................... 28<br />

4.2 Transformator......................................................................................... 30<br />

4.3 L<strong>auf</strong>zeitumgebung.................................................................................. 31<br />

4.4 Verteilungsplan.......................................................................................32<br />

5 Ein Modell für Migrationsstrategien..........................................................35<br />

5.1 Transformation....................................................................................... 35<br />

5.2 Migration und Replikation......................................................................36<br />

5.2.1 Migration ........................................................................................36<br />

5.2.2 Replikation......................................................................................42<br />

5.3 Migrationsstrategien............................................................................... 45<br />

6 Die Realisierung des Modells im JScatter-System.................................... 51<br />

6.1 Transformation....................................................................................... 51<br />

VII


INHALTSVERZEICHNIS<br />

6.1.1 Erkennung migrierbarer und replizierbarer Objekte ......................51<br />

6.1.2 Erweiterung des Transformationskonzeptes .................................. 52<br />

6.1.3 Nutzung der statischen Programmanalyse <strong>zur</strong> L<strong>auf</strong>zeit .................55<br />

6.2 Migration................................................................................................ 56<br />

6.2.1 Methodenzugriffe ...........................................................................58<br />

6.3 Replikation..............................................................................................59<br />

6.3.1 Methodenzugriffe ...........................................................................60<br />

6.4 Migrationsstrategien............................................................................... 62<br />

7 Evaluation..................................................................................................... 65<br />

7.1 Unterschiede bei Migration und Replikation .........................................65<br />

7.2 Eine komplexere Migrationsstrategie.....................................................70<br />

8 Zusammenfassung und Ausblick................................................................ 73<br />

Literatur........................................................................................................... 75<br />

Anhang A: Schnittstellen der Realisierung...................................................81<br />

Anhang A.1: Instanzenanteil-repräsentierende Objekte...............................81<br />

Anhang A.2: Klassenanteil-repräsentierende Objekte..................................82<br />

Anhang A.3: Replikatgruppen-Objekte........................................................85<br />

Anhang B: Transformationsbeispiele............................................................87<br />

Anhang B.1: Instanzenanteil-repräsentierende Objekte............................... 87<br />

Anhang B.2: Klassenanteil-repräsentierende Objekte..................................90<br />

Anhang C: Basisimplementierung der Informationserfassung.................. 93<br />

Anhang D: L<strong>auf</strong>zeitmessungen...................................................................... 95<br />

Anhang D.1: Beispiel Post: Instanzenanteil................................................. 95<br />

Anhang D.1.1: 10 Füllungen der Posttasche ...........................................95<br />

Anhang D.1.2: 100 Füllungen der Posttasche..........................................97<br />

Anhang D.2: Beispiel Post: Klassenanteil....................................................98<br />

Anhang D.2.1: 10 Füllungen der Posttasche ...........................................98<br />

Anhang D.2.2: 100 Füllungen der Posttasche..........................................99<br />

Anhang D.3: Beispiel Bibliothek................................................................100<br />

Anhang E: Beispielimplementierungen für Entscheidungsstrategien......103<br />

VIII


1 EINLEITUNG<br />

In den vergangenen Jahren wurde Java immer häufiger für wissenschaftliche<br />

Berechnungen eingesetzt. Wissenschaftliche Berechnungen sind jedoch größtenteils<br />

sehr umfangreich, so dass es von Vorteil ist, die entstehende Rechenlast<br />

<strong>auf</strong> mehrere Rechner zu verteilten, um die Ausführungsgeschwindigkeit<br />

der Berechnung zu erhöhen.<br />

In der Projektgruppe ParJava [FFF+2002] wurde ein System <strong>zur</strong> Verteilung<br />

von parallelen Java-Anwendungen entwickelt. Bevor eine Anwendung verteilt<br />

ausgeführt werden kann, wird sie <strong>auf</strong> Bytecodeebene unter Einsatz statischer<br />

Programmanalyse in eine verteilt ausführbare Anwendung transformiert.<br />

Ein Problem der verteilten Ausführung ist jedoch, dass für Objekte, die häufig<br />

von entfernt platzierten Objekten aus zugegriffen werden, ein enormer<br />

Kommunikations<strong>auf</strong>wand entsteht. Dieser Aufwand kann jedoch verringert<br />

werden, wenn das zugegriffene Objekt sich <strong>auf</strong> dem Rechner befindet, von<br />

dem aus die meisten Zugriffe <strong>auf</strong> das Objekt erfolgen. Diese Folgerung<br />

resultiert aus der Feststellung, dass lokale Zugriffe kostengünstiger sind als<br />

entfernte Zugriffe.<br />

Zur Veränderung der Platzierung eines Objektes dient die <strong>Objektmigration</strong>.<br />

Eine spezielle Form der <strong>Objektmigration</strong> stellt die Replikation von Objekten<br />

dar. Durch Replikation ist es möglich, alle Zugriffe <strong>auf</strong> ein Objekt lokal<br />

auszuführen, die den Zustand des Objektes nicht verändern. Zugriffe, die den<br />

Zustand eines Objektes verändern werden Schreibzugriffe genannt.<br />

Durch statische Programmanalyse kann schon vor der L<strong>auf</strong>zeit festgestellt<br />

werden, an welchen Stellen einer Anwendung der Zustand eines Objektes <strong>zur</strong><br />

L<strong>auf</strong>zeit verändert wird. Somit stellt die statische Programmanalyse eine<br />

Möglichkeit dar, Objekte einer Anwendung <strong>auf</strong> die wenige Schreibzugriffe<br />

erfolgen zu identifizieren und <strong>zur</strong> L<strong>auf</strong>zeit <strong>zur</strong> Replikation vorzuschlagen.<br />

Zusätzlich <strong>zur</strong> statischen Programmanalyse können jedoch mittels dynamischer<br />

Programmanalyse auch Erkenntnisse über das Verhalten einer Anwendung <strong>zur</strong><br />

L<strong>auf</strong>zeit gewonnen werden.<br />

Die Aufgabe von Migrationsstrategien ist, die Migration und/oder Replikation<br />

von Objekten einer verteilt auszuführenden Anwendung so einzusetzen, dass<br />

die Anzahl der entfernten Methodenzugriffe minimiert und somit die Ausführungsgeschwindigkeit<br />

der Anwendung erhöht wird.<br />

Herkömmliche Migrationsstrategien setzen für diese Berechnungen größtenteils<br />

Informationen aus dynamischer Programmanalyse ein. Es gibt jedoch<br />

auch Systeme, bei denen die Migrationsstrategie während der Implementierung<br />

direkt vom Programmierer festgelegt wird. Solchen <strong>Strategien</strong> ist es jedoch<br />

nicht möglich, <strong>auf</strong> Veränderungen der Rechenlast, beispielsweise durch<br />

Veränderung der Anzahl der Rechner bei der Ausführung der Anwendung,<br />

dynamisch zu reagieren.<br />

1


KAPITEL 1 EINLEITUNG<br />

Diese Diplomarbeit erweitert den Ansatz von Migrationsstrategien, die Informationen<br />

aus dynamischer Programmanalyse nutzen, um die Nutzung von<br />

Informationen aus statischer Programmanalyse. Hierbei wird die statische<br />

Programmanalyse einerseits eingesetzt, um nur selten schreibend zugegriffene<br />

Objekte zu identifizieren und Migrationsstrategien <strong>zur</strong> L<strong>auf</strong>zeit <strong>zur</strong> Replikation<br />

vorzuschlagen. Andererseits wird sie eingesetzt, um den bei der Migration zu<br />

übertragenden Objektzustand in Form der Felder des Objektes während der<br />

Transformation der Anwendung zu ermitteln und spezielle Klassen zu generieren,<br />

mit denen <strong>zur</strong> L<strong>auf</strong>zeit die Migration bzw. Replikation des Objektes<br />

durchgeführt wird.<br />

In Kapitel 2 werden verwandte Arbeiten im Zusammenhang mit Migration und<br />

Replikation betrachtet. Hierbei wird zum Einen <strong>auf</strong> ausgewählte verteilte<br />

Systeme, die <strong>Objektmigration</strong> und/oder Objektreplikation bei der Ausführung<br />

von Java-Anwendungen unterstützen, eingegangen. Zum Anderen werden<br />

verschiedene Migrations- bzw. Replikationsstrategien diskutiert. Abschließend<br />

geht Kapitel 2 <strong>auf</strong> die Migration von Thread-Objekten ein.<br />

Auf Vorüberlegungen, die im Zusammenhang mit Migrationsstrategien in<br />

dieser Arbeit unerlässlich sind, wird in Kapitel 3 eingegangen. Hierzu gehören<br />

eine nähere Betrachtung der Migration und Replikation von Objekten, die<br />

Objektserialisierung, Parameter von Migrationsstrategien und die Betrachtung<br />

von statischer und dynamischer Programmanalyse.<br />

Kapitel 4 stellt das JScatter-System vor, in dem das entwickelte Modell für<br />

Migrationsstrategien realisiert wurde. In Kapitel 5 wird <strong>auf</strong> das entwickelte<br />

Modell für Migrationsstrategien näher eingegangen. Hierbei werden die<br />

Erweiterung des Transformationskonzeptes für Migration und Replikation und<br />

die Aufgaben von Migrationsstrategien näher vorgestellt.<br />

Die Realisierung des Modells, dessen Besonderheiten bei der Serialisierung<br />

sowie dessen Erweiterbarkeit, werden im Kapitel 6 beschrieben. Abschließend<br />

wird in Kapitel 7 eine Evaluation der Implementierung an zwei Beispielsimulationen<br />

vorgenommen und in Kapitel 8 eine kurze Zusammenfassung<br />

durchgeführt sowie einige Vorschläge für die Weiterentwicklung des Systems<br />

präsentiert.<br />

2


2 VERWANDTE ARBEITEN<br />

2 VERWANDTE ARBEITEN<br />

In diesem Kapitel werden einige Arbeiten vorgestellt, die sich mit Teilgebieten<br />

der Diplomarbeit beschäftigen. Zuerst werden einige ausgewählte Systeme<br />

vorgestellt bevor Migrations- und Replikationsstrategien beschrieben werden.<br />

Abschließend wird noch die Thread-Migration diskutiert.<br />

2.1 Verteilte Systeme<br />

2.1.1 JAVAPARTY<br />

JavaParty [PZJ1998] ist eine Bibliothek zum Schreiben von verteilten<br />

Anwendungen in Java. Hierfür erweitert es die Programmmiersprache um den<br />

Modifier remote. Durch Verwendung des neuen Modifiers legt der Programmierer<br />

fest, dass die Instanzen einer Klasse entfernt platziert werden<br />

können. Diese Instanzen werden auch als remote objects bezeichnet.<br />

JavaParty besteht aus einem Übersetzer und einem L<strong>auf</strong>zeitsystem. Der<br />

JavaParty-Übersetzer generiert für jede Klasse einer in JavaParty geschriebenen<br />

Anwendung, sieben Klassen und sechs Interfaces, ruft anschließend den<br />

javac-Übersetzer <strong>auf</strong> und erzeugt unter Einsatz des RMI-Generators die für die<br />

RMI-Kommunikation notwendigen Stub- und Skeletonklassen. Nach dem<br />

Übersetzen kann das Programm im JavaParty-L<strong>auf</strong>zeitsystem ausgeführt<br />

werden.<br />

JavaParty unterstützt die Migration von Objekten und die Replikation<br />

statischer Konstanten [PZ1997, Bor2002]. Wenn ein remote object <strong>auf</strong> einen<br />

entfernten Rechner migriert wird, bleibt <strong>auf</strong> dem Ursprungsrechner ein<br />

Stellvertreter <strong>zur</strong>ück. Wird <strong>auf</strong> den Sellvertreter ein Zugriff ausgeführt, löst<br />

dieser eine MovedException aus und gibt sie an das <strong>auf</strong>rufende Objekt<br />

<strong>zur</strong>ück. Mit der MovedException erfährt das <strong>auf</strong>gerufene Objekt die neue<br />

Platzierung des migrierten Objektes und führt den misslungenen Zugriff ein<br />

zweites mal <strong>auf</strong> das migrierte Objekt aus.<br />

Als Stellvertreter eines remote objects dient in JavaParty ein sogenanntes<br />

Handle. Über sie werden alle Zugriffe <strong>auf</strong> remote objects durchgeführt. Durch<br />

das Weiterleiten der Zugriffe über Handles können alle Zugriffe <strong>auf</strong> remote<br />

objects während dessen Migration solange <strong>auf</strong>gehalten werden, bis der Umzug<br />

abgeschlossen ist.<br />

Die Migration eines remote objects ist in JavaParty nur dann möglich, wenn<br />

keine Methode <strong>auf</strong> dem zu migrierenden Objekt gerade ausgeführt und kein<br />

anderes Objekt zum Zeitpunkt des Migrationswunsches migriert wird.<br />

In JavaParty gibt es zwei unterschiedliche Möglichkeiten, das zu migrierende<br />

Objekt neu zu platzieren. Bei der ersten wird das Objekt <strong>auf</strong> den Rechner<br />

migriert, <strong>auf</strong> dem das zugreifende Objekt platziert ist. Als zweite Möglichkeit<br />

kann ein beliebiger Rechner als neuer Platzierungsort gewählt werden, der<br />

bereits ein remote object enthält. In beiden Möglichkeiten setzt JavaParty<br />

3


KAPITEL 2 VERWANDTE ARBEITEN<br />

folgende Migrationstechnik ein.<br />

Zuerst beschafft sich das Handle die JavaParty-Repräsentation der lokalen<br />

JVM des zu migrierenden remote objects. Anschließend wird dieses<br />

LocalJVM-Objekt über einen Methoden<strong>auf</strong>ruf dazu <strong>auf</strong>gefordert, das remote<br />

object in seine Lokalität zu holen. Dar<strong>auf</strong>hin initiiert das LocalJVM-Objekt das<br />

remote object, mit der Migration zu beginnen. Dies prüft, ob die Migration<br />

zum gegenwärtigen Zeitpunkt möglich ist. Ist die Migration durchführbar, wird<br />

das remote object für weitere Zugriffe gesperrt und durch Objekt-Serialisierung<br />

zu einem byte-Array konvertiert. Anderenfalls wird die Migration abgelehnt<br />

und das bisherige remote object bleibt weiterhin gültig [Zen1997].<br />

Ist das byte-Array <strong>auf</strong> dem neuen Rechnerknoten eingetroffen, wird aus ihm<br />

ein neues remote object – also eine Kopie – erzeugt und ein Stub an das<br />

Handle <strong>zur</strong>ückgegeben. Das alte remote object und das Handle aktualisieren<br />

ihren Stub und schließen somit die Migration ab. Von diesem Zeitpunkt an<br />

leitet das veraltete remote object alle weiteren Methoden<strong>auf</strong>rufe an die Kopie<br />

weiter.<br />

Bei der Übersetzung einer in JavaParty geschriebenen Anwendung wird für<br />

jede Klasse der Anwendung auch eine Klasse generiert, die den statischen<br />

Anteil einer Originalklasse repräsentiert. Diese Klassen werden als remote<br />

classes bezeichnet. Remote classes werden im JavaParty-L<strong>auf</strong>zeitsystem einmal<br />

platziert. Eine Migration von remote classes ist jedoch nicht vorgesehen.<br />

JavaParty verfügt über Verteilungsstrategien, die die Initialplatzierung eines<br />

Objektes berechnen. Als Migrationsstratgie setzt es load-balancing ein. Durch<br />

load-balancing werden migrierbare Objekte <strong>zur</strong> L<strong>auf</strong>zeit so platziert, dass alle<br />

beteiligten Rechner ungefähr den gleichen Anteil der zu verrichtenden Arbeit<br />

ausführen. Die Verteilungs-, wie auch die Migrationsstrategie ist austauschbar.<br />

Hierfür wurden sie über das Entwurfsmuster Strategy [GHJV1995] realisiert.<br />

2.1.2 DOORASTHA<br />

Doorastha [Dah2000] ist wie JavaParty (siehe Abschnitt 2.1.1) eine Bibliothek<br />

zum Schreiben von verteilten Anwendungen in Java. Es basiert <strong>auf</strong> der Idee,<br />

eine zentrale Java-Anwendung durch Hinzufügen von speziellen Kommentaren<br />

(Tags) zu verteilen. Doorastha verfügt ebenfalls über einen Übersetzer sowie<br />

ein L<strong>auf</strong>zeitsystem. Beim Übersetzen einer kommentierten Anwendung ersetzt<br />

der Doorastha-Übersetzer die Kommentare durch speziellen Code und generiert<br />

für jede Klasse der Anwendung eine Wrapper-Klasse, eine Proxy-Klasse<br />

sowie ein Interface. Nach anschließendem Übersetzen der Anwendung mit dem<br />

javac-Übersetzer, ist sie im Doorastha-L<strong>auf</strong>zeitsystem ausführbar.<br />

Die Proxy-Klasse dient als Stellvertreter für entfernte Zugriffe <strong>auf</strong> ein Objekt.<br />

Ein Wrapper packt ein im Doorastha-L<strong>auf</strong>zeitsystem platziertes Objekt ein und<br />

leitet alle von einem Proxy erfolgten Zugriffe an das umschlossene Objekt<br />

weiter. Doorastha unterstützt Migration, Kopieren und die sogenannte Globa-<br />

4


2.1 VERTEILTE SYSTEME<br />

lisierung von Objekten. Ein globalisiertes Objekt kann im Doorastha-L<strong>auf</strong>zeitsystem<br />

wie ein zentrales Originalobjekt referentiell zugegriffen werden.<br />

Beim Kopieren von Objekten handelt es sich nicht um echte Replikation mit<br />

Aktualisierung nach Schreibzugriffen, sondern um das Kopieren von Objekten,<br />

wie es die RMI-Funktionalität von Sun [Sun2003] <strong>zur</strong> Verfügung stellt.<br />

Durch Hinzufügen des entsprechenden Kommentars /*:globalizable*/, /<br />

*:copyable*/ oder /*:migratable*/ bereitet der Programmierer eine<br />

Klasse für die verteilte Ausführung seiner Instanzen vor. Beim Instanzieren<br />

einer Klasse durch den new-Operator muss der Programmierer angeben, <strong>auf</strong><br />

welchen Rechner das Objekt instanziert werden soll, anderenfalls wird es lokal<br />

platziert. Analog dazu gibt der Programmierer auch den Rechner an, <strong>auf</strong> den<br />

ein Objekt <strong>zur</strong> L<strong>auf</strong>zeit migriert werden soll.<br />

Auf diese Weise legt der Programmierer sowohl die Verteilungsstrategie als<br />

auch die Migrationsstrategie beim Schreiben der Anwendung fest.<br />

Analog zu JavaParty wird auch bei Doorastha eine MovedException beim<br />

Zugriffsversuch <strong>auf</strong> einen Stellvertreter eines migrierten Objekts ausgelöst. Die<br />

neue Platzierung des migrierten Objektes erhält das zugreifende Objekt über<br />

die MovedException. Nach der Aktualisierung der Referenz wird der misslungene<br />

Zugriff wiederholt.<br />

Doorastha unterstützt die sogenannte schwache Thread-Migration, bei der das<br />

Objekt nach der Migration explizit beliefert werden muss, um den Zustand des<br />

Objektes an der neuen Platzierung wiederherstellen zu können. Wie in Abschnitt<br />

2.3 beschrieben wird, ist diese Form der Thread-Migration jedoch mit<br />

Informationsverlust verbunden.<br />

2.1.3 JUGGLE<br />

Juggle [SH1998] ist eine verteilte virtuelle Maschine für Java-Anwendungen,<br />

die Objekte und Threads automatisch migriert und für eine ausgewogene Auslastung<br />

der beteiligten Rechnerknoten sorgt. Mit Juggle kann eine beliebige<br />

parallele Java-Anwendung <strong>auf</strong> mehreren Rechnern verteilt abl<strong>auf</strong>en. Sie setzt<br />

Instrumentierung ein, um während des Programmabl<strong>auf</strong>s ständig günstige<br />

Platzierungen für Objekte und Threads zu errechnen. Die Umsetzung der errechneten<br />

Konfigurationen wird unter Verwendung von Migration und Replikation<br />

hergestellt.<br />

Im Gegensatz zu JavaParty (siehe Abschnitt 2.1.1) und Doorastha (siehe Abschnitt<br />

2.1.2) benötigt Juggle nur die übersetzte Java-Anwendung <strong>zur</strong> verteilten<br />

Ausführung. Eine Transformation oder spezieller Code ist nicht notwendig.<br />

Ähnlich wie in Distributed Shared Memory-Systemen (DSM) ist jedes Objekt<br />

von allen beteiligten Rechnerknoten aus zugreifbar [SH1998]. Die gesamten<br />

Objektdaten werden jedoch anders als bei DSM nicht zu dem zugreifenden<br />

Knoten verschickt, sondern beim Zugriff <strong>auf</strong> eine Instanzvariable das entsprechende<br />

Datum vom zugehörigen Rechnerknoten angefordert. Daher<br />

5


KAPITEL 2 VERWANDTE ARBEITEN<br />

können die Daten eines Objektes transparent migriert werden.<br />

Objekte, die <strong>zur</strong> L<strong>auf</strong>zeit nur einmal modifiziert werden und <strong>auf</strong> die im Weiteren<br />

voraussichtlich lediglich lesend zugegriffen wird, werden nicht migriert<br />

sondern repliziert. Auf diese Weise enthält jeder Rechnerknoten seine eigene<br />

Kopie der benötigten Objekte, wodurch zeit<strong>auf</strong>wändige entfernte Zugriffe<br />

entfallen.<br />

Juggle optimiert dynamisch und automatisch die Verteilung von Objekten, indem<br />

es ein Maß für die Last der Threads <strong>auf</strong> die Objekte bestimmt. Hierbei<br />

werden für jedes Objekt die Anzahl der Zugriffe aller Threads in einer festgelegten<br />

Zeitspanne gezählt. Diese Anzahl dient als Maß für die Last der Threads<br />

<strong>auf</strong> das Objekt. Juggle migriert ein Objekt immer zu dem Thread, der die<br />

größte Last erzeugt. Auf diese Weise können die meisten Zugriffe <strong>auf</strong> das<br />

Objekt lokal ausgeführt werden.<br />

Durch dieses Verfahren wird die Anzahl der Zugriffe <strong>auf</strong> entfernte Rechnerknoten<br />

stark reduziert. Während der gesamten L<strong>auf</strong>zeit überprüft das System<br />

periodisch die Auslastung der Rechnerknoten und korrigiert sie gegebenenfalls.<br />

Für diesen Vorgang werden die Korrelationen zwischen Threads und<br />

Objekten berücksichtigt. Hierbei werden Threads, die <strong>auf</strong> die gleichen Objekte<br />

Last ausüben, möglichst <strong>auf</strong> denselben Rechnerknoten bewegt. Somit wird<br />

sowohl die Last <strong>auf</strong> die Rechenleistung, als auch die Anzahl der entfernten<br />

Objektzugriffe minimiert.<br />

Wie am Anfang des Abschnitts beschrieben, wird durch Instrumentierung festgestellt,<br />

von welchem Rechner aus <strong>auf</strong> ein Objekt die größte Last ausgeübt<br />

wird. Jedes Objekt hat hierfür einen Zeiger <strong>auf</strong> eine Datenstruktur, die den<br />

Knoten beschreibt, <strong>auf</strong> dem sich das Objekt befindet. Ist das Objekt lokal, ist<br />

der Zeiger NULL. Über weitere Zähler zählt jedes Objekt Zugriffe von lokalen<br />

und entfernten Threads. Erreicht die Summe beider Zähler einen konfigurierbaren<br />

Schwellwert, wird die Platzierung des Objektes neu bestimmt und gegebenenfalls<br />

durch Migration oder Replikation angepasst. Ist die Anzahl der<br />

Zugriffe von lokalen Threads größer als die Anzahl der Fernzugriffe, bleibt das<br />

Objekt <strong>auf</strong> dem Rechnerknoten, <strong>auf</strong> dem es sich befindet, andernfalls wird<br />

überprüft, ob eine Verlagerung des Objektes <strong>auf</strong> einen anderen Rechnerknoten<br />

für das Gesamtsystem effizienter ist.<br />

Damit Juggle ermitteln kann, von welchem Rechnerknoten aus wieviele entfernte<br />

Zugriffe <strong>auf</strong> ein Objekt durchgeführt wurden, enthält jedes Objekt für<br />

jeden Rechner einen zusätzlichen Zähler, der die entfernten Zugriffe jedes<br />

Rechners zählt.<br />

Juggle setzt Objektreplikation anstelle von <strong>Objektmigration</strong> ein, wenn die Anzahl<br />

der Lesezugriffe nach einem Schreibzugriff einen konfigurierbaren Wert<br />

überschreitet. Zum Zählen der Lesezugriffe wird ein weiterer Zähler verwendet,<br />

der bei einem Lesezugriff um eins inkrementiert und bei einem Schreibzugriff<br />

gelöscht wird. Soll ein Objekt migriert werden, wird überprüft, ob der<br />

6


2.1 VERTEILTE SYSTEME<br />

Lesezugriffs-Zähler den konfigurierbaren Wert für Migration überschreitet. Ist<br />

dies der Fall, wird das Objekt repliziert. Bei der Replikation eines Objektes<br />

merkt sich das System über ein Bitfeld, welche Rechnerknoten Replikate<br />

besitzen. Bei einem Schreibzugriff <strong>auf</strong> ein Replikat werden diese Knoten<br />

benachrichtigt und die Replikate gelöscht [SH1998]. Auf diese Weise<br />

verhindert Juggle inkonsistente Objektzustände zwischen einem Original-<br />

Objekt und seinen Replikaten.<br />

2.1.4 JOCHESTRA<br />

JOchestra [TS2002a, TS2002b] ist ein automatisches Partitionierungssystem<br />

für Java-Anwendungen. Es transformiert die Klassen einer Java-Anwendung in<br />

eine durch die Standard-JVM ausführbare verteilte Anwendung. JOchestra<br />

besteht aus einem Klassifikator, einem Profiler und einem Transformator.<br />

Unter Einsatz des Profilers und des Klassifikators legt der Benutzer die<br />

Partitionierung fest, die anschließend durch den Transformator erstellt wird.<br />

Bei der Klassifikation unterscheidet JOchestra zwischen anchored und mobile<br />

Klassen. Eine Klasse ist anchored, wenn die Platzierung ihrer Instanzen nicht<br />

verändert werden kann. Andernfalls ist eine Klasse mobile. Klassen deren<br />

Methoden native Code enthalten, können bei der Transformation nicht<br />

verändert werden. Sie werden in JOchestra als unmodifiable bezeichnet. Unter<br />

Verwendung dieser Begrifflichkeiten stehen dem Benutzer bei der<br />

Klassifikation der Klassen einer zu transformierenden Anwendung drei<br />

Optionen <strong>zur</strong> Auswahl.<br />

Anchored unmodifiable Klassen: Eine Systemklasse C ist anchored unmodifiable,<br />

wenn sie nativen Maschinen-Code enthält, oder <strong>auf</strong> Referenzen von C-<br />

Objekte zwischen modifizierbarem Maschinen-Code und Instanzen einer<br />

anchored unmodifiable Klasse zugegriffen werden.<br />

Anchored modifiable Klassen: Eine Klasse ist anchored modifiable, wenn es<br />

eine modifizierbare Klasse der Anwendung ist, die eine anchored unmodifiable<br />

Klasse erweitert.<br />

Mobile Klassen: Mobile Klassen sind alle Klassen, die nicht in eine der<br />

anderen beiden Kategorien fallen. Alle Klassen, die in purem Java geschrieben<br />

sind und keine Systemklasse erweitern (außer java.lang.Object) sind<br />

mobile Klassen. Auch Systemklassen können zu dieser Kategorie gehören,<br />

wenn sie keinen nativen Maschinen-Code enthalten und von keiner anchored<br />

unmodifiable Klasseninstanz zugegriffen werden oder <strong>auf</strong> eine solche<br />

zugreifen.<br />

Mit Hilfe des Profilers und des Klassifikators legt der Benutzer die Klassifikation<br />

für jede Klasse der zu transformierenden Anwendung fest. Hierbei<br />

überprüft der Klassifikator die Auswahl und schlägt dem Benutzer Veränderungen<br />

vor, um eine gültige Partitionierung zu erstellen. Für mobile Klassen<br />

muss der Benutzer außerdem eine Migrationsstrategie beschreiben, in der er<br />

7


KAPITEL 2 VERWANDTE ARBEITEN<br />

festlegt, wann und wohin das Objekt migriert werden soll. Zur Auswahl stehen<br />

call-by-move und call-by-visit. Mit call-by-move wird das Objekt <strong>auf</strong> den<br />

angegebenen Rechner migriert und behält seine neue Platzierung bei. Mit callby-visit<br />

behält das Objekt nur solange die neue Platzierung bei, bis der Zugriff<br />

abgeschlossen ist. Nach dem Zugriff migriert es zu seiner Ursprungsplatzierung<br />

<strong>zur</strong>ück [JLHB1988].<br />

TILEVICH und SMANAGDAKIS heben hervor, dass das Erstellen einer Migrationsstrategie<br />

keine triviale Aufgabe ist. Eine falsche Wahl hat eine ineffektive und<br />

inkorrekte verteilte Anwendung <strong>zur</strong> Folge [TS2002a].<br />

Neben der Migrationsstrategie muss der Benutzer auch die Initialplatzierung<br />

aller Objekte bei ihrer Klassifikation festlegen. Instanzen von anchored<br />

Klassen werden einmal platziert und behalten ihre Platzierung während der<br />

gesamten L<strong>auf</strong>zeit der verteilten Anwendung bei. Auf diese Weise legt der<br />

Benutzer in JOchestra die Verteilungsstrategie fest.<br />

Bei der Transformation werden durch den JOchestra-Transformator Stellvertreter-Klassen<br />

im Sourcecode-Format und Remote-Klassen in Bytecode erzeugt.<br />

Die Stellvertreter-Klassen werden zum Abschluss der Transformation<br />

übersetzt. Die Original-Klassen werden so transformiert, dass sie nicht mehr<br />

die Klasse java.lang.Object sondern die Klasse java.rmi.server.<br />

UniCastRemoteObject erweitern. Dies ist notwendig, da die transformierte<br />

Anwendung über RMI kommuniziert.<br />

2.2 Migrationstrategien<br />

2.2.1 REPLIXA<br />

In [JGK1999, Jäg1999] stellen JÄGER, GOLM und KLEINÖDER ein <strong>auf</strong> Reflektion<br />

[Mae1987] basierendes Framework names RepliXa <strong>zur</strong> transparenten und<br />

anpassbaren Replikation von Objekten vor. RepliXa verfügt <strong>zur</strong> Zeit nur über<br />

ein Grundsystem, das noch vieler Erweiterungen bedarf [Jäg1999]. Jedoch<br />

zeigt es für die Entwicklung von Replikationsstrategien einige interessante<br />

Aspekte <strong>auf</strong>.<br />

Das wesentliche Anliegen der Entwicklung war es, für Programmierer ein<br />

einfaches, transparentes, anpassbares und offenes System <strong>zur</strong> Objektreplikation<br />

zu entwickeln. Hierbei bedeuten<br />

einfach: eine einheitliche und kleine Schnittstelle <strong>zur</strong> Nutzung der Dienste<br />

anzubieten,<br />

transparent: Objekte entwickeln zu können, ohne Kenntnis darüber, ob sie<br />

in der Zukunft repliziert werden,<br />

anpassbar: die Ausrichtung für neue Ausführungsumgebungen ein<strong>zur</strong>ichten<br />

und neue Replikationsprotokolle möglichst orthogonal <strong>zur</strong> Anwendung<br />

hinzufügen zu können,<br />

offen: neue Replikationsstrategien problemlos integrieren zu können.<br />

8


2.2 MIGRATIONSTRATEGIEN<br />

RepliXa verfügt über einen Mechanismus zum selektiven Binden von<br />

Replikationsstrategien an Objekte. Hierzu setzt es den Java-Interpreter metaXa<br />

[Gol1997] ein. Beim selektiven Binden wird an jedes zu replizierende Objekt<br />

(Basis-Objekt) ein sogenanntes Meta-Objekt gebunden. Jedes Meta-Objekt<br />

kapselt in einem Strategie-Objekt die Verhaltensänderung des Basis-Objektes<br />

bei seiner Replikation. Das dynamische Binden <strong>zur</strong> L<strong>auf</strong>zeit muss vom<br />

Benutzer jedoch speziell vorbereitet werden.<br />

Für einen entfernten Methoden<strong>auf</strong>ruf setzt RepliXa einen eigenen RMI-Mechanismus<br />

ein. Soll beispielsweise ein Objekt A eine Methode eines entfernt<br />

platzierten Objektes B <strong>auf</strong>rufen, geschieht dies über einen Stellvertreter von B,<br />

der im gleichen Adressraum wie A platziert ist (siehe Abbildung 2.1). Der<br />

Aufruf einer Methode von B durch A wird über den Stellvertreter von B, dessen<br />

Meta-Objekt und dem Meta-Objekt von B an B weitergeleitet. Das Ergebnis<br />

des Aufrufes wird in entgegengesetzter Richtung <strong>zur</strong>ückgegeben.<br />

Abbildung 2.1: Abl<strong>auf</strong> einer Remote Methode Invocation in RepliXa<br />

Für Objektreplikation stellt RepliXa eine aktive und eine passive Replikationsstrategie<br />

bereit. Beide <strong>Strategien</strong> halten den Zustand einer Menge von<br />

Objekten konsistent. Ihre Vorgehensweisen sind jedoch unterschiedlich.<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

Objek<br />

t<br />

Prozess A<br />

Meta-Objekt B_Stellvertreter<br />

Meta System<br />

Basis System<br />

Prozess B<br />

Meta System<br />

Basis System<br />

1 Methoden<strong>auf</strong>ruf an ein beliebiges<br />

1 Replikat (also auch an einen Primary)<br />

2 Weiterleiten an den Primary<br />

3 Koordination mit anderen Methoden<strong>auf</strong>-<br />

3 rufen<br />

4 Weiterleiten an alle Replikate<br />

5 Bearbeiten des Methoden<strong>auf</strong>rufes<br />

6 Rückgabe der Ergebnisse an den Primary<br />

7 Rückgabe des Ergebnisses an das<br />

7 Replikat<br />

8 Rückgabe des Ergebnisses an den<br />

8 Aufrufer<br />

Abbildung 2.2: Abl<strong>auf</strong> eines replizierten Methoden<strong>auf</strong>rufes<br />

IPC<br />

B_Stellvertreter A B<br />

Primary Replikat<br />

1<br />

Replikat<br />

Meta-Objekt B<br />

9


KAPITEL 2 VERWANDTE ARBEITEN<br />

Die aktive Replikationsstrategie geht von folgender Grundidee aus:<br />

Wenn <strong>auf</strong> eine gegebene Menge von Objekten mit konsistentem<br />

Zustand die gleiche Methode <strong>auf</strong>gerufen wird, dann befinden sich<br />

nach der Ausführung der Methode alle Objekte wiederum in einem<br />

konsistenten Zustand. Damit die Konsistenz der Objekte gewahrt<br />

bleibt, werden Methoden<strong>auf</strong>rufe <strong>auf</strong> ein Objekt an alle anderen<br />

Objekte der Menge weitergeleitet.<br />

Damit auch bei synchroner Methodenausführung, bei der Realisierung der<br />

Idee, keine inkonsistenten Zustände zusammengehöriger Replikate <strong>auf</strong>treten,<br />

wurde eine spezielle Vorgehensweise entwickelt. Hierfür setzen JÄGER, GOLM<br />

und KLEINÖDER einen sogenannten Primary für jede Gruppe von Replikaten ein.<br />

Trifft bei einem Replikat ein Methoden<strong>auf</strong>ruf ein, leitet er den Methoden<strong>auf</strong>ruf<br />

an seinen Primary weiter. Beim Eingang mehrerer Methoden<strong>auf</strong>rufe legt der<br />

Primary die Reihenfolge der Abarbeitung fest. Anschließend leitet er die<br />

Ausführung der Methoden<strong>auf</strong>rufe in der festgelegten Reihenfolge an alle<br />

Replikate weiter (siehe Abbildung 2.2).<br />

Ein weiteres Problem besteht, wenn bei der Abarbeitung des Methoden<strong>auf</strong>rufes<br />

<strong>auf</strong> einem Replikat ein weiterer Methoden<strong>auf</strong>ruf <strong>auf</strong> ein anderes Objekt<br />

involviert ist (siehe Abbildung 2.3).<br />

1<br />

2<br />

3<br />

Abbildung 2.3: Problem des mehrfachen Methoden<strong>auf</strong>rufs<br />

1 Replizierter Methoden<strong>auf</strong>ruf<br />

1 (vereinfachte Darstellung)<br />

2 Teilweise Bearbeitung des<br />

2 Methoden<strong>auf</strong>rufes<br />

3 Verschachtelter Methoden<strong>auf</strong>ruf<br />

In RepliXa wird dieses Problem durch Koordinierung der Replikate über den<br />

Primary gelöst (siehe Abbildung 2.4). Alle Replikate arbeiten die Methode bis<br />

zu dem Punkt ab, an dem die Methode des involvierten Objektes <strong>auf</strong>gerufen<br />

werden muss. Dann geben sie dem Primary ein Signal, dass sie die Stelle erreicht<br />

haben. Der Primary wartet solange, bis er von allen Replikaten das<br />

Signal bekommen hat und führt anschließend die Methode <strong>auf</strong> dem involvierten<br />

Objekt aus. Nach dem Abarbeiten der involvierten Methode arbeiten die<br />

Replikate nach Aufforderung durch den Primary die Methode weiter ab.<br />

Die passive Replikationsstrategie erhält den konsistenten Zustand aller Replikate<br />

<strong>auf</strong> einem anderen Weg. Jedes Replikat arbeitet die Methode ab und leitet<br />

nur die Veränderungen an die anderen Replikate weiter. Hierbei entstehen jedoch<br />

zeitweise inkonsistente Objektzustände zwischen den Replikaten.<br />

Weshalb dar<strong>auf</strong> geachtet werden muss, dass durch inkonsistente Replikate die<br />

Gesamtausführung des Programms, im Gegensatz <strong>zur</strong> lokalen Ausführung der<br />

10<br />

Objekt Replikat Replikat Replikat<br />

3<br />

Objekt


2.2 MIGRATIONSTRATEGIEN<br />

Anwendung, nicht verändert wird. Das Problem wurde in RepliXa wiederum<br />

durch einen Primary gelöst, der die Zugriffe <strong>auf</strong> die Replikate koordiniert. Des<br />

Weiteren wurde ein sogenannter Snooper geschaffen, dessen Aufgabe darin<br />

besteht, den Primary zu überwachen. Fällt dieser aus, übernimmt der Snooper<br />

die Aufgaben des Primarys. Diese Lösung basiert <strong>auf</strong> dem erweiterten DSM-<br />

Algorithmus der in [BW1994] ausführlich beschrieben ist.<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

Objekt<br />

A<br />

Primary Replikat<br />

1<br />

Replikat<br />

2<br />

Objekt<br />

B<br />

Abbildung 2.4: Verschachtelter Methoden<strong>auf</strong>ruf<br />

2.2.2 REPLIKATIONSHEURISTIKEN FÜR VERTEILTE SYSTEME<br />

1 Replizierter Methoden<strong>auf</strong>ruf<br />

1 (vereinfachte Darstellung)<br />

2 Teilweise Bearbeitung des<br />

2 Methoden<strong>auf</strong>rufes<br />

3 Verschachtelter Methoden<strong>auf</strong>ruf<br />

3 (weiterleiten zum Primary)<br />

4 Methoden<strong>auf</strong>ruf durch Primary<br />

5 Bearbeitung des verschachtelten<br />

5 Methoden<strong>auf</strong>rufes<br />

6 Rückgabe des Ergebnisses<br />

7 Verteilung des Ergebnisses<br />

8 Fortführung der Bearbeitung<br />

8 des Methoden<strong>auf</strong>rufes<br />

9 Rückgabe der Ergebnisse<br />

Ziel dieses Abschnittes ist es, die von MAFFEIS und CAP in [MC1992] dargestellten<br />

Replikationsheuristiken sowie Eigenschaften von Replikationsheuristiken<br />

vorzustellen. MAFFEIS und CAP gehen davon aus, dass <strong>auf</strong> ein<br />

Replikat nicht nur vom Rechner seiner Platzierung aus zugegriffen wird,<br />

sondern, dass Replikate für Zugriffe eines gesamten Subnetzwerkes vorgesehen<br />

sein können. Auf diese Weise kann durch wenige Replikate ein<br />

gesamtes Netzwerk abgedeckt werden. Zugriffe erfolgen in solch einem<br />

System immer <strong>auf</strong> das Replikat, das vom Zugreifer aus am nächsten platziert<br />

ist.<br />

Eine Objekt-Replikationsheuristik besteht aus einer Menge von Regeln die<br />

festlegen, wann welches Objekt repliziert wird, und wo das neue Replikat zu<br />

platzieren ist. Der Level einer Replikationsheuristik gibt an, wieviele Replikate<br />

eines Objektes <strong>zur</strong> L<strong>auf</strong>zeit maximal im Netzwerk platziert werden dürfen.<br />

Replikationsheuristiken dienen in Replikationssystemen als Kontrolleinheit,<br />

die den Replikationslevel an die Veränderungen im verteilten System<br />

dynamisch anpasst. Veränderungen finden durch Ereignisse, wie Knoten- oder<br />

Gateway-Fehler, zerbrechen von Anfragen zwischen Objekten, erzeugen und<br />

11


KAPITEL 2 VERWANDTE ARBEITEN<br />

platzieren neuer Objekte im System, usw. statt [MC1992].<br />

Das Anpassen des Levels einer Replikationsheuristik ist eine nicht triviale<br />

Aufgabe. Wenn im Netzwerk <strong>auf</strong> jedem Rechner ein Replikat eines Objektes<br />

vorhanden ist (Replikationslevel = Anzahl verfügbarer Rechner), dann<br />

bestimmen die Anzahl der Rechner und der Datenverkehr im Netzwerk,<br />

wieviel Zeit für das Aktualisieren aller Replikate eines Objektes, z. B. nach<br />

einen Schreibzugriff <strong>auf</strong> das replizierte Objekt, benötigt wird. Dieses Problem<br />

wird als TOO-MANY-REPLICAS-EFFECT bezeichnet.<br />

Werden in einer solchen Situation verstärkt Schreibzugriffe <strong>auf</strong> ein Objekt<br />

ausgeführt, dann hat das verteilte System hauptsächlich damit zu tun, die<br />

Replikate des Objekte zu aktualisieren. MAFFEIS und CAP nennen dies<br />

Distributed-Thrashing.<br />

Wenn der Replikationslevel hingegen zu niedrig ist, müssen die Referenzen<br />

zwischen einem Objekt und seinen Replikaten zu häufig aktualisiert werden,<br />

wobei auch lange Wartezeiten für Datenverkehr-intensive Aufgaben <strong>auf</strong>treten<br />

können. In einer solchen Situation müssen einerseits Maßnahmen ergriffen<br />

werden, die die Leistungsfähigkeit des Systems verbessern, und andererseits<br />

muss die Verfügbarkeit der im System platzierten Replikate erhöht werden.<br />

Um die Leistungsfähigkeit zu verbesseren, muss der Datenverkehr gesenkt<br />

werden. Um die Verfügbarkeit der platzierten Replikate zu erhöhen, müssen<br />

neue Replikate im System platziert werden, was zu erhöhtem Datenverkehr<br />

führt. Somit stehen sich die Maßnahmen konträr gegenüber. Dieses Problem<br />

wird als TOO-FEW-REPLICAS-EFFECT bezeichnet.<br />

MAFFEIS und CAP setzen sich in [MC1992] mit vier Replikationsheuristiken<br />

auseinander, die im Folgenden vorgestellt werden.<br />

NAIVE: Wenn von einem Rechner R <strong>auf</strong> ein Objekt O das erste Mal<br />

zugegriffen wird, dann wird ein Replikat von O <strong>auf</strong> R platziert, und<br />

in die Menge der lokalen Replikate <strong>auf</strong> R eingetragen. Eine obere<br />

Replikationsschranke ist durch die Speicherkapazität des verteilten<br />

Systems gegeben.<br />

LIMIT: LIMIT arbeitet wie NAIVE, benutzt jedoch zwei Schranken für die<br />

Replikation eines Objektes O. Die obere Replikationsschranke wird<br />

mit OU (upper bound) und die untere mit OL (lower bound) bezeichnet.<br />

LIMIT vermeidet Distributed-Thrashing wie auch TOO-FEW-<br />

REPLICAS-EFFECT und sorgt dafür, dass die beiden Schranken an<br />

die Veränderungen im System angepasst werden. Bei LIMIT ist es<br />

möglich, für jedes Objekt verschiedene OU und OL festzulegen.<br />

COUNT: Erst nach dem <strong>auf</strong> ein Objekt CO mal von R aus zugegriffen wurde,<br />

wird <strong>auf</strong> R ein Replikat von O platziert. Diese Strategie ist für<br />

Systeme geeignet, bei denen eine große Anzahl von Objekten nur<br />

selten von einem entfernten Rechner im Netzwerk zugegriffen wird.<br />

12


2.2 MIGRATIONSTRATEGIEN<br />

Im Gegensatz zu NAIVE und LIMIT, bei denen exzessiv kopiert und<br />

migriert wird, werden hier nur wenige Replikate erstellt. Wie in<br />

LIMIT gibt es auch in COUNT obere und untere Replikationsschranken.<br />

Des Weiteren enthält COUNT Zähler zum Zählen der<br />

Zugriffe <strong>auf</strong> ein Objekt, die von Zeit zu Zeit <strong>auf</strong> ihren Initialwert<br />

<strong>zur</strong>ückgesetzt werden.<br />

INTENSITY: INTENSITY baut <strong>auf</strong> COUNT <strong>auf</strong>. Sie lässt die Replikation von<br />

O jedoch nur zu, wenn die CO Zugriffe während eines Zeitintervalls<br />

I eingetroffen sind. Des Weiteren stellt INTENSITY<br />

Mechanismen für den Umgang mit zerbrochenen Anfragen<br />

zwischen Objekten bereit.<br />

2.3.3 OBJEKTMIGRATIONSSTRATEGIEN IN VERTEILTEN SYSTEMEN<br />

In verteilten Systemen setzen sowohl Lastverwaltungssysteme als auch verteilte<br />

Betriebssysteme <strong>Objektmigration</strong> ein, um eine Leistungsverbesserung zu<br />

erreichen. Hierbei soll vor allem die Rechenlast der über ein Netzwerk<br />

verbundenen Rechner möglichst gleichmäßig verteilt werden. Lastverwaltungssysteme<br />

und verteilte Betriebssysteme setzen zum Erreichen des Lastausgleichs<br />

größtenteils Prozessmigration ein.<br />

In Lastverwaltungssystemen wird hierbei versucht, echte Lastbalancierung<br />

zwischen den im Netzwerk vorhandenen Rechnern zu erreichen. Dabei gehen<br />

einige Lastverwaltungssysteme davon aus, dass die eigentliche Migration vom<br />

Betriebssystem unterstützt wird [Lux1995]. Im verteilten Betriebssystem Birlix<br />

[Lux1995] werden neben Prozessen auch Dateien und nicht aktive Objekte<br />

migriert.<br />

Mit den Aufgaben von <strong>Objektmigration</strong>sstrategien in verteilten Systemen<br />

beschäftigten sich GERNS und LIE im Rahmen des Projektes DIOMEDES<br />

(Design and Implementation of an Object Migration System for Performance<br />

Enhancements in Distributed Environment) [GL1994]. Hierbei fanden sie<br />

heraus, dass Migrationsstrategien Informationen über die Last der Rechner in<br />

Netzwerken sammeln, die gesammelten Informationen auswerten, eine Entscheidung<br />

treffen wie die Lastsituation verbessert werden kann und die Entscheidung<br />

unter Einsatz von Migration durchführen. Anschließend überprüfen<br />

sie, wie ihre Entscheidung die Lastsituation verändert hat und führen einen<br />

neuen Entscheidungsprozess <strong>zur</strong> Verbesserung der Lastsituation durch.<br />

Migrationsstrategien sind in sich geschlossene Systeme, die über Stunden,<br />

Tage, Wochen oder Jahre arbeiten und Maßnahmen zum Erhalt oder Herstellen<br />

von Lastausgleich vornehmen.<br />

Für das Sammeln von Informationen bestimmen Migrationsstrategien sowohl<br />

die Auslastung der <strong>auf</strong> den Rechnern vorhandenen CPUs als auch deren Speicherauslastung.<br />

Nach der Bewertung der Informationen erfolgt der Entscheidungsprozess,<br />

bei dem Maßnahmen zum Beheben von Unter- oder Überlast<br />

13


KAPITEL 2 VERWANDTE ARBEITEN<br />

getroffen werden. Während eines Entscheidungsprozesses müssen zuerst die<br />

Objekte ausgewählt werden, deren Platzierung zum Beheben der Lastsituation<br />

verändert werden müssen. Anschließend werden für die ausgewählten Objekte<br />

die Rechner der neuen Platzierung bestimmt. Nach Abschluss des Entscheidungsprozesses<br />

wird die Migration durchgeführt.<br />

Für die Durchführung der Migration stellen die Migrationsstrategien verschiedene<br />

Migrationsmechanismen bereit. Dies ist erforderlich, da Objekte, Prozesse<br />

und Dateien unterschiedlich migriert werden müssen.<br />

Eine weitere Aufgabe einer Migrationsstrategie ist die Informationsverteilung.<br />

Bei der Informationsverteilung werden die gesammelten Lastinformationen<br />

eines Rechners <strong>auf</strong> einen anderen Rechner übertragen. Erst durch die Informationsverteilung<br />

hat eine Migrationsstrategie das globale Wissen über die<br />

Lastsituation aller im Netzwerk befindlichen Rechnerknoten <strong>zur</strong> Verfügung<br />

und kann Maßnahmen für eine wirkliche Balancierung der Rechnenlast treffen.<br />

Migrationsstrategien in Lastverwaltungssystemen mit Lastbalancierung haben<br />

auch die Aufgabe, die Initialplatzierung für Objekte durchzuführen. Für diese<br />

Entscheidung sind globale Informationen der aktuellen Lastsituation aller<br />

Rechner im Netzwerk von Vorteil.<br />

Bei den Untersuchungen im Rahmen des Projekts DIOMEDES wurden die<br />

Aufgaben von Migrationsstrategien in ein Komponentenmodell eingeordnet.<br />

Hierbei entstand das in Abbildung 2.5 dargestellte Modell.<br />

Abbildung 2.5: Komponentenmodell für Migrationsstrategien<br />

Bei einigen <strong>Strategien</strong> war die Realisierung der Aufgaben jedoch so stark<br />

miteinander verflochten, dass verschiedene Modelle entstanden. So fassen<br />

MILOJIÈIÉ, PJEVAÈ und VALAÐEVIÆ in [MPV1991] in ihrer<br />

Entscheidungsstrategie-Komponente die Aufgaben der Transferentscheidungs-<br />

Komponente und der Lokalisierungs-Komponente von VEHMEIER [Veh1992]<br />

14<br />

Lastinformation der<br />

Rechner im Netzwerk<br />

sammeln<br />

Lastsituation bewerten<br />

und entscheiden, wie sie<br />

verbessert werden kann<br />

Transfer der neu zu<br />

platzierenden Objekte<br />

Entscheidungs-Komponente<br />

an die veränderte<br />

Lastsituation anpassen<br />

Informationserfassung<br />

Informationsverteilung<br />

Entscheidung<br />

Objektauswahl<br />

Ortswahl<br />

Transfer<br />

Adaption<br />

Lastinformation der<br />

Rechner im Netzwerk<br />

verteilen<br />

Initialplatzierung<br />

Auswahl der Objekte, die <strong>zur</strong><br />

Verbesserung der Lastsituation<br />

neu platziert werden müssen<br />

Auswahl der Rechner <strong>auf</strong><br />

denen ein neu zu platzierendes<br />

Objekte platziert werden soll


2.2 MIGRATIONSTRATEGIEN<br />

zusammen. GERNS und LIE teilen die Transferentscheidungs-Komponente von<br />

VEHMEIER [Veh1992] jedoch in eine Entscheidungs-Komponente und eine<br />

Objektauswahl-Komponente <strong>auf</strong> (siehe Abbildung 2.6).<br />

Informationsstrategie<br />

Entscheidungsstrategie<br />

Transfermechanismus<br />

Informationserfassung<br />

Informationsverteilung<br />

Entscheidung<br />

Objektauswahl<br />

Ortswahl<br />

Transfer<br />

Adaption<br />

Abbildung 2.6: Modelle verschiedener Migrationsstrategien<br />

Die Migrationsstrategie von GERNS und LIE enthält gegenüber den anderen<br />

Modellen auch eine sogenannte adaptive Komponente. Sie hat die Aufgabe,<br />

das Entscheidungsverhalten von Migrationsstrategien <strong>zur</strong> L<strong>auf</strong>zeit der veränderten<br />

Lastsituation anzupassen. Eine solche adaptive Migrationsstrategie<br />

wird im Abschnitt 2.3.4 näher vorgestellt.<br />

2.3.4 ADAPTIVE OBJEKTMIGRATION MIT NEURONALEN NETZEN<br />

Informationsgewinnung<br />

Transferentscheidung<br />

Lokalisierung<br />

Transfer<br />

[MPV1991] [GL1994] [Veh1992]<br />

In dem Projekt DIOMEDES (siehe auch Abschnitt 2.3.3) [GL1994] wurde ein<br />

System <strong>zur</strong> Untersuchung von Migrationsstrategien für verteilte Systeme<br />

entwickelt. Im Rahmen dieses Projektes beschäftigten sich DENECKE und<br />

WAHLBUHL mit der Entwicklung einer adaptiven Migrationsstrategie [DW1994].<br />

Diese Strategie ermöglicht es, Parameter des jeweilig genutzten Migrations-<br />

Verfahrens, wie beispielsweise Schwellwerte und Intervalllängen, <strong>auf</strong>grund<br />

langfristiger Beobachtungen an die jeweilige Lastsituation anzupassen.<br />

Da Neuronale Netze <strong>auf</strong> <strong>Grundlage</strong> unvollständiger Informationen erfolgreich<br />

arbeiten und <strong>zur</strong> Steuerung komplexer Systeme geeignet sind, bieten sie sich<br />

<strong>zur</strong> Verarbeitung der gesammelten Daten an. Aus diesem Grund setzen<br />

DENECKE und WAHLBUHL Neuronale Netze ein, um <strong>auf</strong> <strong>Grundlage</strong> ihrer Berechnungen<br />

sowohl die <strong>Objektmigration</strong>sverfahren, als auch die Einstellung der<br />

Parameter der Migrationsverfahren vorzunehmen.<br />

<strong>Objektmigration</strong>sverfahren stellen für ein verteiltes System durch ihren Verbrauch<br />

von Rechenkapazitäten und wegen der anfallenden Kosten für Kommunikation<br />

eine zusätzliche Belastung dar. Die verursachten Kosten stehen<br />

jedoch der Zielsetzung von <strong>Objektmigration</strong>sverfahren – der Leistungssteigerung<br />

– entgegen [DW1994]. Durch Verwendung von dynamischer Lastverteilung<br />

hängt die Migrationsentscheidung vom aktuellen Systemzustand ab.<br />

Außerdem mindern die bei der Migration entstehenden Kosten die durch<br />

15


KAPITEL 2 VERWANDTE ARBEITEN<br />

Migration erzielten Leistungsverbesserungen. Jedoch hängt die Wahl der<br />

besten Lastverteilungsstrategie immer von dem aktuellen Systemverhalten ab.<br />

Aus diesem Grund ist es sinnvoll, bei geringer Last eine andere Strategie zu<br />

verfolgen als bei einer hohen Systemlast. Dies ist die Aufgabe der adaptiven<br />

Komponente, um die Kosten zu minimieren und die Leistung zu steigern.<br />

Wie oben erwähnt, erfolgt die Adaption durch Änderung der Parameter der<br />

aktuellen Lastverteilungsstrategie. Die wichtigsten Parameter sind hierbei:<br />

die Aufrufintervalle<br />

<strong>zur</strong> Ermittlung der lokalen Last in periodischen Abständen,<br />

<strong>zur</strong> Informationsverteilung an allen Rechnerknoten, bei regelmäßigen Nachrichtenaustausch<br />

und<br />

<strong>zur</strong> Entscheidung in periodischen Abständen, ob eine Über- oder Unterlast<br />

vorliegt, sowie<br />

die Schwellwerte<br />

<strong>zur</strong> Entstehung der Initiierung einer Migrationsentscheidung, <strong>zur</strong> Ortsauswahl<br />

und zum Bestimmen der minimalen Differenz zwischen lokaler Last<br />

und der des entfernten Rechnerknotens, und die<br />

Anzahl der Rechnerknoten für den Informationsaustausch.<br />

Bei ersten Simulationen, die wegen ihres hohen Speicherverbrauchs nur 30<br />

Minuten dauerten, hat sich gezeigt, dass bei hoher Speicherlast sich die Leistung<br />

des Systems <strong>auf</strong>grund der zusätzlichen Kosten verringert. Aus diesem<br />

Grund ist es ratsam, die Intervalllängen für die periodische Informationsgewinnung,<br />

bei hoher Systemlast, zu erhöhen. Außerdem schlagen DENECKE und<br />

WAHLBUHL vor, bei Schwellwert-gesteuerten Entscheidungsstrategien den Parameter,<br />

welcher das Vorliegen einer Überlast angibt, zu erhöhen. Mit diesen<br />

Maßnahmen ist eine Minimierung der Kosten im Lastverteilungsverfahren zu<br />

erreichen. Eine detaillierte Beschreibung der Leistungsmessungen der adaptiven<br />

Migrationsstrategie kann in [DW1994] nachgelesen werden.<br />

Adaptive Migrationsstrategien sind in dieser Diplomarbeit jedoch nicht<br />

Gegenstand der Untersuchung. Zum Einen ist es nicht sinnvoll, in den zu<br />

entwickelnden Migrationsstrategien Neuronale Netze einzusetzen, da diese<br />

sich nur für die Beobachtung und Steuerung von Anwendung eignen, die über<br />

einen längeren Zeitraum (z.B. > 5 Min) l<strong>auf</strong>en. Zum Anderen sind die Berechnungen<br />

<strong>zur</strong> Adaption von <strong>Strategien</strong> sehr Speicher<strong>auf</strong>wändig [DW1994], so<br />

dass deren Einsatz <strong>zur</strong> Auslagerung von Speicherinhalten <strong>auf</strong> die Festplatte<br />

führen könnte. Eine Auslagerung von Speicherinhalten ist jedoch mit einem<br />

Abfall der Ausführungsgeschwindigkeit verbunden, die jedoch durch Migration<br />

und Replikation gerade verbessert werden soll.<br />

Die gewonnenen Erkenntnisse über den Einsatz von Schwellwerten und Auf-<br />

16


2.2 MIGRATIONSTRATEGIEN<br />

rufintervallen sind jedoch bei der Entwicklung von Migrationsstrategien im<br />

JScatter-System berücksichtigt worden.<br />

2.3 Capture-time based Thread Serialization<br />

In [BH2002] wird mit Capture-time based Thread Serialization (CTS) eine<br />

JVM vorgestellt, mit der Thread-Migration ohne Verlust von Zustandsinformationen<br />

des zu migrierenden Threads möglich ist.<br />

Die Migration von Threads ist ohne Verlust von Zustandsinformationen nur<br />

durch eine JVM möglich, über die der Zustand des zu migrierenden Threads<br />

ermittelt werden kann. Die Standard-JVM enthält keine solche Schnittstelle.<br />

Aus diesem Grund ist mit ihr keine verlustfreie Thread-Migration möglich.<br />

Die Werte der Variablen eines Threads werden innerhalb der Standard-JVM<br />

<strong>auf</strong> einem Stack gespeichert, <strong>auf</strong> den von außerhalb nicht zugegriffen werden<br />

kann. Da für Threads keine Migration in eine andere JVM vorgesehen ist,<br />

enthält der Stack auch keine Typinformationen über die <strong>auf</strong> ihm platzierten<br />

Daten. Die einzige Stelle an der die Typinformationen vorliegen ist der<br />

Bytecode der Methoden, die die Daten <strong>auf</strong> den Stack legen.<br />

Die einfachste Vorgehensweise, um an die Typinformationen zu gelangen ist,<br />

den Java-Interpreter für jede Bytecodeinstruktion zu verändern und die<br />

Typinformationen zu speichern, wenn sie <strong>auf</strong> den Stack gelegt werden. Der<br />

größte Nachteil bei dieser Vorgehensweise ist jedoch, dass die JVM geändert<br />

werden müsste, und durch das Speichern der Typinformationen ein großer<br />

Overhead entstände. Aus diesem Grund schlägt CTS einen anderen Weg ein<br />

und analysiert den Bytecode der Anwendung <strong>zur</strong> Migrationszeit, um an die<br />

Typinformationen zu gelangen.<br />

BOUCHENAK und HAGIMONT heben in [BH2002] hervor, dass Systeme, die<br />

Thread-Migration <strong>auf</strong> Anwendungsebene durchführen, neben dem Informationsverlust<br />

des Threadzustandes auch einen enormen Overhead an Quellcode<br />

<strong>auf</strong>zuweisen. Jedoch gestehen sie ein, dass der Austausch der Standard-JVM<br />

durch die CTS-JVM ein Nachteil ihres Ansatzes ist. Ein ausführlicher<br />

Vergleich der beiden Ansätze wird in [BH2002] durchgeführt.<br />

17


KAPITEL 2 VERWANDTE ARBEITEN<br />

18


3 VORÜBERLEGUNGEN<br />

3 VORÜBERLEGUNGEN<br />

Nachdem im Kapitel 2 einige verwandte Arbeiten vorgestellt wurden, werden<br />

in diesem Kapitel einige Überlegungen durchgeführt, die im Zusammenhang<br />

mit Migrationsstrategien und deren Realisierung benötigt werden.<br />

3.1 Migration und Replikation<br />

<strong>Objektmigration</strong>sstrategien beschäftigen sich damit, für eine gegebene Objektkonstellation<br />

bei der verteilten Ausführung einer Anwendung die Platzierung<br />

der Objekte so anzupassen, dass die Anwendung möglichst effizient ausgeführt<br />

werden kann. Ergibt sich beim Überprüfen der Platzierung eines Objektes, dass<br />

eine Änderung der Platzierung die Effizienz der Anwendung verbessern<br />

könnte, wird der Umzug des Objektes durch Migration durchgeführt.<br />

Wie aus diesen einleitenden Sätzen hervorgeht, sind Objekte der Untersuchungsgegenstand<br />

von Migrationsstrategien. Aus diesem Grund beschäftigt<br />

sich dieser Abschnitt im Folgenden mit Objekten sowie deren Migration bzw.<br />

Replikation.<br />

Beim Ausführen einer Anwendung werden Objekte im physikalischen Speicher<br />

eines Rechners platziert. Damit Objekte unterschieden werden können,<br />

besitzt jedes Objekt eine eindeutige Identität. Objekte haben Eigenschaften, die<br />

in ihren Attributen (auch Felder genannt) gespeichert sind. Die Attribute<br />

beschreiben den Zustand eines Objektes. Neben Eigenschaften besitzen<br />

Objekte auch noch Operationen, die auch als Methoden bezeichnet werden.<br />

Über die Operationen eines Objektes kann dessen Zustand entweder zugegriffen<br />

oder aber verändert werden. Objekte stehen über Referenzen mit<br />

anderen Objekten in Verbindung.<br />

Bei der Migration eines Objektes wird es aus dem physikalischen Speicher<br />

eines Rechners entfernt und in dem eines anderen Rechners neu platziert. Eine<br />

Sonderform der Migration ist die Replikation. Bei ihr wird im Gegensatz <strong>zur</strong><br />

Migration eine Kopie des Objektes im selben physikalischen Speicher erstellt<br />

und anschließend die Kopie in den eines anderen Rechners migriert. Kopien<br />

eines Objektes werden auch als Replikate bezeichnet. Alle Replikate und das<br />

Original-Objekt bilden eine Replikatgruppe. Die Objekte einer Replikatgruppe<br />

haben identische Eigenschaften aber unterschiedliche Identität. Wird<br />

der Zustand eines Objektes einer Replikatgruppe verändert, befinden sich die<br />

Objekte der Replikatgruppe in einem unterschiedlichen oder inkonsistenten<br />

Zustand. Da Replikatgruppen-Objekte die gleichen Eigenschaften haben, muss<br />

die Zustandsänderung an alle Mitglieder der Replikatgruppe weitergegeben<br />

werden.<br />

Die Migration wie auch die Replikation eines Objektes kann nicht zu jedem<br />

Zeitpunkt während der Programmausführung durchgeführt werden. Wenn ein<br />

Objekt migriert werden soll kann es vorkommen, dass zu diesem Zeitpunkt<br />

gerade eine oder mehrere Methoden des Objektes abgearbeitet werden. Damit<br />

19


KAPITEL 3 VORÜBERLEGUNGEN<br />

es bei der Durchführung der Migration nicht zu einem Fehler bei der<br />

Ausführung der Methode kommt, muss vor dem Umzug des Objektes solange<br />

gewartet werden, bis alle Methoden des Objektes abgearbeitet sind. Um diese<br />

für die Durchführung einer Migration notwendige Situation herzustellen,<br />

müssen alle Methoden<strong>auf</strong>rufe, die nach der Migrationsentscheidung <strong>auf</strong> das<br />

Objekt ausgeführt werden sollen solange warten, bis die Migration durchgeführt<br />

wurde. Anschließend sind die wartenden Methoden<strong>auf</strong>rufe <strong>zur</strong> neuen<br />

Platzierung des Objektes weiterzuleiten und <strong>auf</strong> dem migrierten Objekt<br />

auszuführen.<br />

Diese Situation wird in [Lin2002] als sicherer Zustand für die Durchführung<br />

einer <strong>Objektmigration</strong> bezeichnet. Dieser sichere Zustand muss analog auch<br />

vor dem Durchführen einer Objektreplikation erreicht werden. Den Zeitpunkt<br />

in dem sich ein Objekt in einem für seine Migration sicheren Zustand befindet<br />

bezeichnet LUX [Lux1995] als Migrationspunkt.<br />

Damit ein Objekt migriert werden kann, muss von dem Zustand des Objektes<br />

ein transportierbares Abbild erzeugt werden können, aus dem nach der<br />

Übertragung wieder ein Objekt mit identischem Zustand rekonstruiert werden<br />

kann. Dieser Vorgang wird Objektserialisierung genannt und im Abschnitt<br />

3.2 näher untersucht. Somit ist die Objektserialisierung eine notwendige<br />

Bedingung für die Migrierbarkeit bzw. Replizierbarkeit eines Objektes. Durch<br />

statische Programmanalysen können weitere Aussagen über die Migrierbarkeit<br />

bzw. Replizierbarkeit eines Objektes gemacht werden. Hiermit beschäftigt sich<br />

Abschnitt 3.4.<br />

3.2 Objektserialisierung<br />

In Abschnitt 3.1 wurde herausgearbeitet, dass Objektserialisierung eine Grundvoraussetzung<br />

für das Durchführen von Migration und Replikation ist. Bei<br />

Objektserialisierung handelt es sich um das Erstellen eines transportierbaren<br />

Abbildes eines Objektes, aus dem durch Deserialisierung ein Objekt mit identischem<br />

Zustand rekonstruiert werden kann. In diesem Abschnitt wird das<br />

Konzept Objektserialisierung vorgestellt und am Beispiel der JDK-Serialisierung<br />

dessen Vorgehensweise im JDK (Java Development Kit) erläutert.<br />

Der Zustand eines Objektes lässt sich in zwei Teile untergliedern. Zum<br />

sogenannten persistenten Zustand gehören alle Objektbestandteile, die auch<br />

nach der Deserialisierung des Objektes verfügbar sind. Alle anderen Objektbestandteile<br />

gehören zum nicht-persistenten Zustand eines Objektes. In einigen<br />

Programmiersprachen, z. B. Java, können Felder, die nur lokale Ressourcen<br />

wie Dateien oder temporale Werte enthalten, als transient gekennzeichnet<br />

werden. Sie bilden nur vorübergehend einen Teil des Objektzustandes oder<br />

können rekonstruiert werden und gehören deshalb zum nicht-persistenten<br />

Zustand eines Objektes. Somit kann an dieser Stelle festgehalten werden, dass<br />

Objektserialisierung das Speichern des persistenten Zustands eines Objektes<br />

ist.<br />

20


3.2 OBJEKTSERIALISIERUNG<br />

Bei der JDK-Serialisierung wird der Zustand des Objektes in einen byte-<br />

Strom serialisiert, aus dem durch Deserialisierung eine Kopie des Objektes im<br />

selben Zustand rekonstruiert werden kann. Mit der JDK-Serialisierung können<br />

jedoch nur Objekte serialisiert werden, die Java's Serialisierungs-Schnittstelle<br />

erfüllen. Hierfür muss jede Klasse eines Java-Objektes oder eine seiner<br />

Oberklassen das Interface Serializable oder dessen Subinterface<br />

Externalizable implementieren. Diese Objekte werden als serialisierbare<br />

Objekte bezeichnet.<br />

Bei der Standard-JDK-Serialisierung werden alle Felder eines Objektes in<br />

einen byte-Strom geschrieben, die in ihrer Klasse nicht als transient oder<br />

static gekennzeichnet sind. Enthält ein Feld ein Objekt muss es serialisierbar<br />

sein. Felder, die primitive Datentypen enthalten sind immer serialisierbar.<br />

Wenn das Objekt eines Feldes serialisiert wird, werden alle seine Felder<br />

serialisiert. Auf diese Weise wird der gesamte Objektgraph, der direkt oder<br />

indirekt mit einem zu serialisierenden Objekt verbunden ist mit serialisiert<br />

[Sun2001]. Sind bei der Serialisierung des Objektgraphen zyklische Strukturen<br />

durch mehrfache Referenzierung <strong>auf</strong> dasselbe Objekt enthalten, werden diese<br />

durch eine Hashtabelle <strong>auf</strong>gelöst [NPH1999]. Handelt es sich bei dem zu<br />

serialisierenden Objekt um einen Arraytyp, wird die Anzahl seiner Elemente<br />

und die Elemente analog wie bei Objekten bzw. primitiven Datentypen<br />

beschrieben serialisiert.<br />

Byte-Strom<br />

Byte-Strom<br />

serialisierte<br />

Daten<br />

Objekt<br />

Objekt (Kopie)<br />

Legende<br />

persistenter Zustand<br />

nicht-persistenter Zustand<br />

Abbildung 3.1: Serialisierung und Deserialisierung eines Objektes im JDK<br />

Bei der Deserialisierung des Zustandes eines Objektes werden alle Felder mit<br />

ihrem typ-spezifischen Initialwert initialisiert. Anschließend werden allen<br />

Feldern, die zum persistenten Zustand des Objektes gehören, die serialisierten<br />

Werte zugeordnet. In Abbildung 3.1 ist die Serialisierung und Deserialisierung<br />

zusammenfassend visualisiert.<br />

Es gibt jedoch auch Klassen, deren Objekte nicht serialisiert werden können.<br />

Ein Beispiel hierfür ist die Klasse Object, die die Wurzel aller Klassen bildet.<br />

Somit gibt es in der Klassenhierachie eines zu serialisierenden Objektes immer<br />

eine Oberklasse, dessen Objekte nicht serialisierbar sind. Da alle Klassen über<br />

21


KAPITEL 3 VORÜBERLEGUNGEN<br />

Konstruktoren und vererbte Klassenteile mit ihren Oberklassen verbunden<br />

sind, müssen diese Klassenbestandteile mit serialisiert werden. Damit Klassen<br />

mit nicht-serialisierbaren Oberklassen trotzdem serialisiert werden können,<br />

müssen ihre nicht-serialisierbaren Oberklassen einen accessible Konstruktor<br />

ohne Parameter besitzen. Das heißt, die Klasse muss einen Konstruktor<br />

definieren, der als public deklariert ist und keine Parameter enthält. Hierbei<br />

ist jedoch der Standard-Konstruktor nicht ausreichend, er muss konkret in der<br />

nicht-serialisierbaren Klasse definiert werden [GJS1996].<br />

Die accessible Konstruktoren ohne Parameter werden für die Deserialisierung<br />

eines Objektzustandes benötigt. Sie werden bei der Deserialisierung als erstes<br />

<strong>auf</strong>gerufen, anschließend werden die weiteren Bestandteile des Objektes<br />

deserialisiert.<br />

In verteilten Anwendungen wird Objektserialisierung sehr oft durchgeführt.<br />

Deshalb bietet eine nicht-effiziente Objektserialisierung ein großes Einsparpotential<br />

für die effiziente Ausführung einer verteilten Anwendung. PHILIPPSEN<br />

und HAUMACHER haben sich mit der Verbesserung der JDK-Serialisierung<br />

befasst und eine effizientere Objektserialisierung namens UKA-Serialisierung<br />

[PH1999] entwickelt. Sie baut <strong>auf</strong> der JDK-Serialisierung <strong>auf</strong>, ist jedoch 20% -<br />

50% schneller als die JDK-Serialisierung. Wie der Geschwindigkeitsvorteil<br />

erreicht wurde, kann in [PH1999] nachgelesen werden. In ihrer grundlegenden<br />

Vorgehensweise unterscheiden sich JDK-Serialisierung und UKA-Serialisierung<br />

nicht.<br />

3.3 Parameter von Migrationsstrategien<br />

Einige Migrationsstrategien besitzen Parameter, die den Entscheidungsprozess<br />

über Migration, Replikation oder die Beibehaltung der Platzierung eines<br />

Objektes beeinflussen. Diese Parameter stellen meist Schwellwerte dar die<br />

darüber Auskunft geben, ab oder bis zu welcher Anzahl von Zugriffen ein<br />

Objekt migriert oder repliziert wird.<br />

Wie entscheidend die Wahl des Schwellwertes für Migration das Verhalten<br />

eines verteilten Systems beeinflusst, wurde in [DW1994] hervorgehoben. Eine<br />

zu niedrige Wahl des Schwellwertes führt zu hoher zusätzlicher Belastung des<br />

verteilten Systems. Im Gegensatz dazu verhindert eine zu hohe Wahl des<br />

Schwellwertes die Leistungsverbesserung, die durch Migration und Replikation<br />

erreicht werden soll.<br />

Im Abschnitt 2.2.2 wurde schon dar<strong>auf</strong> eingegangen, dass einige Migrationsstrategien<br />

einen Replikationslevel besitzen, der die Höchstgrenze der im<br />

verteilten System befindlichen Replikate eines Objektes angibt. Findet <strong>auf</strong> ein<br />

Replikat ein Schreibzugriff statt, bestimmt der Replikationslevel und die<br />

hierdurch verursachte Größe der Zustandsänderung, wie hoch der Aufwand für<br />

den Datentransfer ist, um alle Replikate des Objektes im verteilten System<br />

konsistent zu halten.<br />

22


3.3 PARAMETER VON MIGRATIONSSTRATEGIEN<br />

Dieser Effekt macht deutlich, dass durch den Einsatz von Replikation nicht nur<br />

schnelle Zugriffe durch lokale Kopien von Objekten erreicht werden, sondern<br />

auch Leistungsverlust durch Aktualisieren der verteilten Replikate entsteht.<br />

Auch bei der Migration tritt ein ähnlicher Effekt <strong>auf</strong>. Wenn eine Migrationsstrategie<br />

entschieden hat ein Objekt zu migrieren, werden alle ehemals lokalen<br />

Zugriffe durch die Migration zu entfernten Zugriffen. Hierbei kann es<br />

vorkommen, dass nach der Migration die ehemals lokal zugreifenden Objekte<br />

mehr Datenverkehr verursachen als das ehemals entfernt zugreifende Objekt<br />

verursacht hat. Dieser Effekt muss beim Entwerfen einer Migrationsstrategie<br />

verhindert werden. Hierfür ist nicht nur <strong>auf</strong> die Wahl des Schwellwertes<br />

sondern auch <strong>auf</strong> dessen Einsatz in der Migrationsstrategie zu achten.<br />

Das Einstellen der optimalen Parameter für eine Migrationsstrategie ist eine<br />

schwierige Aufgabe. In [DW1994, Rei1997] wird hervorgehoben, dass die<br />

optimalen Parameter einer Migrationsstrategie erst nach mehrmaligem<br />

Ausführen für eine konkrete Anwendung ermittelt wurde. Demnach ist es nicht<br />

möglich, die Schwellwerte einer Migrationsstrategie so zu wählen, dass sie<br />

einmal eingestellt, für alle verteilt auszuführenden Anwendungen gleich gute<br />

Ergebnisse liefern, sondern individuell für jede Anwendung bestimmt werden<br />

müssen.<br />

3.4 Statische Programmanalyse<br />

Durch statische Programmanalyse können zum Einen schon vor der Ausführung<br />

einer Anwendung Aussagen über das Verhalten der Anwendung<br />

gemacht werden. Zum Anderen kann die Anwendung oder Teile der Anwendung<br />

<strong>auf</strong> spezielle Eigenschaften untersucht werden.<br />

In Zusammenhang mit Migrationsstrategien ist es deshalb möglich, vor der<br />

Ausführung, die Klassen einer Anwendung <strong>auf</strong> spezielle Eigenschaften zu<br />

untersuchen um festzustellen, ob die Instanzen der Klasse <strong>zur</strong> L<strong>auf</strong>zeit<br />

repliziert werden sollen, oder eine Migration der Replikation vorzuziehen ist.<br />

Eine Replikation sollte einer Migration vorgezogen werden, weil nach der<br />

Replikation alle Objekte der verteilten Anwendung lokal <strong>auf</strong> das replizierte<br />

Objekt zugreifen können jedoch durch Migration nur einige. Lokale Zugriffe<br />

<strong>auf</strong> ein Objekt sind mit weniger Datenverkehr verbunden als entfernte Zugriffe<br />

und steigern somit die Effizienz des Programms.<br />

Für diese Untersuchungen bieten sich die Immutable-Feldanalyse und die<br />

Immutable-Objektanalyse an.<br />

Mit der Immutable-Feldanalyse [Thi2001] kann bestimmt werden, ob die<br />

Felder einer Klasse nicht modifiziert werden und somit konstant sind. Objekte<br />

von Klassen, die während der gesamten Ausführung eines Programms nicht<br />

verändert werden bieten sich für die Replikation an.<br />

Mit der Immutable-Objektanalyse kann für eine gesamte Anwendung<br />

untersucht werden, ob die Instanz einer Klasse modifiziert wird. Hierfür setzt<br />

23


KAPITEL 3 VORÜBERLEGUNGEN<br />

die Immutable-Objektanalyse die Immutable-Feldanalyse und die globale Def-<br />

Use-Analyse ein.<br />

Mit der globalen Def-Use-Analyse werden alle Verwendungsstellen eines<br />

Feldes in der gesamten Anwendung gefunden. Verwendungsstellen eines<br />

Feldes sind alle Stellen einer Anwendung, an denen <strong>auf</strong> das Feld lesend oder<br />

schreibend zugegriffen wird. Die Erzeugungsstelle eines Feldes ist die Stelle<br />

einer Anwendung, an der das Feld vor der Initialisierung definiert wird.<br />

Die globale Def-Use-Analyse beginnt bei der Erzeugungsstelle des Feldes und<br />

ermittelt über die Methoden<strong>auf</strong>rufe <strong>auf</strong> das zu untersuchende Feld alle<br />

Verwendungsstellen des Feldes. Die Abbildung 3.2 zeigt ein Beispiel für das<br />

Finden aller Verwendungstellen eines Feldes mit Hilfe der globalen Def-Use-<br />

Analyse.<br />

5<br />

10<br />

15<br />

20<br />

class A {<br />

}<br />

int i;<br />

B b;<br />

A() {<br />

i = 5;<br />

b = new B(i);<br />

}<br />

getB() { return b; }<br />

class B {<br />

}<br />

int value;<br />

B(int i) { value = i; }<br />

getValue() { return value; }<br />

setVale(int i) { value = i; }<br />

25<br />

30<br />

35<br />

40<br />

45<br />

class C {<br />

}<br />

A a;<br />

B b1;<br />

C() {<br />

a = new A();<br />

b1 = a.getB();<br />

}<br />

m(boolean value, int i) {<br />

int x = 0;<br />

}<br />

Legende<br />

if(value)<br />

x = b1.getValue();<br />

else<br />

b1.setValue(i);<br />

...<br />

Erzeugungsstelle<br />

Verwendungsstelle (Schreibzugriff)<br />

Verwendungsstelle (Lesezugriff)<br />

Abbildung 3.2: Beispiel einer Def-Use-Analyse für Feld b der Klasse A<br />

Im Beispiel sind die Klassen A, B und C gegeben. Die Klasse C enthält ein Feld<br />

a vom Typ A und ein Feld b1 vom Typ B, die über den Konstruktor von C<br />

initialisiert werden. Des Weiteren enthält Klasse C eine Methode m. Die Klasse<br />

A enthält ein Feld i vom Typ int und ein Feld b vom Typ B sowie eine<br />

Methode getB über die <strong>auf</strong> b zugegriffen werden kann. Die Klasse B enthält<br />

ein Feld value vom Typ int und jeweils eine Methode über die lesend oder<br />

schreiben <strong>auf</strong> das Feld zugegriffen werden kann.<br />

Nun sollen für das Feld b der Klasse A alle Verwendungsstellen bestimmt<br />

werden. Die Def-Use-Analyse beginnt an der Erzeugungsstelle von b in der<br />

Klasse A. Anschließend sucht sie nach allen Stellen im Programm, an denen<br />

die Methode getB <strong>auf</strong> ein A-Objekt <strong>auf</strong>gerufen wird. Eine solche Stelle<br />

befindet sich im Konstruktor der Klasse C. An dieser Stelle wir dem Feld b1 die<br />

Speicherstelle von b zugewiesen. Nach dieser Zuweisung sind alle Verwendungsstellen<br />

von b1 auch potentielle Verwendungsstellen von b. Aus diesem<br />

24


3.4 STATISCHE PROGRAMMANALYSE<br />

Grund sucht die Def-Use-Analyse auch nach allen Verwendungsstellen von b1<br />

und findet sie in der Methode m der Klasse C. In der Methode m wird über die<br />

Fälle einer if-Anweisung einmal lesend und einmal schreibend <strong>auf</strong> b1<br />

zugegriffen. In der Immutable-Objektanalyse werden Stellen, die wie eine if-<br />

Anweisung von der Auswertung der Anweisung abhängen, konservativ<br />

abgeschätzt und somit als Schreibzugriffe interpretiert.<br />

In [Rei1997] wurde durch dynamische Analyse festgestellt, dass Objekte, bei<br />

denen höchstens 10% aller Zugriffe Schreibzugriffe sind sich besonders gut für<br />

Replikation eignen. Ist die Anzahl der Schreibzugriffe höher, wird der positive<br />

Effekt von lokalen Zugriffe verringert.<br />

Damit nicht unnötig <strong>auf</strong> Replikation verzichtet wird, weil die Anzahl der<br />

Verwendungsstellen mit Schreibzugriff sehr hoch ist, muss überprüft werden,<br />

in welchem Teil des Programms die Schreibzugriffe erfolgen. Finden die<br />

meisten Zugriffe bei der Initialisierung des Objektes statt, kann das Objekt<br />

trotzdem repliziert werden, wenn der Anteil alle weiteren Schreibzugriffe<br />

höchstens z. B. 10% beträgt.<br />

In Abschnitt 3.2 wurde herausgearbeitet, dass Thread-Migration nur durch eine<br />

erweiterte JVM ohne Verlust von Informationen möglich ist. Deshalb sollten<br />

aktive Objekte nicht <strong>auf</strong> Anwendungsebene migriert werden. In Java gibt es<br />

zwei Möglichkeiten um aktive Objekte zu erzeugen. Zum Einen durch<br />

Erweitern der Klasse Thread und zum Anderen durch Implementieren des<br />

Interfaces Runnable.<br />

Durch Klassenhierachieanalyse können die Oberklassen einer Klasse ermittelt<br />

werden. Wird durch Klassenhierachieanalyse festgestellt, dass eine Klasse eine<br />

Unterklasse von Thread ist oder Runnable implementiert, werden die<br />

Instanzen der Klasse <strong>zur</strong> L<strong>auf</strong>zeit weder migriert noch repliziert.<br />

Statische Programmanalysen sich sehr <strong>auf</strong>wändig. Deshalb sollten sie nicht<br />

während der L<strong>auf</strong>zeit, sondern vor der Ausführung des Programms durchgeführt<br />

werden, um die Effizienz der verteilt ausgeführten Anwendung nicht zu<br />

beeinträchtigen [TK1998 , Thi1999].<br />

3.5 Dynamische Programmanalyse<br />

Die dynamische Programmanalyse dient zum Beobachten des Verhaltens einer<br />

Anwendung <strong>zur</strong> L<strong>auf</strong>zeit. Hierbei werden Zählvariablen eingesetzt über die<br />

ermittelt werden kann, wie oft <strong>auf</strong> ein Objekt von einem anderen Objekt aus<br />

zugegriffen wurde. Einige Systeme, wie beispielsweise Juggle [SH1998] halten<br />

noch zusätzliche Zählvariablen. Zum Einen unterscheiden sie zwischen Leseund<br />

Schreibzugriffen und zum Anderen zwischen entfernten und lokalen<br />

Zugriffen <strong>auf</strong> ein Objekt.<br />

Neben dem Zählen der Ausführungshäufigkeit von Programmstellen wird<br />

dynamische Programmanalyse auch eingesetzt, um die Auslastung der CPU,<br />

die Verteilung von CPU-Zeiten bei der Nutzung verschiedener Programmteile<br />

25


KAPITEL 3 VORÜBERLEGUNGEN<br />

oder den Nutzungsgrad eines Caches zu bestimmen [RP1999].<br />

Die Auslastung der CPU wird in verteilten Betriebssystemen, wie Birlix<br />

[Lux1995] und Lastverwaltungssystemen [Lin2002] in Migrationsstrategien<br />

bei der Migrationsentscheidung eingesetzt. In Lastverwaltungssystemen werden<br />

jedoch auch die Auslastung des Hauptspeicher und der Plattenkapazität bei<br />

der Migrationsentscheidung betrachtet.<br />

Um die Information über die Belegung der Ressourcen eines Rechners im<br />

verteilten System präsent zu haben, werden sie beispielsweise über Broadcast<br />

verteilt [DW1994].<br />

Da die dynamische Programmanalyse <strong>zur</strong> L<strong>auf</strong>zeit ausgeführt wird, beeinträchtigt<br />

sie die effektive Ausführung einer verteilten Anwendung. Damit die<br />

Beeinträchtigung so gering wie möglich ist, werden Zählvariablen erst dann<br />

erzeugt, wenn sie benötigt werden. Juggle [SH1998] führt aus diesem Grund<br />

die Instrumentierung der verteilt ausgeführten Anwendung erst dann durch,<br />

wenn die Daten notwendig sind. Auf diese Weise wird nicht nur L<strong>auf</strong>zeit<br />

sondern auch Speicher gespart.<br />

26


4 DAS JSCATTER-SYSTEM<br />

4 DAS JSCATTER-SYSTEM<br />

In diesem Kapitel wird das JScatter-System [FFF+2002] vorgestellt, in dem die<br />

Realisierung des entwickelten Modells für Migrationsstrategien durchgeführt<br />

wurde. Ziel dieses Kapitels ist es, einen grundlegenden Überblick über die<br />

Struktur des Systems zu vermitteln und einen Einblick in den Aufbau und die<br />

Vorgehensweise der Teilkomponenten des Systems zu geben.<br />

Mit dem JScatter-System 1 ist es möglich, eine nebenläufige Java-Anwendung<br />

transparent für den Benutzer so zu transformieren, dass sie unter Verwendung<br />

mehrerer Rechner in der JScatter-L<strong>auf</strong>zeitumgebung verteilt ausgeführt werden<br />

kann.<br />

Für die Transformation besitzt das System einen Transformator, der in Abschnitt<br />

4.2 näher beschrieben wird. Der Transformator benötigt als Eingabe<br />

den Bytecode der zu transformierenden Anwendung inklusive aller verwendeten<br />

Bibliotheken, die nicht <strong>zur</strong> Java-Standard-Bibliothek gehören. Zur Ausführung<br />

des transformierten Programms enthält das JScatter-System eine eigene<br />

L<strong>auf</strong>zeitumgebung, <strong>auf</strong> die im Abschnitt 4.3 näher eingegangen wird.<br />

Für die Initialplatzierung der einzelnen Objekte dient ein eigens hierfür von<br />

KARSTEN KLOHS [Klo2002] entwickelter Verteilungsplan, der <strong>auf</strong> statischer<br />

Programmanalyse beruht und im Abschnitt 4.4 vorgestellt wird.<br />

vor der L<strong>auf</strong>zeit<br />

<strong>zur</strong> L<strong>auf</strong>zeit<br />

Java-Virtual-<br />

Machine<br />

Java-Virtual-<br />

Machine<br />

Rechnerknoten<br />

Rechnerknoten<br />

Verteilungsplan<br />

Verteilungsplan<br />

Java-Anwendung<br />

(Bytecode)<br />

Transformator Verteilungsplan<br />

L<strong>auf</strong>zeitumgebung<br />

Java-Virtual-<br />

Machine<br />

Java-Virtual-<br />

Machine<br />

Rechnerknoten<br />

Rechnerknoten<br />

Verteilungsplan<br />

Verteilungsplan<br />

Abbildung 4.1: Die Struktur des JScatter-Systems [FFF+2002]<br />

In Abbildung 4.1 ist die grobe Struktur von JScatter und das Zusammenspiel<br />

seiner Teilkomponenten dargestellt.<br />

1 JScatter wurde ursprünglich unter dem Namen ParJava [FFF+2002] entwickelt.<br />

27


KAPITEL 4 DAS JSCATTER-SYSTEM<br />

Damit die transformierte Anwendung in der verteilten L<strong>auf</strong>zeitumgebung genauso<br />

ausgeführt werden kann, wie die Originalanwendung in einer zentralen<br />

JVM, war es notwendig, jede Klasse der Originalanwendung so zu zerlegen,<br />

dass die Semantik der Ergebnisklassen in der verteilten L<strong>auf</strong>zeitumgebung der<br />

Semantik einer Originalklasse entsprechen. Hierfür wurde ein Transformationskonzept<br />

entwickelt das sicherstellt, dass der Klassenanteil einer Originalklasse<br />

in der verteilten L<strong>auf</strong>zeitumgebung nur einmal vorhanden ist, der<br />

Instanzenanteil jedoch mehrfach <strong>auf</strong> jedem Rechner vorhanden sein und von<br />

entfernten Rechnern aus eindeutig identifiziert und zugegriffen werden kann.<br />

Das Transformationskonzept, welches sowohl die Vorgehensweise des Transformators<br />

als auch die Struktur der L<strong>auf</strong>zeitumgebung beeinflusst hat, wird im<br />

anschließenden Abschnitt 4.1 vorgestellt.<br />

4.1 Transformation einer Java-Klasse<br />

Die Transformation einer Java-Klasse, zu ihrer Repräsentation in einer verteilten<br />

L<strong>auf</strong>zeitumgebung, ist eine nicht triviale Aufgabe. Das im JScatter-System<br />

verwendete Konzept basiert <strong>auf</strong> dem Transformationskonzept einer Java-<br />

Klasse des JavaParty-Systems (siehe Abschnitt 2.1). Im Unterschied zu Java-<br />

Party zerlegt es eine Java-Klasse jedoch nicht in sechs Klassen und sieben<br />

Interfaces sondern nur in vier Klassen und zwei Interfaces (siehe Abbildung<br />

4.2), deren Aufgaben nun im Einzelnen erläutert werden.<br />

Abbildung 4.2: Klassen<strong>auf</strong>teilung einer Klasse Example<br />

In Abbildung 4.2 sind die Ergebnisklassen der Transformation einer Klasse<br />

namens Example dargestellt. Bei der Transformation einer Klasse entsteht<br />

28<br />

Example<br />

(Originalklasse)<br />

Example<br />

(Instanzenanteil)<br />

_ExampleHandel<br />

(dient als Proxy und ermöglicht<br />

den entfernten Zugriff <strong>auf</strong> ein<br />

Example-Objekt)<br />

_ExampleInstanceManager<br />

(verwaltet alle Example-<br />

Objekte <strong>auf</strong> einem<br />

Rechnerknoten)<br />

_ExampleClassObject<br />

(Klassenanteil)<br />

Legende<br />

Klasse<br />

Interface<br />

Klasse zerlegt in ...<br />

Klasse erweitert Klasse<br />

Klasse implementiert Interface<br />

_ExampleInstanceManagerInterface<br />

(erfüllende RMI-Bedingung)<br />

_ExampleClassObjectInterface<br />

(erfüllende RMI-Bedingung)


4.1 TRANSFORMATION EINER JAVA-KLASSE<br />

eine gleichnamige Klasse, die jedoch nur die Instanzenanteile der Originalklasse<br />

enthält. Diese neue Klasse Example repräsentiert in der transformierten<br />

Anwendung den Instanzenanteil der Originalklasse. Die Felder und Methoden,<br />

die in der Originalklasse zum Klassenanteil gehören, bilden die Felder und Methoden<br />

der Klasse _ExampleClassObject. Sie stellt in der transformierten<br />

Anwendung den Klassenanteil der Originalklasse Example dar.<br />

Im Unterschied <strong>zur</strong> Originalklasse sind die Felder und Methoden jedoch nicht<br />

mehr statisch, sondern werden durch gleichnamige Instanzenfelder und<br />

Instanzmethoden ersetzt. Diese Ersetzung des statischen durch Instanzenanteil<br />

war notwendig, da statischer Klassenanteil in einer JVM nur einmal vorhanden<br />

ist, die verteilte L<strong>auf</strong>zeitumgebung jedoch aus mehreren JVMs besteht, die <strong>auf</strong><br />

verschiedenen Rechnern verteilt platziert sind.<br />

In der verteilten L<strong>auf</strong>zeitumgebung darf der Klassenanteil einer Klasse auch<br />

nur einmal vorhanden sein, jedoch kann er in verschiedenen JVMs der verteilten<br />

L<strong>auf</strong>zeitumgebung für Zugriffe benötigt werden. Aus diesem Grund<br />

steht in allen JVMs ein Stellvertreter <strong>auf</strong> die Klassenanteil-repräsentierende<br />

Instanz _ExampleClassObject <strong>zur</strong> Verfügung. Da die Kommunikation zwischen<br />

Stellvertreter und Original über RMI durchgeführt wird, wird für die<br />

Beispielklasse ein Interface namens _ExampleClassObjectInterface<br />

generiert, das die entfernte Schnittstelle <strong>auf</strong> das _ExampleClassObject<br />

definiert.<br />

besitzt<br />

1 ..*<br />

besitzt<br />

1<br />

Object<br />

Example<br />

_ExampleHandel<br />

InstanceManager<br />

_ExampleInstanceManager<br />

Serializable<br />

Handle<br />

_ExampleInstanceManagerInterface<br />

Anmerkung: Um die Verbindungen der Klassen zu verdeutlichen,<br />

wurden die Assoziationen zwischen _ExampleHandel und _Example-<br />

InstanceManager und _ExampleInstanceManager und Example<br />

zusätzlich eingezeichnet.<br />

UnicastRemoteObject<br />

Remote<br />

_ExampleClassObjectInterface<br />

Abbildung 4.3: Klassenhierachie der Klasse Example<br />

ClassObject<br />

Legende<br />

X Y<br />

X Y<br />

n<br />

X Y<br />

_ExampleClassObject<br />

So wie in der Originalanwendung mehrere Instanzen einer Klasse in einer JVM<br />

erzeugt werden können, muss dies in einer verteilten L<strong>auf</strong>zeitumgebung<br />

möglich sein. Aus diesem Grund wird für die Originalklasse eine Klasse mit<br />

Klasse<br />

Interface<br />

X erweitert Y<br />

X implementiert Y<br />

X assoziiert n Y<br />

29


KAPITEL 4 DAS JSCATTER-SYSTEM<br />

Suffix InstanceManager generiert, die alle Instanzenanteil-repräsentierenden<br />

Objekte der Originalklasse pro Rechner verwaltet. Somit ist <strong>zur</strong> L<strong>auf</strong>zeit<br />

<strong>auf</strong> jeden beteiligten Rechner der verteilten L<strong>auf</strong>zeitumgebung genau eine<br />

Verwaltungsinstanz pro Originalklasse vorhanden.<br />

Damit <strong>auf</strong> ein entfernt platziertes Instanzenanteil-repräsentierendes Objekt<br />

auch über seine Verwaltungsinstanz zugegriffen werden kann, wurde eine<br />

Stellvertreterklasse mit Suffix Handle geschaffen, deren Instanzen <strong>zur</strong><br />

L<strong>auf</strong>zeit jeweils über eine Referenz zum zugehörigen InstanceManager<br />

verfügen. Über diese Referenz leitet ein Handle <strong>zur</strong> L<strong>auf</strong>zeit Zugriffe <strong>auf</strong> das<br />

Instanzenanteil-repräsentierende Objekt weiter.<br />

Eine ausführliche Beschreibung des Klassentransformationskonzepts kann in<br />

[FFF+2002] nachgelesen werden.<br />

In der Abbildung 4.3 ist beispielhaft für die Klasse Example, die nach der<br />

Transformation bestehende Klassenhierachie dargestellt. Hierbei ist im Zusammenhang<br />

mit Abbildung 4.2 zu sehen, dass Klassen mit dem Suffix<br />

Handle, InstanceManager und ClassObject gemäß ihren Suffixen<br />

gleichnamige Klassen erweitern.<br />

4.2 Transformator<br />

Der Transformator erzeugt aus einer übersetzten Java-Anwendung, unter<br />

Verwendung des in Abschnitt 4.1 erläuterten Transformationskonzeptes, eine<br />

Java-Anwendung, die im JScatter-L<strong>auf</strong>zeitsystem verteilt ausgeführt werden<br />

kann.<br />

Für die Arbeit mit Java-Klassen setzt der Transformator die Bibliothek BCEL<br />

[Dah1998, Dah2001] und die Analyseumgebung Pauli [Thi2001] ein. Bei der<br />

Transformation einer Java-Anwendung werden Instruktionen zum Bytecode<br />

der Klassen hinzugefügt, über die <strong>zur</strong> L<strong>auf</strong>zeit mit einem Verteilungsplan<br />

(siehe Abschnitt 4.4) die Rechnerknoten bestimmt werden, <strong>auf</strong> denen ein<br />

Objekt platziert wird.<br />

Der Transformationsprozess besteht aus vier Phasen (siehe Abbildung 4.4). In<br />

der ersten Phase, der Hüllenbildung, wird die Menge aller Klassen und<br />

Methoden der Eingabeanwendung sowie der notwendigen Bibliotheken<br />

inklusive der Java-Standard-Bibliothek <strong>auf</strong> den minimal <strong>zur</strong> Ausführung<br />

benötigten Umfang reduziert [FFF+2002].<br />

Die Hüllenbildung wird mit einem Werkzeug namens Jode [JSN2002] durchgeführt.<br />

Jode benötigt als Eintrittspunkte die Namen der Methoden einer<br />

Klassen, von denen aus die Anwendung ausgeführt wird.<br />

In der zweiten Phase, der sogenannten Stellenersetzung, werden alle zu transformierenden<br />

Stellen untersucht, an denen entweder ein neues Objekt erzeugt<br />

wird (Erzeugungsstelle) oder ein erzeugtes Objekt verwendet wird (Verwendungsstelle).<br />

In der dritten Phase generiert der Transformator alle Klassen,<br />

30


nach dem in Abschnitt 4.1 beschriebenen Transformationskonzept.<br />

Java-Anwendung<br />

Class C<br />

Class ................ B<br />

................ ................<br />

Class A<br />

................<br />

................<br />

Abbildung 4.4: Grober Abl<strong>auf</strong> der Transformation<br />

4.2 TRANSFORMATOR<br />

Bei Verwendungstellen muss in der dritten Phase dar<strong>auf</strong> geachtet werden, ob<br />

es sich bei dem Zugriff um ein lokal oder entfernt platziertes Objekt handelt.<br />

Eine Erzeugungsstelle wird in der dritten Phase mit speziellem Code angereichert,<br />

durch den die Initialplatzierung des Objektes <strong>zur</strong> L<strong>auf</strong>zeit festgelegt<br />

wird. In der den Transformationsprozess abschließenden vierten Phase, werden<br />

alle für die RMI-Kommunikation benötigten Stub- und Skeletonklassen<br />

erzeugt.<br />

4.3 L<strong>auf</strong>zeitumgebung<br />

Transformator<br />

Phase 1: Hüllenbildung<br />

Phase 2: Stellenersetzung<br />

Phase 3: Klassen erzeugen<br />

Java-Anwendung ausführbar in<br />

JScatter-L<strong>auf</strong>zeitumgebung<br />

Class C<br />

Class A<br />

Skeleton ................ Class B<br />

................<br />

................ ................ Terface ................<br />

................ _CClassObject<br />

................ ................ ................Stub<br />

.............................. Manager<br />

................ ................<br />

..............................<br />

................<br />

................<br />

..............................<br />

................<br />

Phase 4: Stubs und Skeletons erzeugen<br />

Bibliotheken<br />

Die Aufgabe der L<strong>auf</strong>zeitumgebung ist es, die transformierte Java-Anwendung<br />

transparent für den Benutzer unter Verwendung aller beteiligten Rechner<br />

verteilt auszuführen. Hierfür verbindet die L<strong>auf</strong>zeitumgebung, die <strong>auf</strong> den verschiedenen<br />

Rechnern vorhandenen JVMs miteinander. Die JVMs werden <strong>zur</strong><br />

L<strong>auf</strong>zeit über die Instanzen der Klasse Node repräsentiert. Die Nodes kommunizieren<br />

über RMI miteinander. Die zentrale Instanz der verteilten L<strong>auf</strong>zeitumgebung<br />

stellt die Klasse RuntimeManger dar, an die <strong>zur</strong> L<strong>auf</strong>zeit alle<br />

Fehlermeldungen der beteiligten JVMs sowie der ausgeführten Anwendung<br />

weitergeleitet werden.<br />

Damit von jeder an der verteilten Ausführung des transformierten Programmes<br />

beteiligten JVM aus ein Objekt entfernt platziert werden kann, ist in jeder JVM<br />

eine Instanz des Verteilungsplans (siehe Abschnitt 4.4) enthalten.<br />

Neben dem Verbinden der beteiligten Rechner dient die L<strong>auf</strong>zeitumgebung<br />

auch als Schnittstelle zum verteilt auszuführenden Programm. Hierfür enthält<br />

Jode<br />

Pauli<br />

BCEL<br />

Verteilungsplan<br />

RMIC-Tool<br />

31


KAPITEL 4 DAS JSCATTER-SYSTEM<br />

sie eine Klasse RuntimeEnvironment, von der <strong>zur</strong> L<strong>auf</strong>zeit pro JVM genau<br />

eine Instanz vorhanden ist. Diese Schnittstelle wird vom verteilt auszuführenden<br />

Programm verwendet, um <strong>auf</strong> die Instanzen- und Klassenanteil-repräsentierenden<br />

Objekten zuzugreifen. Hierbei können die Zugriffe sowohl lokal als<br />

auch entfernt durchgeführt werden. Auf die Besonderheiten bei einem Zugriff<br />

wurde schon im Abschnitt 4.2 eingegangen. Eine graphische Darstellung der<br />

geschilderten Aufgaben und Vorgehensweisen der verteilten L<strong>auf</strong>zeitumgebung<br />

stellt Abbildung 4.5 dar.<br />

Abbildung 4.5: Vorgehensweise der L<strong>auf</strong>zeitumgebung mit zwei Rechnern<br />

4.4 Verteilungsplan<br />

Wie am Anfang des Kapitels beschrieben, bestimmt der Verteilungsplan<br />

[Klo2002] <strong>auf</strong> <strong>Grundlage</strong> statischer Programmanalyse die Initialplatzierung der<br />

Objekte der im JScatter-System verteilt auszuführenden Anwendung.<br />

Bereits nach der Transformation der Anwendung (siehe Abschnitt 4.2) enthält<br />

das transformierte Programm an allen Stellen, an denen ein Objekt initial<br />

platziert wird, einen Aufruf an den lokalen Verteilungsplan. Das Ergebnis der<br />

Anfrage bestimmt, ob das Objekt lokal oder entfernt platziert wird. Die L<strong>auf</strong>zeitumgebung<br />

führt <strong>zur</strong> L<strong>auf</strong>zeit die Platzierung des Objektes durch. Die<br />

Abbildung 4.6 stellt den Abl<strong>auf</strong> bei der Platzierung eines Objektes durch den<br />

Verteilungsplan im JScatter-System dar.<br />

In der aktuellen Version des Verteilungsplans stehen verschiedene Verteilungsstrategien<br />

bereit, die nach belieben ausgetauscht werden können.<br />

Der Verteilungsplan verfügt über eine Schnittstelle, über die bei Verteilungsstrategien<br />

auch Ergebnisse aus dynamischer Programmanalyse (siehe Abschnitt<br />

3.2.5) genutzt werden können. So kann eine Verteilungsstrategie, <strong>auf</strong> Grund-<br />

32<br />

Node<br />

Rechner A<br />

Klassenanteilrepräsentierende<br />

Objekte<br />

Instanzenanteilrepräsentierende<br />

Objekte<br />

Node_Stub<br />

Node_Skeleton<br />

RuntimeManager_Stub<br />

RuntimeEnvironment<br />

Verteilungsplan<br />

L<strong>auf</strong>zeitumgebung<br />

Instanzenanteilrepräsentierende<br />

Objekte<br />

Node_Skeleton<br />

Node_Stub<br />

Node_Stub<br />

Klassenanteilrepräsentierende<br />

Objekte<br />

Rechner B<br />

RuntimeManager<br />

Node<br />

Node_Stub<br />

RuntimeManager_Skeleton<br />

Verteilungsplan<br />

RuntimeEnvironment<br />

RuntimeManager_Stub


4.4 VERTEILUNGSPLAN<br />

lage der schon im System platzierten Objekte, ein initial zu platzierendes<br />

Objekt <strong>auf</strong> einem entfernten Rechnerknoten platzieren, um beispielsweise andere<br />

Rechnerknoten von zusätzlichen Fernzugriffen zu entlasten [Klo2002].<br />

vor der L<strong>auf</strong>zeit<br />

während der Transformation<br />

<strong>zur</strong> L<strong>auf</strong>zeit<br />

Rechner 1<br />

Verteilungsplan<br />

Class A<br />

................<br />

................<br />

................<br />

RuntimeEnvironment<br />

Node<br />

Transformator<br />

Class C<br />

Class<br />

................<br />

B<br />

................<br />

Class ................ A<br />

................ ................<br />

................<br />

................ ................<br />

................<br />

................<br />

................<br />

L<strong>auf</strong>zeitumgebung<br />

Node<br />

C<br />

Verteilungsplan<br />

Rechner 2<br />

Verteilungsplan<br />

RuntimeEnvironment<br />

Class B<br />

................<br />

................<br />

................<br />

Legende<br />

.............. Bytecode<br />

Zusätzlicher Code für<br />

Verteilungsplan<strong>auf</strong>ruf<br />

Referenz zwischen Objekten<br />

im JScatter-L<strong>auf</strong>zeitsystem<br />

Referenz zwischen Objekten<br />

im JScatter-L<strong>auf</strong>zeitsystem<br />

über RMI-Schnittstelle<br />

L<strong>auf</strong>zeitsystem-Objekt<br />

Objekt der parallele<br />

ausführbaren Anwendung<br />

im JScatter-System<br />

Abl<strong>auf</strong>:<br />

Beim Abarbeiten einer Methode von<br />

Objekt A <strong>auf</strong> Rechner 1 bestimmt<br />

der Verteilungsplan, ein Objekt C <strong>auf</strong><br />

Rechner 2 zu erzeugen. Über das<br />

RuntimeEnvironment <strong>auf</strong> Rechner 1<br />

und den Nodes <strong>auf</strong> Rechner 1 und 2<br />

erzeugt die L<strong>auf</strong>zeitumgebung das<br />

Objekt C <strong>auf</strong> Rechner 2.<br />

Abbildung 4.6: Initialplatzierung eines Objektes durch den Verteilungsplan<br />

Für statische Programmanalyse, die im Abschnitt 3.2 behandelt wurde, setzt<br />

der Verteilungsplan die Analyseumgebung Pauli [Thi2001] ein. Mittels Pauli<br />

kann eine große Anzahl statischer Programmanalysen durchgeführt werden,<br />

<strong>auf</strong> die in Kapitel 6 eingegangen wird.<br />

Für eine genauere Beschreibung des Verteilungsplans und die Vorgehensweise<br />

von Verteilungsstrategien kann in [Klo2002, KLK2002] nachgelesen werden.<br />

33


KAPITEL 4 DAS JSCATTER-SYSTEM<br />

34


5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

5.1 Transformation<br />

In Kapitel 4 wurde dar<strong>auf</strong> eingegangen, dass im JScatter-System ein sequentielles<br />

Programm durch Transformationen in ein im JScatter-L<strong>auf</strong>zeitsystem<br />

parallel ausführbares Programm transformiert wird. Ziel dieses Abschnittes ist<br />

es herauszuarbeiten, wie statische Programmanalyse beim Transformationsprozess<br />

eingesetzt wird, um die Klassen des zu transformierenden Programms<br />

so vorzubereiten, dass deren Instanzen nach Abschluss der Transformation im<br />

JScatter-L<strong>auf</strong>zeitsystem migriert bzw. repliziert werden können.<br />

Wie in Abschnitt 2.3 dargestellt wurde, ist es in der Programmiersprache Java<br />

außerhalb der JVM nicht möglich, aktive Objekte (Threads) ohne Verlust von<br />

Informationen zu migrieren. Deshalb unterstützt das JScatter-System keine<br />

Thread-Migration. Durch diese Festlegung muss am ursprünglichen Transformationskonzept<br />

für Klassen, deren Instanzen ein aktive Objekte repräsentieren,<br />

keine Änderungen vorgenommen werden. Alle anderen Klassen müssen<br />

jedoch speziell für Migration bzw. Replikation vorbereitet werden.<br />

Um aktive von passiven Objekten zu unterscheiden, wird Klassenhierachieanalyse<br />

eingesetzt, deren Vorgehensweise im Abschnitt 3.4 vorgestellt wurde.<br />

Für alle Klassen deren Instanzen sich <strong>zur</strong> L<strong>auf</strong>zeit passiv verhalten, wurde ein<br />

einheitliches Transformationskonzept <strong>zur</strong> Unterstützung von Migration und<br />

Replikation entwickelt. Ob ein passives Objekt jedoch <strong>zur</strong> L<strong>auf</strong>zeit migriert<br />

oder repliziert wird, entscheidet die eingesetzte Migrationsstrategie (siehe<br />

Abschnitt 5.3).<br />

Durch statische Programmanalyse werden Migrationsstrategien <strong>auf</strong> Informationen<br />

<strong>zur</strong> Verfügung gestellt, die als Entscheidungshilfe für Migration oder<br />

Replikation eingesetzt werden können. Die in [MC19992, Rei1997, SH1998,<br />

Lin2002] beschriebenen <strong>Strategien</strong> nutzen ausschließlich Informationen aus<br />

dynamischer Programmanalyse. Somit stellen Migrationsstrategien, die zusätzlich<br />

auch Ergebnisse aus statische Programmanalyse als Entscheidungshilfe<br />

einsetzen, eine Neuerung im Bereich der Migrationsstrategien dar.<br />

JScatter stellt durch statische Programmanalyse fest, ob die Replikation der<br />

Migration eines Objektes vorzuziehen ist. Ein solcher Fall liegt vor, wenn ein<br />

Objekt nur oder größtenteils unveränderbare bzw. konstante Felder enthält,<br />

oder <strong>auf</strong> die Felder eines Objektes nur selten schreibend zugegriffen wird. Um<br />

unveränderbare oder konstante Felder in einer Klasse zu identifizieren kommt<br />

die im Abschnitt 3.2.3 beschriebene Immutable-Analyse zum Einsatz. Mit der<br />

Immutable-Objektanalyse (siehe Abschnitt 3.4) werden Objekte, die selten<br />

schreibend zugegriffen werden erkannt und der Migrationsstrategie <strong>zur</strong><br />

Replikation vorgeschlagen.<br />

Wie die Ergebnisse aus der statischen Programmanalyse einer Migrationsstrategie<br />

<strong>zur</strong> Verfügung gestellt werden und die Transformation insgesamt<br />

35


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

realisiert ist, wird in Kapitel 6 beschrieben.<br />

5.2 Migration und Replikation<br />

Das Migrationskonzept basiert <strong>auf</strong> dem in Kapitel 4 vorgestellten Klassentransformations-Konzept<br />

des JScatter-Systems. Da bei der Klassentransformation<br />

einer Original-Klasse zwischen Instanzen- und Klassenanteil unterschieden<br />

wird, ist es sowohl bei der Migration als auch bei der Replikation<br />

notwendig, für Instanzen- und Klassenanteil unterschiedliche Konzepte zu<br />

verwenden.<br />

Im Folgenden werden zuerst die Konzepte für die Migration und anschließend<br />

die der Replikation vorgestellt.<br />

5.2.1 MIGRATION<br />

Die grundlegende Vorgehensweise bei der Migration eines Objektes kann in<br />

sieben Schritte untergliedert werden:<br />

1. Sperren des zu migrierenden Objektes<br />

2. Warten, bis alle vor der Migration begonnenen Zugriffe <strong>auf</strong> das zu<br />

migrierenden Objekt abgearbeitet sind<br />

3. Objektzustand serialisieren<br />

4. serialisierten Objektzustand zum Rechner der neuen Platzierung übertragen<br />

5. Objektzustand deserialisieren und Objekt neu Platzieren<br />

6. Objekt an der alten Platzierung durch Stellvertreter des migrierten Objektes<br />

ersetzen<br />

7. Referenzaktualisierung durchführen und alle <strong>auf</strong>gehaltenen Zugriffe <strong>auf</strong> das<br />

Objekt an die neue Platzierung weiterleiten<br />

In Systemen bei denen neben der Migration auch noch die Replikation von<br />

Objektes unterstützt wird, sind für die im System platzierten Replikate<br />

<strong>Strategien</strong> für ihre Behandlung während der Migration des Original-Objektes<br />

vorzusehen. Hierbei sind drei <strong>Strategien</strong> vorstellbar.<br />

Bei der ersten Strategie werden vor der Serialisierung des zu migrierenden<br />

Objektes alle im System vorhandenen Replikate im System gelöscht. Somit ist<br />

sichergestellt, dass nach der Migration sich an der neuen Platzierung des<br />

Objektes nicht gleichzeitig ein Replikat des Objektes befindet.<br />

Bei der zweiten Strategie ist nur das Replikat an der neuen Platzierung des zu<br />

migrierenden Objektes zu löschen. Diese Strategie hat den Vorteil, dass auch<br />

während der Migration des Original-Objektes über seine Replikate <strong>auf</strong> dessen<br />

Objektzustand lesend zugegriffen werden kann. Nach der Migration sind<br />

jedoch alle Replikate von der neuen Platzierung des Original-Objektes zu<br />

unterrichten. Auf diese Weise wird sichergestellt, dass Schreibzugriffe auch<br />

36


5.2 MIGRATION UND REPLIKATION<br />

nach der Migration des Objektes an die gültige Platzierung des Objektes<br />

delegiert werden.<br />

Die dritte Strategie ist eine Erweiterung der zweiten Strategie. Enthält ein zu<br />

migrierendes Objekt <strong>auf</strong> dem Rechner zu dem es migriert werden soll bereits<br />

ein Replikat, so wird das Replikat zum Original erklärt und alle anderen<br />

Replikate davon benachrichtigt. In dieser Strategie werden nur Kosten für das<br />

Setzen des Replikats als Original und für die Benachrichtigung aller anderen<br />

im System befindlichen Replikate der Replikatgruppe verursacht. Somit ist sie<br />

von den drei vorgestellten die effizienteste Strategie.<br />

Bei diesen Überlegungen muss jedoch auch bedacht werden, wann es<br />

überhaupt sinnvoll ist, ein Objekt zu migrieren, wenn es schon Replikate des<br />

Objektes im System gibt. Hierbei kann festgestellt werden, dass es in dieser<br />

Situation nur sinnvoll ist ein Objekt zu migrieren, wenn es häufig schreibend<br />

zugegriffen wird. Häufige Schreibzugriffe <strong>auf</strong> ein repliziertes Objekt<br />

veranlassen jedoch Kosten <strong>zur</strong> Aktualisierung der Replikate. Aus diesem<br />

Grund wurde im JScatter-System die erste Strategie, bei der alle Replikate<br />

eines Objektes vor dessen Migration gelöscht werden, favorisiert.<br />

Im folgenden Teil des Abschnittes wird der Abl<strong>auf</strong> einer Migration im JScatter<br />

beschrieben. Hierbei wird <strong>zur</strong> Verdeutlichung der Zusammenhänge eine etwas<br />

technischere Terminologie verwendet.<br />

Wie in Kapitel 4 erläutert, repräsentiert ein Cuckoo-Objekt den Instanzenanteil<br />

eines Objektes der sequentiellen Original-Anwendung. In der Abbildung 5.1 ist<br />

die Ausgangssituation einer Migration von Cuckoo-Objekt B1 von Rechner 2<br />

nach Rechner 1 zu sehen.<br />

Rechner 1<br />

A B 1 _Handle<br />

B_InstanceManager_Stub<br />

C<br />

Rechner 3<br />

B 1 _Handle<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B_InstanceManager<br />

Abbildung 5.1: Situation vor dem Umzug von Objekt B1<br />

B 2<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

Auf Rechner 1 befindet sich ein Cuckoo-Objekt A, das über ein lokales<br />

B 1<br />

B_InstanceManager_Stub<br />

RMI / Netzwerk<br />

37


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

B1_Handle, ein B_InstanceManager-Schnittstelle-Paar (Stub und Skeleton)<br />

und den <strong>auf</strong> Rechner 2 platzierten B_InstanceManager B1 <strong>auf</strong> Rechner 2<br />

referenziert. Analog stehen die Cuckoo-Objekte C <strong>auf</strong> Rechner 3 mit B1 und D<br />

<strong>auf</strong> Rechner 4 mit B2 <strong>auf</strong> Rechner 2 in referenzieller Verbindung.<br />

In dieser Situation wird von einer Migrationsstrategie der Umzug des Objektes<br />

B1 von Rechner 2 nach Rechner 1 durchgeführt. Hierzu wird B1 serialisiert, <strong>auf</strong><br />

Rechner 1 übertragen und lokal als Objekt B1' platziert. Damit alle Zugriffe<br />

über die veraltete B1-Referenz nach der Migration an B1' weitergeleitet<br />

werden, wird die Referenz <strong>auf</strong> B1 im B_InstanceManager <strong>auf</strong> Rechner 2<br />

durch eine B1'_Handle-Referenz ersetzt. Das B1_Handle leitet alle Zugriffe<br />

über den B_InstanceManager <strong>auf</strong> Rechner 1 an B1' weiter (siehe Abbildung<br />

5.2). Mit dem Ersetzen des Cuckoo-Objektes durch ein entsprechendes Handle<br />

ist die Migration abgeschlossen.<br />

Abbildung 5.2: Situation 1 nach dem Umzug von Objekt B1<br />

Wie in Abbildung 5.2 zu sehen ist, werden Zugriffe von A <strong>auf</strong> B1', obwohl sich<br />

beide Objekt <strong>auf</strong> Rechner 1 befinden, immer über den B_InstanceManager<br />

<strong>auf</strong> Rechner 2 durchgeführt. Damit hätte die Migration ihr Ziel – entfernte<br />

durch lokale Zugriffe zu ersetzen – verfehlt, und Zugriffe <strong>auf</strong> B1' würden<br />

durch Weiterleiten über zwei Handles, zwei InstanceManager sowie zwei<br />

InstanceManager-Schnittstelle-Paare sogar ineffizienter.<br />

Aus diesem Grund wurde eine Lokalitätsoptimierung entwickelt. Sie basiert<br />

<strong>auf</strong> dem Konzept, beim ersten Zugriff über eine veraltete Referenz nach der<br />

Migration eine Ausnahme auszulösen, das in Doorastha (siehe Abschnitt 2.1.2)<br />

und JavaParty (siehe Abschnitt 2.1.1) verwendet wird. Beim Zugriff von A <strong>auf</strong><br />

B1'_Handle wird im B_InstanceManager <strong>auf</strong> Rechner 2 eine Ausnahme<br />

ausgelöst, die im B1_Handle <strong>auf</strong> Rechner 1 abgefangen wird. Über die<br />

38<br />

Rechner 1<br />

A B 1 _Handle<br />

B_InstanceManager_Stub<br />

B_InstanceManager_Skeleton<br />

B_InstanceManager<br />

C<br />

Rechner 3<br />

B 1 '<br />

B 1 _Handle<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B 2<br />

B 1 '_Handle<br />

B_InstanceManager<br />

B_InstanceManager_Stub<br />

B_InstanceManager_Stub<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

RMI / Netzwerk


5.2 MIGRATION UND REPLIKATION<br />

Ausnahme erfährt das B1_Handle die Platzierung von B1'. Da sich B1' und A<br />

<strong>auf</strong> dem selben Rechner befinden, ersetzt das B1_Handle seine Referenz <strong>auf</strong><br />

den B_InstanceManager von Rechner 2 durch eine lokale B1_Instance-<br />

Manager-Referenz. Anschließend führt es den misslungenen Zugriff <strong>auf</strong> B1'<br />

ein zweites mal über die neue Referenz durch. Die Situation nach der Lokalitätsoptimierung<br />

ist in Abbildung 5.3 zu sehen.<br />

Rechner 1<br />

A B 1 _Handle<br />

B_InstanceManager<br />

B_InstanceManager_Skeleton<br />

C<br />

Rechner 3<br />

B 1 '<br />

B 1 _Handle<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B 2<br />

B 1 '_Handle<br />

B_InstanceManager<br />

B_InstanceManager_Stub<br />

B_InstanceManager_Stub<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

RMI / Netzwerk<br />

Abbildung 5.3: Situation 2 nach Umzug von Objekt B1 <strong>auf</strong> Rechner 2 nach<br />

Rechner 1 und Lokalitätsoptimierung nach erstem Zugriff von<br />

Objekt A <strong>auf</strong> Objekt B1'<br />

Analog <strong>zur</strong> Lokalitätsoptimierung werden entfernte Referenzen <strong>auf</strong> migrierte<br />

Cuckoo-Objekte beim ersten Zugriff nach der Migration aktualisiert. Jedoch<br />

wird anders als bei der Lokalitätsoptimierung nach dem Auslösen der Ausnahme<br />

die alte entfernte InstanceManager-Referenz durch eine neue entfernte<br />

InstanceManager-Referenz ersetzt. Die resultierende Situation nach der<br />

Referenzaktualisierung vom B1_Handle <strong>auf</strong> Rechner 3 ist in Abbildung 5.4 zu<br />

sehen.<br />

Auf andere B-Objekte, die wie das migrierte B1-Objekt vom B_Instance-<br />

Manager <strong>auf</strong> Rechner 2 verwaltet werden, hat die Migration keine Auswirkung.<br />

So kann Cuckoo-Objekt D <strong>auf</strong> Rechner 4 auch während und nach der<br />

Migration von B1 über seine B2_Handle-Referenz <strong>auf</strong> Cuckoo-Objekt B2 zugreifen.<br />

Wie in Abschnitt 5.1 hervorgehoben wird, wird bei der Transformation einer<br />

Klasse des sequentiellen Original-Programms eine neue Klasse generiert, die<br />

dessen Klassenanteil im parallel ausführbaren JScatter-Programm repräsentiert.<br />

Bei jedem statischen Zugriff in einem sequentiellen Programm beschafft sich<br />

das zugreifende Objekt eine Referenz <strong>auf</strong> den statischen Anteil des zuzugrei-<br />

39


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

fenden Objektes. Analog hierzu beschafft sich im JScatter-System ein Objekt<br />

bei jedem Zugriff <strong>auf</strong> ein Klassenanteil-repräsentierendes Objekt jeweils eine<br />

Referenz. Durch dieses Konzept des Abbildens ehemals statischer Referenzen<br />

ist, wie im Folgenden beschrieben, die Referenzaktualisierung von Objekten,<br />

die <strong>auf</strong> ein den statischen Anteil repräsentierendes Objekt zugreifen einfacher<br />

als bei Objekten, die Instanzenanteil repräsentieren.<br />

Abbildung 5.4: Situation 3 nach Umzug von Objekt B1 <strong>auf</strong> Rechner 2 nach<br />

Rechner 1 und Lokalitätsoptimierung nach erstem Zugriff von<br />

Objekt A <strong>auf</strong> Objekt B1' sowie Referenzaktualisierung für B1-<br />

Referenz von Objekt C<br />

So wie ein Cuckoo-Objekt vor der Migration serialisiert wird, geschieht dies<br />

auch mit einem ClassObject-Objekt. Aus dem serialisierten Objekt wird <strong>auf</strong><br />

dem Rechner der neuen Platzierung ein ClassObject' erstellt. Ein referenzieller<br />

Zugriff <strong>auf</strong> ein ClassObject erfolgt im JScatter-System über ein sogenanntes<br />

RuntimeEnvironment-Objekt, das <strong>auf</strong> jedem Rechner im System genau einmal<br />

vorhanden ist. Jedes RuntimeEnvironment steht über die lokale Node mit allen<br />

anderen Nodes und und allen ClassObject-Instanzen in Verbindung (siehe<br />

Kapitel 4).<br />

Abbildung 5.5: Situation vor dem Umzug von B_ClassObject<br />

40<br />

Rechner 1<br />

A B 1 _Handle<br />

B_InstanceManager<br />

B_InstanceManager_Skeleton<br />

C<br />

Rechner 3<br />

B 1 '<br />

B 1 _Handle<br />

Rechner 1<br />

B_ClassObject_Stub<br />

A C_ClassObject<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B 2<br />

B 1 '_Handle<br />

B_InstanceManager<br />

B_InstanceManager_Stub<br />

B_InstanceManager_Stub<br />

RMI / Netzwerk<br />

D<br />

Rechner 2<br />

B_ClassObject<br />

B_ClassObject_Skeleton<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

RMI / Netzwerk


5.2 MIGRATION UND REPLIKATION<br />

Nach der Migration eines ClassObjects wird, nach der Aktualisierung der<br />

lokalen alten durch die neue ClassObject-Referenz, die Referenzaktualisierung<br />

<strong>auf</strong> allen Nodes propagiert. Von diesem Zeitpunkt an werden alle Zugriffe<br />

direkt zum neuen ClassObject' weitergeleitet.<br />

Die Abbildungen 5.5 – 5.7 zeigen beispielhaft, wie die Cuckoo-Objekte A und<br />

D sowie ein Klassenanteil-repräsentierendes Objekt C_ClassObject <strong>auf</strong> das<br />

Klassenanteil-repräsentierende Objekt B_ClassObject vor und nach dessen<br />

Migration zugreifen. In Abbildung 5.6 sind, um das Verständnis des Konzeptes<br />

zu erhöhen, auch die an der Migration beteiligten Objekte der L<strong>auf</strong>zeitumgebung<br />

eingezeichnet. Auf eine ausführliche Beschreibung wird jedoch verzichtet.<br />

B'_ClassObject<br />

A<br />

Rechner 1<br />

RuntimeEnvironment<br />

B_ClassObject_Stub<br />

Node_Skeleton<br />

RMI / Netzwerk<br />

Node<br />

C_ClassObject<br />

Abbildung 5.6: Situation nach dem Umzug von B_ClassObject vor der<br />

Referenzaktualisierung<br />

Rechner 1<br />

B'_ClassObject_Skeleton<br />

B'_ClassObject<br />

A C_ClassObject<br />

RMI / Netzwerk<br />

Rechner 2<br />

Abbildung 5.7: Situation nach dem Umzug von B_ClassObject<br />

D<br />

Rechner 2<br />

Node_Stub<br />

RuntimeEnvironment<br />

B_ClassObject<br />

B_ClassObject_Skeleton<br />

Grundlegend findet die Migration eines Objektes im JScatter-System nur statt,<br />

wenn es sich um ein migrierbares Objekt handelt. Wie migrierbare und nicht<br />

migrierbare Objekte vor der L<strong>auf</strong>zeit ermittelt und <strong>zur</strong> L<strong>auf</strong>zeit unterschieden<br />

werden, wurde bereits in Abschnitt 5.1 beschrieben. Bevor die Migration eines<br />

Cuckoo-Objekts oder eines ClassObject-Objekts durchgeführt wird, werden<br />

alle Zugriffe, die vor der Migrationsentscheidung begonnen wurden, ausge-<br />

D<br />

B'_ClassObject_Stub<br />

Legende<br />

Referenz zwischen Objekten<br />

der im JScatter-System<br />

parallele ausführbaren<br />

Anwendung<br />

Referenz zwischen Objekten<br />

im JScatter-L<strong>auf</strong>zeitsystem<br />

bei der Migration<br />

L<strong>auf</strong>zeitsystem-Objekt<br />

Objekt der parallele<br />

ausführbaren Anwendung<br />

im JScatter-System<br />

41


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

führt. Durch diese Vorgehensweise ist die Konsistenz des Objektzustandes vor<br />

und nach dessen Migration sichergestellt.<br />

5.2.2 REPLIKATION<br />

Ziel dieses Abschnittes ist es, die unterschiedlichen Replikationskonzepte für<br />

Instanzen- und Klassenanteil-repräsentierende Objekte vorzustellen. Hierbei<br />

wird vor allem dar<strong>auf</strong> Wert gelegt, zu zeigen, dass verschiedene Arten von<br />

Migrations- bzw. Replikationsstrategien unterstützt werden.<br />

Abbildung 5.8: Situation nach Replikation von Cuckoo-Objekt B1<br />

Wie schon bei der Migration, dient die in Abbildung 5.1 dargestellte Situation<br />

als Ausgangssituation, jedoch führt die Migrationsstrategie diesmal die Replikation<br />

des Cuckoo-Objektes B1 nach Rechner 1 durch. Auch die Replikation<br />

beginnt mit dem Serialisieren des B1-Objektes. Anschließend wird das serialisierte<br />

Objekt von Rechner 2 <strong>auf</strong> Rechner 1 übertragen und in die Verwaltungsinstanz<br />

aller B-Replikate <strong>auf</strong> Rechner 1, den B Replica _InstanceManager eingetragen.<br />

Damit eine Zuordnung der Replikate eines Objektes im JScatter-<br />

System möglich ist, enthält jedes Original-Cuckoo-Objekt eine Referenz <strong>auf</strong><br />

ein ReplicaGroup-Objekt. Über ein ReplicaGroup-Objekt sind alle Replikate<br />

des Cuckoo-Objektes über ein zugehöriges InstanceManager-Schnittstellenpaar<br />

erreichbar. Die Situation nach der Replikation von B1 <strong>auf</strong> Rechner 1 ist in<br />

Abbildung 5.8 zu sehen.<br />

Beim nächsten Lesezugriff von A <strong>auf</strong> B1 wird im B1_Handle die Frage nach<br />

der Existenz eines lokalen B1 Replica -Objektes positiv beantwortet und der Zugriff<br />

an dieses weitergeleitet. In Abbildung 5.9 ist die <strong>auf</strong> Rechner 1 resultierende<br />

Objektkonstellation zu sehen. Analog zu der beschriebenen Replikation<br />

von B1 <strong>auf</strong> Rechner 1 und anschließendem Lesezugriff, stellt Abbildung 5.9 die<br />

42<br />

Rechner 1<br />

B_InstanceManager_Stub<br />

B 1 _Handle<br />

A<br />

B Replica _InstanceManager<br />

Rechner 3<br />

Replica<br />

B1 B Replica _InstanceManager_Skeleton<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B 2<br />

B 1<br />

B_InstanceManager<br />

ReplicaGroup<br />

B Replica _InstanceManager_Stub<br />

B 1 _Handle<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

RMI / Netzwerk<br />

B_InstanceManager_Stub<br />

C


5.2 MIGRATION UND REPLIKATION<br />

Situation nach Erstellen eines B1 Replica -Objektes <strong>auf</strong> Rechner 3 mit nachfolgenden<br />

Lesezugriff dar.<br />

Die Durchführung von Lesezugriffen ist in allen Replikationsstrategien gleich,<br />

bei Schreibzugriffen können jedoch verschiedene Vorgehensweisen gewählt<br />

werden. Zum Einen sind <strong>Strategien</strong> vorstellbar, die vor dem Durchführen des<br />

Schreibzugriffes <strong>auf</strong> das Original alle Replikate der zugehörigen Replikatgruppe<br />

löschen und anschließend den Schreibzugriff ausführen. Beim nächsten<br />

Zugriffsversuch <strong>auf</strong> ein gelöschtes Replikat wird dann ein neues Replikat vor<br />

dessen Ausführung erstellt. Diese Vorgehensweise wird auch als Invalidierung<br />

bezeichnet.<br />

Rechner 1<br />

B_InstanceManager_Stub<br />

B 1 _Handle<br />

A<br />

B Replica _InstanceManager<br />

Rechner 3<br />

Replica B1 Replica<br />

B1 B Replica _InstanceManager_Skeleton<br />

Rechner 2<br />

B_InstanceManager_Skeleton<br />

B 2<br />

B 1<br />

B Replica _InstanceManager<br />

B_InstanceManager<br />

ReplicaGroup<br />

B Replica _InstanceManager_Stub<br />

B Replica _InstanceManager_Stub<br />

B Replica _InstanceManager_Skeleton<br />

B 1 _Handle<br />

Rechner 4<br />

B_InstanceManager_Stub<br />

D B 2 _Handle<br />

RMI / Netzwerk<br />

B_InstanceManager_Stub<br />

Abbildung 5.9: Situation nach mehrfacher Replikation von B1 und<br />

anschließendem Lesezugriff von A und C <strong>auf</strong> ihr lokales<br />

B1-Replikat<br />

Eine andere Strategie stellt <strong>zur</strong> L<strong>auf</strong>zeit fest, dass durch eine Folge von<br />

Schreibzugriffen <strong>auf</strong> ein repliziertes Objekt, es für die Effizienz des Systems<br />

besser ist, die Replikation <strong>auf</strong>zuheben. Auch in diesem Fall müssen alle<br />

Replikate gelöscht werden. Eine dritte Replikationsstrategie kann jedoch ganz<br />

anders vorgehen. Beispielsweise ist vorstellbar, dass nach der Durchführung<br />

des Schreibzugriffs <strong>auf</strong> ein Original-Objekt der neue Zustand oder auch nur die<br />

Zustandsänderung an alle <strong>zur</strong> Replikatgruppe gehörenden Replikate weitergeleitet<br />

werden. Jedoch kann man sich auch hybride <strong>Strategien</strong> vorstellen, die zu<br />

einer Zeit alle Replikate vor Schreibzugriffen löschen und zu einer anderen<br />

Zeit nach Schreibzugriffen die Aktualisierung der Replikatzustände vorsieht.<br />

Das JScatter-System unterstützt die beiden Grundvorgehensweisen, d. h. das<br />

Löschen vor und das Aktualisieren nach dem Durchführen eines Schreibzugriffs.<br />

Hybride <strong>Strategien</strong> können aus diesen erstellt werden. Auf die Realisie-<br />

C<br />

43


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

rung der Grundvorhergehensweisen wird in Kapitel 6 näher eingegangen.<br />

Wie in Abbildung 5.8 und 5.9 zu sehen ist, werden Zugriffe <strong>auf</strong> andere, nicht<br />

replizierte B-Cuckoo-Objekte durch eine Replikation eines Objektes nicht beeinflusst.<br />

So kann unabhängig von der Replikation von B1, B2 von D zugegriffen<br />

oder auch die Migration von B2 nach Rechner 4 durchgeführt werden (siehe<br />

Abbildung 5.10).<br />

Abbildung 5.10: Situation nach mehrfacher Replikation von B1 sowie der<br />

Migration von B2 von Rechner 2 nach Rechner 4<br />

Abbildung 5.11: Situation von Replikation von B_ClassObject <strong>auf</strong> Rechner 1<br />

Analog <strong>zur</strong> Replikation von Instanzenanteil-repräsentierenden Objekten können<br />

auch Klassenanteil-repräsentierende Objekte unter Berücksichtigung der<br />

Abbildung statischer Zugriffe im JScatter-System durchgeführt werden. Zum<br />

Verdeutlichen sei wiederum das Beispielszenario aus Abbildung 5.5 gegeben.<br />

44<br />

Rechner 1<br />

B_InstanceManager_Stub<br />

B 1 _Handle<br />

A<br />

B Replica _InstanceManager<br />

Rechner 3<br />

Replica B1 Replica B1 B Replica _InstanceManager_Skeleton<br />

Rechner 1<br />

Rechner 2<br />

B 2 '_Handle<br />

B_InstanceManager_Skeleton<br />

B Replica _InstanceManager<br />

B Replica _ClassObject_Skeleton<br />

Node<br />

A<br />

B Replica _ClassObject<br />

B 1<br />

B_InstanceManager<br />

ReplicaGroup<br />

B Replica _InstanceManager_Stub<br />

B Replica _InstanceManager_Stub<br />

B Replica _InstanceManager_Skeleton<br />

Node_Skeleton<br />

C_ClassObject<br />

B_ClassObject_Stub<br />

RMI / Netzwerk<br />

B 1 _Handle<br />

Rechner 2<br />

B Replica _ClassObject_Stub<br />

Node<br />

Node_Stub<br />

B_InstanceManager_Stub<br />

D<br />

Rechner 4<br />

B_InstanceManager_Skeleton<br />

B 1 '<br />

RuntimeEnvironment<br />

B_ClassObject_Skeleton<br />

B_InstanceManager<br />

B 2 _Handle<br />

RMI / Netzwerk<br />

B_InstanceManager_Stub<br />

B_ClassObject<br />

D<br />

C<br />

ReplicaGroup


5.2 MIGRATION UND REPLIKATION<br />

In dieser Situation bestimmt die Migrationsstrategie das B_ClassObject-Objekt<br />

von Rechner 2 <strong>auf</strong> Rechner 1 zu replizieren. Nach Durchführung der Replikation<br />

besteht die in Abbildung 5.11 dargestellte Objektkonstellation.<br />

Wie auch bei der Replikation von Cuckoo-Objekten enthält das B_Class-<br />

Object eine Referenz <strong>auf</strong> ein Replikatgruppen-Objekt, über die es alle<br />

Replikate referenziell zugreifen kann. Jedoch erfolgt der Zugriff nicht direkt<br />

über ein Schnittstellenpaar, sondern indirekt über L<strong>auf</strong>zeitsystem-Objekte.<br />

Nach der Replikation werden alle Lesezugriffe von A oder C_ClassObject<br />

nicht <strong>auf</strong> das entfernte B_ClassObject-Objekt, sondern <strong>auf</strong> seiner lokalen<br />

Kopie durchgeführt. Schreibzugriffe werden, wie schon bei der Replikation<br />

Instanzenanteil-repräsentierender Objekte, an das Original weitergeleitet. Hier<br />

kann wiederum bestimmt werden, ob die Replikate vor der Durchführung des<br />

Zugriffes <strong>auf</strong> das Original gelöscht, oder nach dessen Durchführung die<br />

Zustände der Replikate aktualisiert werden. Auf diese Weise werden auch beim<br />

Klassenanteil beide Grundformen des Verhaltens zum Durchführen von<br />

Schreibzugriffen <strong>auf</strong> Replikate unterstützt.<br />

Somit wird sowohl bei der Replikation Klassenanteil- als auch Instanzenanteilrepräsentierender<br />

Objekte die Konsistenz aller <strong>zur</strong> Replikatgruppe eines<br />

Original-Objekts gehörenden Objekte gewahrt.<br />

5.3 Migrationsstrategien<br />

Wie in Abschnitt 5.2 <strong>auf</strong>gezeigt wurde, gibt es eine große Anzahl verschiedener<br />

Migrationsstrategien. Hierbei gibt es sowohl statische <strong>Strategien</strong>, die ihre<br />

Entscheidung nicht an das L<strong>auf</strong>zeitverhalten anpassen, als auch dynamische<br />

<strong>Strategien</strong>, die beispielsweise eine ehemalige Repliktionsentscheidung revidieren<br />

und die Migration des Objektes veranlassen. Migrationsstrategien können<br />

verschiedene Replikationsstrategien (siehe Abschnitt 5.2.1) verwenden.<br />

Hybride <strong>Strategien</strong> machen die verwendete Replikationsstrategie von der vorliegenden<br />

Situation im System abhängig. Alle diese <strong>Strategien</strong> können im<br />

JScatter-System beschrieben werden. Abbildung 5.12 stellt die Zusammenhänge<br />

der identifizierten Migrationsstrategien graphisch dar.<br />

statische Strategie dynamische Strategie<br />

statische Strategie<br />

dynamische Strategie<br />

benutzt<br />

benutzt<br />

Situation 1<br />

Migration Replikationsstrategie 1 Replikationsstrategie 2<br />

Abbildung 5.12: Migrationsstrategien im Überblick<br />

Situation 2<br />

Situation 3<br />

Replikationsstrategie 3<br />

Migrationsstrategien haben neben der eigentlichen Migration bzw. Replikation,<br />

<strong>auf</strong> die in Abschnitt 5.2 eingegangen wurde, noch weitere Aufgaben, die in<br />

45


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

diesem Abschnitt betrachtet werden.<br />

Eine Aufgabe von Migrationsstrategien ist die Migrationsentscheidung, die <strong>auf</strong><br />

statischen und dynamischen Ergebnissen aus der Programmanalyse beruht. Der<br />

Einsatz der statischen Programmanalyse wurde schon in Abschnitt 5.1 betrachtet.<br />

Die dynamische Programmanalyse führt die Migrationsstrategie <strong>zur</strong><br />

L<strong>auf</strong>zeit selbst durch. Auf allgemeine Vorgehensweisen von dynamischen<br />

Programmanalysen wurde bereits im Abschnitt 3.5 eingegangen. Die dynamische<br />

Programmanalyse im JScatter-System wurde für die Unterstützung von<br />

verschiedenen Arten von Migrationsstrategien um einige Funktionalitäten erweitert,<br />

die im Folgenden vorgestellt werden.<br />

Aktive Objekte werden im JScatter-System nicht migriert. Aus diesem Grund<br />

werden Zugriffe <strong>auf</strong> aktive Objekte auch nicht von der dynamischen<br />

Programmanalyse betrachtet, da diese Informationen nur für die Initiierung<br />

einer Migration von Interesse sind. Auch Zugriffe, die während der<br />

Initialisierung eines Objektes <strong>auf</strong> ein anderes Objekt durchgeführt werden,<br />

werden vernachlässigt. Initialzugriffe verfälschen das Analyseergebnis bei<br />

Objekten, <strong>auf</strong> die <strong>zur</strong> L<strong>auf</strong>zeit nur selten zugegriffen wird. So kann es bei der<br />

Initialisierung vorkommen, dass eine Methode innerhalb des Objektes häufig<br />

ausgeführt, nach der Initialisierung jedoch nicht mehr verwendet wird. Durch<br />

die Informationserfassung werden diese Zugriffe als lokale Zugriffe gewertet.<br />

Die Anzahl der entfernten Zugriffe <strong>auf</strong> das Objekt müssen beim Werten von<br />

Initialzugriffen um ein vielfaches höher sein, bevor eine Migration initiiert<br />

wird.<br />

Nach dem Umzug eines Objektes werden die Ergebnisse der dynamischen<br />

Programmanalyse <strong>auf</strong> dem Rechner der Ursprungsplatzierung gelöscht. Sollte<br />

das Objekt zu einem späteren Zeitpunkt wieder <strong>auf</strong> den Rechner der Ursprungsplatzierung<br />

migriert werden, so wird das Objekt erneut in die dynamische<br />

Programmanalyse <strong>auf</strong> dem Rechner <strong>auf</strong>genommen.<br />

In [Rei1997] wird eine Replikationsstrategie vorgestellt, bei der nach der<br />

Gesamtanzahl von z. B. 20 Zugriffen die Ergebnisse der dynamischen Programmanalyse<br />

<strong>auf</strong> ihren Initialwert <strong>zur</strong>ückgesetzt werden. Um <strong>Strategien</strong><br />

dieser Art auch im JScatter-System beschreiben zu können, verfügt die dynamische<br />

Analyse über eine Reset-Funktionalität.<br />

Durch Verwenden der Reset-Funktionalität können <strong>Strategien</strong> besser <strong>auf</strong> die<br />

aktuelle Situation von Zugriffen zwischen Objekten einer Anwendung<br />

reagieren. So ist es beispielsweise möglich, dass <strong>auf</strong> ein Objekt zu einer Zeit Z1<br />

häufiger lesend und zu einer späteren Zeit Z2 häufiger schreibend zugegriffen<br />

wird. Während <strong>zur</strong> Zeit Z1 eine Replikation des Objektes besser wäre, würde<br />

sich <strong>zur</strong> Zeit Z2 eine Migration zum Schreiberobjekt anbieten oder, wenn die<br />

Schreibzugriffe von mehreren Objekten <strong>auf</strong> verschiedenen Rechnern ausgehen,<br />

wäre es besser, die Platzierung des Objektes unverändert zu lassen.<br />

Würde jedoch das Objekt <strong>zur</strong> Zeit Z2, wegen der hohen Anzahl der<br />

46


5.3 MIGRATIONSSTRATEGIEN<br />

Lesezugriffe aus der Zeit Z1, nach jedem Lesezugriff, der nach einem<br />

Schreibzugriff stattfindet repliziert, entsteht ein enormer Datentransfer, der<br />

jedoch durch ein Informations-Reset vermieden werden kann.<br />

Die dynamische Programmanalyse besitzt eine weitere Funktionalität, die <strong>auf</strong><br />

der in [Rei1997] beschriebenen Replikationsstrategie beruht. In dieser<br />

Strategie werden die Lese- und Schreibzugriffe <strong>auf</strong> ein Objekt in ihrem<br />

prozentualen Verhältnis gegenüber gestellt, und <strong>auf</strong> deren Basis eine Replikationsentscheidung<br />

getroffen. Damit die ersten Zugriffe jedoch noch keine<br />

Replikation auslösen, muss <strong>auf</strong> ein Objekt eine Mindestanzahl von Zugriffen<br />

erfolgt sein, bevor das Verhältnis der Lese-und Schreibzugriffe bestimmt und<br />

eine Replikationsentscheidung getroffen wird. Auch <strong>Strategien</strong> dieser Art<br />

können im JScatter-System beschrieben werden.<br />

Replikate können weder migriert noch repliziert werden. Aus diesem Grund<br />

macht es auch keinen Sinn, Zugriffe <strong>auf</strong> Replikate durch dynamische<br />

Programmanalyse zu erfassen.<br />

Sowohl für die oben beschriebene Reset-Funktionalität wie auch für die<br />

Migration und Replikation können in Migrationsstrategien Schwellwerte<br />

definiert werden. Der Reset-Schwellwert ist in <strong>Strategien</strong> zum Durchführen<br />

eines Resets der Analyseergebnisse eines Objektes vorgesehen, wenn die<br />

Gesamtanzahl der Zugriffe <strong>auf</strong> das Objekt dem Reset-Schwellwert entsprechen.<br />

Der Migrations- bzw. Replikationsschwellwert kann vom Schreiber der<br />

Migrationsstrategie unterschiedlich eingesetzt werden. Zum Einen kann er<br />

einen prozentualen Anteil der Schreib- bzw. Lesezugriffe darstellen (siehe<br />

Strategie in [Rei1997]), zum Anderen kann er einen absoluten Wert darstellen<br />

(siehe Strategie in [MC1992]), ab dem ein Objekt migriert bzw. repliziert wird.<br />

Eine weitere Aufgabe von Migrationsstrategien ist der Transfer der zu migrierenden<br />

bzw. replizierenden Objekte. Anders als in Migrationsstrategien, die <strong>auf</strong><br />

Betriebssystemebenen arbeiten, ist es im JScatter-System nicht notwendig, für<br />

Objekte verschiedenen Typs verschiedene Transfermechanismen bereitzustellen.<br />

Der Grund hierfür ist der Einsatz des in Java vorhandenen Mechanismus<br />

zum Erstellen von persistenten Objekten durch Serialisierung und Deserialisierung,<br />

mit dem alle Objekte gleich behandelt werden. Die Einhaltung der<br />

Schnittstelle <strong>zur</strong> Serialisierung wird bei der Transformation der Anwendung<br />

sichergestellt.<br />

Die wohl schwierigste Aufgabe einer Migrationsstrategie ist die Entscheidung,<br />

ob und wann migriert oder repliziert werden soll. Während meiner Recherchen<br />

habe ich festgestellt, dass es keine Migrationsstrategie gibt, die für alle<br />

Anwendungen gleich gute Ergebnisse liefert. Diese Erkenntnis basiert <strong>auf</strong> der<br />

Feststellung, dass Programme sich sehr stark in ihrem Verhalten unterscheiden<br />

können. Ein Programm P1 kann eine Vielzahl von Objekten enthalten, die nur<br />

selten schreibend aber häufig lesend zugegriffen werden. Ein Programm P2<br />

hingegen kann, wie bei einer Consumer-Producer-Simulation, Objekte abwechselnd<br />

lesend und schreibend zugreifen. Für Programm P1 bringt der<br />

47


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

Einsatz einer Migrationsstrategie M1, die bei häufig <strong>auf</strong>tretenden Lesezugriffen<br />

Replikation einsetzt, einen relativ großen L<strong>auf</strong>zeitgewinn, da entfernte Zugriffe<br />

lokal ausgeführt werden. Im Programm P2 bringt der Einsatz der Migrationsstrategie<br />

M1 jedoch keine L<strong>auf</strong>zeitvorteile, sondern verlangsamt im schlechtesten<br />

Fall sogar P2 gegenüber dem sequenziell Fall, da durch die abwechselnden<br />

Lese- und Schreibzugriffe Replikate immer neu erstellt und anschließend<br />

wieder gelöscht werden, oder die Zustandsänderungen an alle Replikate der<br />

Replikatgruppe weitergegeben werden müssen.<br />

Zum Abschluss des Kapitels wird nun das in diesem Abschnitt vorgestellte<br />

Modell, in das in Abschnitt 2.3.3 beschriebene Komponentenmodell für<br />

Migrationsstrategien von GERNS und LIE [GL1994] eingeordnet.<br />

Eine Migrationsstrategie im JScatter-System besteht aus den Komponenten<br />

Informationserfassung, Entscheidung und Transfer. Der Informationserfassungs-Komponente<br />

sind alle Aufgaben zugeordnet, die mit dynamischer und<br />

statischer Programmanalyse in Zusammenhang stehen. Die Entscheidungs-<br />

Komponente entscheidet über Migration oder Replikation eines Objektes. Der<br />

Transferkomponente sind alle Aufgaben zugeordnet, die zum Transfer der<br />

Objekte notwendig sind.<br />

Eine Informationsverteilungs-Komponente ist im JScatter-System nicht vorhanden.<br />

Die Informationsverteilung ist sehr kostenintensiv, da sie dafür verantwortlich<br />

ist, die gesammelten Informationen <strong>auf</strong> alle beteiligten Rechner zu<br />

verteilen. Für spätere Versionen ist es jedoch leicht möglich, eine solche<br />

Komponente in die Migrationsstrategie zu integrieren.<br />

Zur Informationsverteilung gehört nach GERNS und LIE auch die Initialplatzierung<br />

der Objekte. Hierfür steht im JScatter <strong>auf</strong> jedem beteiligten<br />

Rechner ein lokaler Verteilungsplan <strong>zur</strong> Verfügung. Im Zusammenhang mit<br />

Migration muss dar<strong>auf</strong> geachtete werden, dass nach der Migration dem<br />

Verteilungsplan die neue Platzierung des Objektes mitgeteilt wird. Hierfür<br />

müssen zwei Verteilungspläne aktualisiert werden. Zum Einen der Plan <strong>auf</strong><br />

dem Rechner der alten Platzierung des Objektes und zum Anderen der Plan <strong>auf</strong><br />

der neuen Platzierung des migrierten Objektes. Wird eine solche Aktualisierung<br />

unterlassen, berechnet der Verteilungsplan die Initialplatzierung eines<br />

neuen Objektes <strong>auf</strong> <strong>Grundlage</strong> veralteter Informationen.<br />

Eine weitere Hilfe für die Berechnung der Initialplatzierung eines Objektes<br />

durch den Verteilungsplan stellt eine Migrations-History dar. Auf deren<br />

<strong>Grundlage</strong> kann der Verteilungsplan die Entwicklung der Systemsituation vor<br />

und nach einer Migration auswerten und die Platzierungsentscheidung für ein<br />

neues Objekt berechnen.<br />

Die Ortsauswahl erfolgt im JScatter-System implizit bei der Überschreitung<br />

eines Schwellwertes. Hierbei wird das Objekt zu dem Rechner migriert von<br />

dem als erstes bei einem Zugriff der Migrationsschwellwert überschritten<br />

wurde. Für Replikation verhält es sich analog. Das Objekt, <strong>auf</strong> das beim Über-<br />

48


5.3 MIGRATIONSSTRATEGIEN<br />

treten des Schwellwertes zugegriffen wurde, ist das zu migrierende Objekt.<br />

Somit ist eine Objektauswahl- sowie eine Ortsauswahl-Komponente im<br />

JScatter implizit vorhanden.<br />

Auch eine Adaptions-Komponente ist im JScatter nicht vorgesehen. Hier gilt<br />

dasselbe wie bei der Informationsverteilung. Die Kosten, die bei der<br />

Berechnung für die Adaption entstehen, z. B. durch Neuronale Netze (siehe<br />

Abschnitt 2.3.4), sind für die Verteilung von Anwendungen für die das<br />

JScatter-System entwickelt wurde zu hoch.<br />

Eine Gegenüberstellung des hier vorgestellten Modells mit dem Modell von<br />

GERNS und LIE ist in Abbildung 5.12 zu sehen.<br />

Informationserfassung<br />

Informationsverteilung<br />

Entscheidung<br />

Objektauswahl<br />

Ortswahl<br />

Transfer<br />

Adaption<br />

Gerns-Lie-Modell<br />

dynamische Programmanalyse<br />

Verteilung von<br />

Informationen<br />

statische Programmanalyse<br />

Initialplatzierung<br />

{Migration | Replikation | Platzierung bleibt}<br />

Auswahl des Objektes<br />

Auswahl der Platzierung<br />

Transfer des Objektes<br />

dynamisches Anpassen der Strategie<br />

Informationserfassung<br />

Verteilungsplan<br />

Entscheidung<br />

Implizit enthalten<br />

Implizit enthalten<br />

Transfer<br />

JScatter-Modell<br />

Abbildung 5.12: Vergleich Migrationsstrategien Gerns-Lie und JScatter<br />

49


KAPITEL 5 EIN MODELL FÜR MIGRATIONSSTRATEGIEN<br />

50


6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

In diesem Kapitel werden ausgewählte Details bei der Realisierung des<br />

Modells von Kapitel 5 näher betrachtet. Hierbei wird vor allem <strong>auf</strong> den Einsatz<br />

der statischen Programmanalyse eingegangen, einige technische Aspekte bei<br />

der Umsetzung der Migration und der Replikation näher vorgestellt und die<br />

Architektur sowie die Erweiterbarkeit von Migrationsstrategien im JScatter-<br />

System dargelegt.<br />

In der in Kapitel 4 vorgestellten Basisimplementierung von JScatter wurde für<br />

die Kommunikation zwischen den bei der verteilten Ausführung des transformierten<br />

Programms beteiligten Rechnern das RMI von Sun [Sun 2003]<br />

eingesetzt. Während der Realisierung der Migrationsstrategien wurde die<br />

Kommunikation zwischen den Rechnern <strong>auf</strong> KaRMI [Nes1999] umgestellt.<br />

KaRMI ist eine effiziente Alternative zum RMI von Sun. Es verbessert die<br />

Kommunikation bei entfernten Methoden<strong>auf</strong>rufen zwischen Rechnern in einem<br />

Netzwerk durchschnittlich um 34% [Nes1999]. Der Umstieg <strong>auf</strong> KaRMI war<br />

leicht möglich, da seine Schnittstelle zum RMI von Sun kompatibel ist.<br />

6.1 Transformation<br />

6.1.1 ERKENNUNG MIGRIERBARER UND REPLIZIERBARER OBJEKTE<br />

Wie in Abschnitt 5.1 beschrieben wurde, setzt JScatter bei der Transformation<br />

statische Programmanalyse ein. Als Werkzeug hierfür wird die Analyseumgebung<br />

Pauli [Thi2001] eingesetzt. Pauli ist ein Java-Framework, mit dem<br />

unter Einsatz mehrerer Java-Bibliotheken für Java-Anwendungen statische<br />

Programmanalyse durchgeführt werden kann. Jede Programmanalyse ist in<br />

Pauli durch ein Analysemodul realisiert. Statische Programmanalysen, wie<br />

beispielsweise die in Abschnitt 3.4 beschriebene Immutable-Objektanalyse<br />

basieren <strong>auf</strong> Ergebnissen anderer statischer Programmanalysen. Aus diesem<br />

Grund ist es in Pauli möglich, Analyseergebnisse eines Analysemoduls bei der<br />

Realisierung eines anderen Analysemoduls einzusetzen. Derzeit verfügt Pauli<br />

über 37 Analysemodule.<br />

JScatter setzt die Klassenhierachieanalyse von Pauli ein, um aktive Objekte in<br />

der zu transformierenden Anwendung zu identifizieren. In der Programmiersprache<br />

Java bilden Instanzen der Klasse Thread und deren Unterklassen<br />

sowie alle Instanzen von Klassen, die das Interface Runnable implementieren<br />

<strong>zur</strong> L<strong>auf</strong>zeit aktive Objekte. Alle passiven Objekte werden bei der Transformation<br />

für Migration und Replikation speziell vorbereitet. Konkret wird deren<br />

Schnittstelle gegenüber dem im Kapitel 4 vorgestellten Konzept um Methoden<br />

erweitert, die für das Durchführen von Migration und Replikation durch<br />

Migrationsstrategien erforderlich sind.<br />

Damit Zugriffe <strong>auf</strong> ein Objekt durch die dynamische Programmanalyse gezählt<br />

werden, muss in jeder Zugriffsmethode eines Klassenanteil- oder Instanzenanteil-repräsentierenden<br />

Objekts ein Inkrementierungs<strong>auf</strong>ruf in der dynamische<br />

51


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Programmanalyse durchgeführt werden. Nur Methoden, über die in der<br />

transformierten Anwendung <strong>auf</strong> ein Feld der ursprünglichen Originalanwendung<br />

zugegriffen wird und Methoden, die in den Klassen der Originalanwendung<br />

vorhanden waren, werden bei der Transformation um einen<br />

Inkrementierungs<strong>auf</strong>ruf erweitert. Hierbei wird im JScatter-System zwischen<br />

Lese-, Schreib- und Nicht-Lese-Oder-Schreibzugriffen unterschieden.<br />

Lesezugriffe sind Zugriffe, bei denen der Wert eines Feldes der ursprünglichen<br />

Originalklasse gelesen wird. Wird ein Feld der Instanz einer ursprünglichen<br />

Originalklasse während des Zugriffes verändert, handelt es sich um einen<br />

Schreibzugriff. Alle Methoden, die weder lesend noch schreibend <strong>auf</strong> ein Feld<br />

des Objektes zugreifen, sind Nicht-Lese-Oder-Schreibzugriffe. Bei konservativer<br />

Abschätzung können diese Zugriffe als Lesezugriffe gewertet werden,<br />

da sie wie Lesezugriffe den Zustand des zugegriffenen Objektes nicht verändern.<br />

Lese und Schreibzugriffe <strong>auf</strong> ein Feld werden mittels Def-Use-Analyse<br />

festgestellt.<br />

Ein Inkrementierungs<strong>auf</strong>ruf enthält sowohl die Information, welches Objekt<br />

zugegriffen wurde, als auch die Information, von welchem Objekt aus ein<br />

Objekt zugegriffen wurde. Jedes Objekt ist im JScatter-System über eine<br />

eindeutige ID, die aus dem Namen der Klasse, der IP-Adresse des Rechners<br />

<strong>auf</strong> dem es platziert ist und einer weiteren Zahl besteht identifizierbar. Durch<br />

diese Informationen kann neben der Zugriffsart (Lese-, Schreib-, Nicht-Lese-<br />

Oder-Schreibzugriff) bei der Auswertung auch zwischen lokalen und<br />

entfernten Zugriffen unterschieden werden.<br />

6.1.2 ERWEITERUNG DES TRANSFORMATIONSKONZEPTES<br />

Um im JScatter-System Migration und Replikation von Objekten zu unterstützen,<br />

wurde das im Abschnitt 4.1 vorgestellte Transformationskonzept<br />

erweitert. Für die Erweiterung implementieren Instanzenanteil-repräsentierende<br />

Objekte das Interface MigrateableCuckoo (siehe Anhang A) und<br />

Felder, die die eindeutige ID des Objektes beschreiben (siehe Abschnitt 6.1.1),<br />

die Replikatgruppe des Objektes enthalten oder für die Steuerung der<br />

Methodenzugriffe im Zusammenhang mit Migration und Replikation benötigt<br />

werden. Auf die Besonderheiten bei Methodenzugriffen wird in den Abschnitten<br />

6.2.1 und 6.3.1 eingegangen.<br />

Für die Migration und Replikation eines Instanzenanteil-repräsentierenden<br />

Objektes wird das Interface Handle durch ein Interface Migrateable-<br />

Handle erweitert und ein Interface MigrateableInstanceManager eingeführt,<br />

das das Interface Remote erweitert (siehe Anhang A).<br />

Alle Klassenanteil-repräsentierenden Objekte implementieren das Interface<br />

MigrateableClassObject, welches ebenfalls das Interface Remote<br />

erweitert. Des Weiteren wurde im JScatter-System eine abstrakte Klasse<br />

AbstractMigrateableClassObject eingeführt, in der alle Felder und<br />

Methoden enthalten sind, die bei allen Klassenanteil-repräsentierenden<br />

52


6.1 TRANSFORMATION<br />

Objekten identisch sind. Auf diese Weise werden die bei der Transformation<br />

zu generierenden Methoden <strong>auf</strong> die beschränkt, die je nach ClassObject<br />

unterschiedlichen Maschinencode enthalten.<br />

Vor der Migration und der Replikation muss, wie in Abschnitt 5.2 beschrieben,<br />

der Objektzustand des zu migrierenden bzw. zu replizierenden Objektes<br />

serialisiert und anschließend <strong>zur</strong> neuen Platzierung übertragen werden. Bei der<br />

Objektserialisierung werden neben dem persistenten Zustand des Objektes<br />

auch alle Methoden serialisiert. Um den Zustand eines Objektes zu migrieren<br />

ist es jedoch nur notwendig, die Werte seiner nicht-transienten Felder zu<br />

serialisieren und zu übertragen. Auf diese Weise kann die unnötige Übertragung<br />

der implementierten Methoden gespart werden. Hierbei ist jedoch<br />

wichtig, das die Serialisierung der Felder erst durchgeführt wird, wenn alle vor<br />

der Migrationsentscheidung begonnenen Zugriffe <strong>auf</strong> das Objekt durchgeführt<br />

sind, und alle während der Migration des Objektes eintreffenden Methodenzugriffe<br />

<strong>auf</strong>gehalten und erst nach der Migration an der neuen Platzierung des<br />

Objektes ausgeführt werden.<br />

Um nur die notwendigen Teile des Objektes zu übertragen, wurde in Anlehnung<br />

an das Entwurfsmuster Memento [GHJV1995] im JScatter ein<br />

Interface Memento eingeführt. Ein Memento-Objekt speichert den Zustand<br />

eines Objektes und ist nur von seinem zugehörigen Objekt aus zugreifbar.<br />

Für jede Klasse der zu transformierenden Anwendung, deren Instanzen keine<br />

aktiven Objekte sind, werden vier Memento-Klassen generiert. Zwei für den<br />

Klassenanteil und zwei für den Instanzenanteil. Für die Klasse Example im<br />

Beispiel 6.1 werden für den Instanzenanteil die Memento-Klassen Example-<br />

MigrationMemento und ExampleReplikationMemento generiert.<br />

05<br />

10<br />

15<br />

20<br />

class Example<br />

{<br />

transient int value;<br />

private HashMap map;<br />

Example(int value)<br />

{<br />

this.value = value;<br />

this.map = new HashMap();<br />

}<br />

addElem(Object elem)<br />

{ ... }<br />

getElemAt(int i)<br />

{ ... }<br />

numberOfElems()<br />

{ ... }<br />

...<br />

}<br />

Legende<br />

nicht-transientes Feld<br />

eindeutige Objekt-ID<br />

25<br />

30<br />

35<br />

40<br />

45<br />

class ExampleMigrationMemento implements Memento<br />

{<br />

private HashMap map;<br />

ExampleMigrationMemento(HashMap map)<br />

{ this.map = map; }<br />

public HashMap getMap() { return map; }<br />

}<br />

class ExampleReplicationMemento extends<br />

ReplicationMemento<br />

{<br />

private HashMap map;<br />

ExampleReplicationMemento(HashMap map,<br />

int objectID,String objectIP, String className)<br />

{<br />

super(objectID, objectIP, className);<br />

this.map = map;<br />

}<br />

public HashMap getMap() { return map; }<br />

}<br />

Beispiel 6.1: Memento-Klassen Instanzenanteil-repräsentierender Objekte<br />

53


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Die Klasse ExampleMigrationMemento enthält für alle nicht-transienten<br />

Felder der Klasse Example ein äquivalentes private Feld und eine Zugriffsmethode.<br />

Genauso verhält es sich bei der Klasse ExampleReplikation-<br />

Memento, jedoch besitzt sie zusätzlich noch Felder für die eindeutige ID des<br />

Objektes. Da alle ReplikationMemento-Klassen Felder für die eindeutige<br />

ID besitzen, erweitern sie eine gleichnamige abstrakte Klasse, die auch die<br />

notwendigen Zugriffsmethoden besitzt. Durch die Vererbung brauchen bei der<br />

Transformation sowohl die Felder als auch die Zugriffsmethoden der<br />

eindeutigen ID nicht explizit generiert zu werden. Für den Klassenanteil<br />

werden analoge Klassen generiert. Wie Memento-Objekte bei der Migration<br />

und Replikation eingesetzt werden, wird in den Abschnitten 6.2 und 6.3<br />

beschrieben.<br />

1 ..*<br />

Abbildung 6.1: Klassenhierachie einer im JScatter-System migrierbaren und<br />

replizierbaren Klasse Example<br />

Für die Durchführung der Transformation wird BCEL [Dah1998, Dah2001]<br />

54<br />

besitzt<br />

Object<br />

1 ..*<br />

1 ..*<br />

besitzt<br />

besitzt<br />

besitzt<br />

_ExampleMigrationMemento<br />

_ExampleReplicationMemento<br />

1<br />

MigrateableCuckoo<br />

Example<br />

_ExampleHandel<br />

InstanceManager<br />

_ExampleInstanceManager<br />

Serializable<br />

Handle<br />

UnicastRemoteObject<br />

_ExampleInstanceManagerInterface<br />

Memento<br />

{abstract}<br />

ReplicationMemento<br />

MigrateableHandle<br />

_ExampleClassObjectReplicationMemento<br />

Remote<br />

Legende<br />

X Y<br />

X Y<br />

X<br />

n<br />

Y<br />

X<br />

n<br />

Y<br />

_ExampleClassObject<br />

Klasse<br />

Interface<br />

X erweitert Y<br />

X implementiert Y<br />

X referenziert n Y<br />

ClassObject<br />

{abstract}<br />

AbstractMigrateableClassObject<br />

MigrateableClassObject<br />

_ExampleClassObjectInterface<br />

1 ..*<br />

besitzt<br />

_ExampleClassObjectMigrationMemento<br />

1 ..*<br />

ursprüngliche Hierarchie<br />

erweiterte Hierarchie<br />

X erzeugt n Y<br />

besitzt


6.1 TRANSFORMATION<br />

eingesetzt. Mit BCEL kann der Bytecode bestehender Java-Klassen modifiziert<br />

und neue Java-Klassen in Bytecode erzeugt werden.<br />

Die bei der Transformation einer Klasse durch Hinzufügen von Migration und<br />

Replikation entstehende Klassenhierachie und deren Beziehungen untereinander<br />

sind für eine Klasse Example in Abbildung 6.1 dargestellt.<br />

6.1.3 NUTZUNG DER STATISCHEN PROGRAMMANALYSE ZUR LAUFZEIT<br />

In Abschnitt 5.1 wurde dar<strong>auf</strong> hingewiesen, dass bei der Migrationsentscheidung<br />

Ergebnisse aus der statischen Programmanalyse <strong>zur</strong> L<strong>auf</strong>zeit verwendet<br />

werden können, um passive Objekte <strong>zur</strong> Replikation anstelle von Migration<br />

vorzuschlagen. Im Folgenden wird dar<strong>auf</strong> eingegangen, wie die Analyseergebnisse<br />

<strong>zur</strong> L<strong>auf</strong>zeit nutzbar gemacht werden, ohne die <strong>auf</strong>wändigen statischen<br />

Programmanalysen <strong>zur</strong> L<strong>auf</strong>zeit durchzuführen.<br />

JScatter-Transformator<br />

Class A<br />

Class B<br />

Class E<br />

Pauli<br />

Immutable-Objektanalyse<br />

Def-Use-Analyse<br />

Klassenhierachieanalyse<br />

Text-Datei<br />

Rechner 1<br />

Class A<br />

Class B<br />

Class E<br />

Daten einlesen<br />

StaticAnalysisResultsManager<br />

Migrationsstrategie<br />

Class C<br />

................<br />

Class B<br />

................ ................<br />

Class A<br />

................ ................<br />

A<br />

................<br />

................ ................<br />

................<br />

................<br />

................<br />

Anwendung<br />

JScatter-L<strong>auf</strong>zeitumgebung<br />

Class D<br />

Class ................ E<br />

................<br />

................<br />

................<br />

................<br />

Legende<br />

vor der L<strong>auf</strong>zeit<br />

während der Transformation<br />

<strong>zur</strong> L<strong>auf</strong>zeit vor der Ausführung der Anwendung<br />

Rechner 2<br />

Class A<br />

Class B<br />

Class E<br />

Daten einlesen<br />

StaticAnalysisResultsManager<br />

Migrationsstrategie<br />

...........<br />

...........<br />

............<br />

durch Pauli erkanntes<br />

Immutable-Objekt<br />

Abbildung 6.2: Die Aufbewahrung statischer Analyseergebnisse nach ihrer<br />

Ermittlung und ihre Bereitstellung <strong>zur</strong> L<strong>auf</strong>zeit<br />

Während der Transformation der Anwendung werden alle Klassen, deren<br />

Objekte immutable sind, durch Immutable-Objektanalyse identifiziert und<br />

deren Namen in einer Text-Datei gespeichert. Beim Start der JScatter-L<strong>auf</strong>zeitumgebung<br />

werden die Namen aus der Datei in eine Datenstruktur eingelesen.<br />

55


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Soll <strong>zur</strong> L<strong>auf</strong>zeit in einer Migrationsstrategie entschieden werden, ein Objekt<br />

zu migrieren oder zu replizieren, kann die Entscheidungsstrategie als Entscheidungshilfe<br />

über die Datenstruktur anfragen, ob die statische Programmanalyse<br />

dieses Objekt <strong>zur</strong> Replikation vorschlägt oder nicht. Die Abbildung 6.2 zeigt<br />

die Vorgehensweise im Überblick.<br />

Die Schnittstelle zum Bereitstellen der Ergebnisse aus der statischen<br />

Programmanalyse für Entscheidungsstrategien ist sehr einfach gehalten. Es<br />

wäre wünschenswert, bei der Weiterentwicklung des Systems die Schnittstelle<br />

so zu erweitern, dass abhängig vom Kontext in dem ein Objekt <strong>zur</strong> L<strong>auf</strong>zeit<br />

verwendet wird, Ergebnisse aus statischer Programmanalyse nutzen zu können,<br />

um über Migration oder Replikation eines Objektes <strong>zur</strong> L<strong>auf</strong>zeit zu entscheiden.<br />

6.2 Migration<br />

In Abschnitt 5.2.1 wurde die Vorgehensweise bei der Migration für Instanzenund<br />

Klassenanteil-repräsentierende Objekte vorgestellt. In diesem Abschnitt<br />

werden die einzelnen Schritte bei der Migration ausführlicher beschrieben.<br />

Jedes für die Migration bei der Transformation vorbereitete Objekt besitzt eine<br />

_migrate(int)-Methode. Diese _migrate(int)-Methode wird von der<br />

Migrationsstrategie <strong>auf</strong>gerufen, wenn die Entscheidungsstrategie die Migrationsentscheidung<br />

für ein Objekt positiv beantwortet hat. Über den Parameter<br />

der Methode legt die Migrationsstrategie den Rechner fest, <strong>auf</strong> den das Objekt<br />

migriert werden soll.<br />

Beim Ausführen der _migrate(int)-Methode (siehe Abbildung 6.3) wird<br />

das zu migrierende Objekt zuerst als Migration aktiv makiert und alle weiteren<br />

Methodenzugriffe <strong>auf</strong>gehalten. Bevor mit der Migration des Objektes<br />

begonnen wird, wird solange gewartet, bis alle Methodenzugriffe außer dem,<br />

der die Migration ausgelöst hat, abgearbeitet sind.<br />

Ist diese Situation eingetreten, werden alle <strong>auf</strong> entfernten Rechnern vorhandenen<br />

Replikate des Objektes gelöscht. Dies ist notwendig, da ein Objekt nach<br />

seiner Migration eine neue eindeutige ID zugewiesen bekommt, und alle<br />

Replikate seiner Replikatgruppe über die gleiche ID verfügen müssen. Durch<br />

das Löschen der Replikate bei der Migration eines Instanzenanteilrepräsentierenden<br />

Objektes wird die Konsistenz zwischen dem zu migrierenden<br />

Objekt und seinen Replikaten gewahrt.<br />

Nachdem die Replikate gelöscht wurden, wird der Objektzustand in ein byte-<br />

Array serialisiert. Hierfür wird die Transfer-Komponente der aktuellen<br />

Migrationsstrategie verwendet. Zum Erzeugen des Objektzustand-Abbildes<br />

besitzt jedes Instanzenanteil-repräsentierende Objekt eine spezielle Methode.<br />

Abhängig vom Objekt erzeugt die Methode ein Abbild des Objektzustandes als<br />

Memento.<br />

Nach der Serialisierung beschafft sich das Objekt eine Referenz <strong>auf</strong> einen<br />

56


6.2 MIGRATION<br />

entfernten InstanceManager <strong>auf</strong> dem Rechner, zu dem das Objekt migriert<br />

werden soll. Hierbei wird entweder ein neuer InstanceManager erzeugt oder<br />

die Referenz <strong>auf</strong> einen existierenden <strong>zur</strong>ückgegeben. Über die InstanceManager-Referenz<br />

wird <strong>auf</strong> dem Rechner der neuen Platzierung ein neues<br />

Objekt des konkreten Typs erzeugt und anschließend der deserialisierte<br />

Objektzustand gesetzt. Hierfür besitzt jedes MigrateableCuckoo-Objekt<br />

eine Methode, über die der deserialisierte Objektzustand in Form eines<br />

Mementos gesetzt werden kann.<br />

Nach dem Setzen des Zustandes wird das Objekt als migriert gekennzeichnet.<br />

Über diese Kennzeichnung kann eine Migrationsstrategie feststellen, ob das zu<br />

migrierende Objekt schon einmal migriert wurde. Hierdurch kann bei<br />

Ausführen einer Anwendung der sogenannte Ping-Pong-Effekt [GL1994]<br />

vermieden werden. Beim Ping-Pong-Effekt wird eine Objekt zwischen zwei<br />

Rechner hin und her migriert. Erlaubt eine Migrationsstrategie jedoch nur die<br />

Einfach-Migration von Objekten, kann dieser Effekt nicht <strong>auf</strong>treten.<br />

Nachdem das Objekt als migriert gekennzeichnet ist, wird ein Stellvertreter des<br />

migrierten Instanzenanteil-repräsentierenden Objektes <strong>auf</strong> dem Rechner der<br />

neuen Platzierung erzeugt. Beim Initialisieren des Stellvertreters wird dem<br />

Objekt an seiner neuen Platzierung die neue eindeutige ID zugewiesen.<br />

Anschließend wird das Handle als migriert gekennzeichnet und die ID des<br />

Rechners zu dem das Objekt migriert wurde gesetzt. Zum Abschluss der<br />

Migration wird das migrierte Objekt im InstanceManager der alten Platzierung<br />

durch den neuen als migriert gekennzeichneten Stellvertreter ersetzt.<br />

Durch die Kennzeichnung des Stellvertreters als migriert, wird beim nächsten<br />

Zugriff <strong>auf</strong> die alte Platzierung des Objektes im Stellvertreter eine Moved-<br />

Exception ausgelöst, die die neue eindeutige ID des Objektes enthält. Auf<br />

diese Weise wird dem Zugreifer, beim Auslösen der Ausnahme, die neue<br />

Platzierung des Objektes mitgeteilt.<br />

Nach dem die Migration abgeschlossen ist, wird das Objekt wieder <strong>auf</strong><br />

Migration inaktive gesetzt. Der Quellcode eines für die Migration vorbereiteten<br />

Instanzenanteil-repräsentierenden Klasse kann in Anhang B nachgeschlagen<br />

werden.<br />

Die Migration eines Klassenanteil-repräsentierenden Objektes läuft bis zum<br />

Serialisieren des Objektzustandes in Form eines Memento-Objektes analog zu<br />

einem Instanzenanteil-repräsentierenden Objekt ab. Jedoch erfolgt der Umzug<br />

und das Ersetzen der alten durch eine neue Referenz <strong>auf</strong> eine andere Weise ab.<br />

Über die lokale Node wird <strong>auf</strong> die Node der neuen Platzierung der serialisierte<br />

Objektzustand als byte-Array übertragen. Anschließend wird aus dem byte-<br />

Array über die ObjectTransferer-Komponente der aktuellen Migrationsstrategie<br />

ein Memento erzeugt. Unter Einsatz des Reflektion-Mechanismus von<br />

Java wird ein neues Objekt des Klassenanteil-repräsentierenden Objektes<br />

instanziert und anschließend der deserialisierte Objektzustand in Form eines<br />

57


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Mementos gesetzt. Damit <strong>auf</strong> das neue Objekt von der entfernten Platzierung<br />

aus zugegriffen werden kann, wird es in KaRMI gebunden und anschließend<br />

die neue Platzierung allen Nodes im JScatter-System bekannt gegeben. Mit der<br />

Bekanntgabe ist die Migration abgeschlossen. Nun wird das alte Objekt <strong>auf</strong><br />

Migration inaktiv gesetzt.<br />

In Abschnitt 6.2.1 wird beschrieben, wie während der Migration eines<br />

Objektes eingehende Methoden<strong>auf</strong>rufe <strong>auf</strong>gehalten und nach Abschluss der<br />

Migration an die neue Platzierung des Objektes delegiert werden.<br />

6.2.1 METHODENZUGRIFFE<br />

Während der Migration eines Instanzenanteil-repräsentierenden Objektes<br />

werden alle Methoden<strong>auf</strong>rufe im InstanceManager des Objektes abgefangen.<br />

Nach der Migration werden die Zugriffe durchgeführt. Da zum Abschluss<br />

der Migration das Original-Objekt durch ein Handle, das <strong>auf</strong> die neue<br />

Platzierung des Objektes verweist, ausgetauscht wurde, werden die Zugriffe<br />

nach der Migration über das Handle ausgeführt. Hier erfolgt dann das schon<br />

mehrmals erwähnte Auslösen der MovedException. Da der Zugreifer immer<br />

über einen Handle <strong>auf</strong> ein Instanzenanteil-repräsentierendes Objekt zugreift,<br />

und im InstanceManager die MovedException zum <strong>auf</strong>rufenden Handle<br />

weitergegeben wird, ist es sinnvoll, die MovedException im Handle<br />

abzufangen. Beim Behandeln der abgefangenen MovedException wird eine<br />

Referenzaktualisierung durchgeführt. Hierbei wird per Lokalitätsoptimierung<br />

überprüft, ob sich das Objekt nach der Migration lokal befindet. Andernfalls<br />

wird der Zugriff entfernt über KaRMI ausgeführt.<br />

Bei jeder Methodenausführung wird vor der eigentlichen Abarbeitung der Methode<br />

eine Zählervariable inkrementiert, über die die Anzahl der aktuell in<br />

Abarbeitung stehenden Methodenzugriffe des Objektes gezählt werden. Am<br />

Ende der Methode wird die Zählvariable dekrementiert. Für Methoden ohne<br />

Rückgabewert ist dies kein Problem, jedoch muss für Methoden mit<br />

Rückgabewert dieser zwischengespeichert und anschließend nach der Dekrementierung<br />

dessen Rückgabe erfolgen.<br />

Bei Klassenanteil-repräsentierenden Objekten werden die Methoden<strong>auf</strong>rufe,<br />

die während der Migration des Objektes erfolgen, direkt in den zugegriffenen<br />

Methoden des zu migrierenden Objektes abgefangen (siehe Beispiel 6.1). Ist<br />

die Migration beendet, wird eine Referenzaktualisierung innerhalb der<br />

Methode durchgeführt und der Methoden<strong>auf</strong>ruf an die neue Platzierung des<br />

Objektes delegiert. Auch bei Klassenanteil-repräsentierenden Objekten wird<br />

die Anzahl der aktiven Methodenzugriffe gezählt. Die Inkrementierung des<br />

Zählers darf jedoch erst nach der Abfrage, ob eine Migration des Objektes<br />

aktuell durchgeführt wird, erfolgen, da es sonst zu einem Deadlock zwischen<br />

auszuführender Migration und auszuführenden Methoden kommen kann. In<br />

einer solchen Situation kann die Migration nicht durchgeführt werden, weil<br />

gerade <strong>auf</strong> das zu migrierende Objekt zugegriffen wird und die Zugriffe nicht<br />

58


6.2 MIGRATION<br />

ausgeführt werden können, da die Migration des Objektes gerade durchgeführt<br />

wird. Die Dekrementierung der Zugriffszählung erfolgt analog zu Instanzenanteil-repräsentierenden<br />

Objekten.<br />

05<br />

10<br />

15<br />

20<br />

void addElem(Object elem, ...) throws RemoteException<br />

{<br />

while(_isMigrationActive()){} // warten bis Migration beendet ist<br />

_numberOfActivedMethodCalls++; // Anzahl der aktiven Methodenzugriffe inkrementieren<br />

if(_updateReferenz()) // Aktualisierung der Referenz durchführen<br />

{<br />

_updateReferenz = false; // Referenzaktualisierung <strong>auf</strong> durchgeführt setzen<br />

_ExampleClassObjectInterface _classObject = (_ExampleClassObjectInterface)<br />

RuntimeEnvironment.getClassObject(_getClassName());<br />

// Anzahl der aktiven Methodenzugriffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

// Methodenzugriff <strong>auf</strong> aktualisierter Referenz durchführen<br />

_classObject.addElem(elem, ...);<br />

}<br />

else<br />

{ ... }<br />

}<br />

Beispiel 6.1: Referenzaktualisierung der Methode eines Klassenanteilrepräsentierenden<br />

Objektes<br />

Ein ausführliches Beispiel für Instanzenanteil- und Klassenanteil-repräsentierende<br />

Objekte kann im Anhang B nachgeschlagen werden.<br />

6.3 Replikation<br />

In Abschnitt 5.2.2 wurden zwei grundlegende Replikationsstrategien vorgestellt,<br />

die das JScatter-System unterstützt. In diesem Abschnitt werden zuerst<br />

die Gemeinsamkeiten bei der Realisierung der beiden <strong>Strategien</strong> vorgestellt,<br />

bevor ihre wesentlichen Unterschiede erläutert werden.<br />

Das Erstellen eines Replikats ist in beiden Replikationsstrategien mit der in<br />

Abschnitt 6.2 geschilderten Vorgehensweise bei Migration vergleichbar. Auch<br />

bei der Replikation, egal ob für Instanzenanteil- oder Klassenanteil-repräsentierende<br />

Objekte, wird das zu replizierende Objekt nach der Replikationsentscheidung<br />

für weitere Methodenzugriffe gesperrt. Bevor mit der Serialisierung<br />

des Objektzustandes begonnen wird, müssen alle vor der Replikationsentscheidung<br />

begonnenen Methodenzugriffe abgearbeitet sein.<br />

Anders als bei der Migration werden bei der Serialisierung des Objektzustandes<br />

in Form eines Mementos zusätzlich zu den nicht-transienten Feldern<br />

des Objektes auch noch dessen eindeutige ID mit serialisiert. Beim Erzeugen<br />

des Replikats <strong>auf</strong> dem Zielrecher werden die Replikate unabhängig von dem<br />

<strong>auf</strong> dem Rechner befindlichen Originalen in einer eigenen Datenstruktur<br />

gespeichert. Auf diese Weise können Zugriffe <strong>auf</strong> Replikate und Zugriffe <strong>auf</strong><br />

Originale unterschieden werden. Durch die gemeinsame eindeutige ID des<br />

Original-Objektes und seiner Replikate wird dem <strong>auf</strong> ein Replikat zugreifenden<br />

Objekt suggeriert, dass es <strong>auf</strong> ein entferntes Original-Objekt zugreift.<br />

59


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Bevor ein Replikat in die Datenstruktur eingefügt wird, wird es als Replikat<br />

gekennzeichnet. Methodenzugriffe <strong>auf</strong> als repliziert gekennzeichnete Objekte<br />

werden nicht durch die dynamische Programmanalyse erfasst. Hierdurch ist<br />

sichergestellt, dass ein repliziertes Objekt nicht migriert wird. Die Propagierung<br />

einer Migrationsentscheidung geht immer von der Inkrementierung<br />

eines Methodenzugriffs aus. Hier<strong>auf</strong> wird in Abschnitt 6.4 näher eingegangen.<br />

Als weiterer Unterschied <strong>zur</strong> Migration, wird bei der Replikation das Original-<br />

Objekt nicht gelöscht. Nach dem Abschluss einer Replikation werden alle<br />

<strong>auf</strong>gehaltenen Methoden<strong>auf</strong>rufe ohne Referenzaktualisierung durchgeführt.<br />

Eine Referenzaktualisierung, nach der der Zugriff <strong>auf</strong> ein lokal vorhandenes<br />

Replikat durchgeführt würde, hätte einen unnötigen Mehr<strong>auf</strong>wand <strong>zur</strong> Folge.<br />

Lesezugriffe können zwar über das lokale Replikat stattfinden, aber<br />

Schreibzugriffe würden auch nach einer Referenzaktualisierung <strong>auf</strong> dem<br />

Original-Objekt ausgeführt. Aus diesem Grund werden nach dem Abschluss<br />

einer Replikation die angehaltenen Zugriffe einfach weiter abgearbeitet.<br />

Wie in Abschnitt 5.2.2 erwähnt, enthält jedes Original-Objekt, für das mindestes<br />

einmal ein Replikat erzeugt wurde, eine Replikatgruppe. Eine Replikatgruppe<br />

wird im JScatter-System durch ein ReplicaGroup-Objekt (siehe<br />

Anhang A) repräsentiert. ReplicaGroup-Objekte von Instanzenanteil-repräsentierenden<br />

Objekten enthalten für jedes Replikat eine Referenz <strong>auf</strong> einen<br />

InstanceManager_Stub-Objekt. Über die InstanceManager_Stubs<br />

kann ein Original <strong>auf</strong> seine Replikate zugreifen. Handelt es sich bei dem<br />

Original-Objekt um ein Klassenanteil-repräsentierendes Objekt, enthält das<br />

ReplicaGroup-Objekt ClassObject_Stub-Objekte für Zugriffe <strong>auf</strong> seine<br />

Replikate.<br />

6.3.1 METHODENZUGRIFFE<br />

Nach der Einführung der Replikation von Objekten im JScatter-System müssen<br />

Lesezugriffe und Schreibzugriffe unterschiedlich behandelt werden. Lesezugriffe<br />

können, wenn ein lokales Replikat existiert, <strong>auf</strong> diesem ausgeführt<br />

werden. Schreibzugriffe hingegen werden zum Original-Objekt weitergeleitet,<br />

egal, ob ein lokales Replikat existiert oder nicht.<br />

Beispiel 6.2 zeigt den Aufbau einer, einen Schreibzugriff ausführenden<br />

Methode eines Instanzenanteil-repräsentierenden Objekts. Schreibzugriffe<br />

müssen je nach Replikationsstrategie unterschiedlich behandelt werden. Deshalb<br />

wird vor dem Ausführen eines Schreibzugriffes der Typ der auszuführenden<br />

Replikationsstrategie über die aktuelle Migrationsstrategie ermittelt.<br />

Sollen alle Replikate vor dem Durchführen des Schreibzugriffes gelöscht<br />

werden (Strategie: DELETE_REPLICAS_BY_WRITEACCESS), so wird dies<br />

durch einen Aufruf der gleichnamigen Lösch-Methode durchgeführt, bevor der<br />

Schreibzugriff erfolgt. Beim Ausführen der Lösch-Methode werden über die<br />

InstanceManager_Stub-Objekte der Replikatgruppe des Objektes, alle<br />

existierenden Replikate des Objektes in den entfernten InstanceManager-<br />

60


Objekten gelöscht.<br />

05<br />

10<br />

15<br />

20<br />

25<br />

6.3 REPLIKATION<br />

void addElem(Object elem, ...) throws RemoteException, MovedException<br />

{<br />

...<br />

// Replikations-Strategie-Typ beschaffen<br />

int strategieType = RuntimeEnvironment.getMigrationStrategy()<br />

.getDecidionStrategy().getReplicationStrategieType();<br />

// Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Löschen"<br />

if(strategieType == DecidionStrategy.REPLICATIONTYPE_DELETE_REPLICAS_BY_WRITEACCESS)<br />

{<br />

deleteReplicaByWriteAccess(); // Replikate löschen<br />

map.put(elem.toString(), elem); // Zugriff ausführen<br />

}<br />

// Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Aktualisieren"<br />

else if (strategieType ==<br />

DecidionStrategy.REPLICATIONTYPE_UPDATE_REPLICAS_BY_WRITEACCESS)<br />

{<br />

lockReplicaForWriteAccess(); // Replikate für Zugriffe sperren<br />

map.put(elem.toString(), elem); // Zugriff ausführen<br />

updateAndReleaseReplicaForWriteAccess(); // Replikate aktualisieren und freigeben<br />

...<br />

}<br />

Beispiel 6.2: Schreibzugriff-ausführende Methode <strong>auf</strong> einem Instanzenanteilrepräsentierendem<br />

Objekt<br />

Bei der zweiten Strategie (UPDATE_REPLICAS_BY_WRITEACCESS)<br />

werden die Replikate eines Objektes nach der Durchführung des Schreibzugriffes<br />

aktualisiert. Damit <strong>auf</strong> Replikaten während der Ausführung des<br />

Schreibzugriffes von zugreifenden Objekten keine inkonsistenten Werte<br />

gelesen werden, werden alle Zugriffe <strong>auf</strong> Replikate während der Durchführung<br />

eines Schreibzugriffes <strong>auf</strong>gehalten. Hierfür wird eine Zugriffs-Sperre gesetzt.<br />

Nachdem alle Replikate gesperrt sind, wird der Schreibzugriff <strong>auf</strong> dem<br />

Original ausgeführt. Anschließend werden alle Replikate nacheinander<br />

aktualisiert und freigegeben. Bei der Aktualisierung wird der Objektzustand<br />

des Original-Objektes als Memento serialisiert und nach der Übertragung zum<br />

Replikat deserialisiert und <strong>auf</strong> diesem gesetzt. Die Aktualisierung und die<br />

Freigabe eines Replikats finden als atomare Operation statt. Durch diese<br />

Vorgehensweise wird jeweils ein entfernter Zugriff <strong>zur</strong> Freigabe eines<br />

Replikats nach der Aktualisierung gespart. Hierdurch befinden sich die<br />

Replikate kurzweilig in inkonsistentem Zustand. Dies hat jedoch keine<br />

negativen Folgen für die Ausführung des Programms, da zugreifende Objekte<br />

durch die Sperre den inkonsistenten Zustand nicht lesen können.<br />

Damit Lesezugriffe auch tatsächlich <strong>auf</strong> lokal existierenden Replikaten<br />

ausgeführt werden, wird bei jedem Zugriff im Handle eines Instanzenanteilrepräsentierenden<br />

Objektes überprüft, ob ein lokales Replikat existiert. Gibt es<br />

jedoch kein lokales Replikat, wird der Lesezugriff über den zugehörigen<br />

InstanceManager an das Original-Objekt weitergeleitet.<br />

Bei Klassenanteil-repräsentierenden Objekten läuft die Vorgehensweise für<br />

beide Replikationsstrategien analog ab. Damit ein Lesezugriff jedoch <strong>auf</strong> dem<br />

lokal existierenden Replikat ausgeführt wird, muss das Objekt sich eine<br />

61


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Referenz <strong>auf</strong> das lokale Replikat beschaffen. Hierfür steht eine spezielle<br />

Methode <strong>zur</strong> Verfügung. Über die erhaltene Referenz wird der Lesezugriff<br />

ausgeführt. Existiert kein lokales Replikat des zu referenzierenden Objektes,<br />

liefert die Methode eine Referenz zum Original-Objekt. Schreibzugriffe<br />

werden immer <strong>auf</strong> dem Original-Objekt ausgeführt.<br />

Ein ausführliches Quelltext-Beispiel, dass die Unterschiede zwischen Leseund<br />

Schreibzugriffen im Zusammenhang mit Replikation zeigt, kann in<br />

Anhang B nachgeschlagen werden.<br />

6.4 Migrationsstrategien<br />

In diesem Abschnitt wird zum Einen die Architektur und zum Anderen die<br />

Erweiterbarkeit von Migrationsstrategien im JScatter-System erläutert.<br />

Der Aufbau einer Migrationsstrategie unterteilt sich in die in Abschnitt 5.3<br />

identifizierten Komponenten Informationserfassung, Entscheidung und<br />

Transfer. Für die Architektur der Migrationsstrategien wurde das Entwurfsmuster<br />

Strategy [GHJV1995] eingesetzt. Hierdurch sind Migrationsstrategien<br />

dynamisch austauschbar.<br />

Die Informationserfassungs-Komponente führt die dynamische Programmanalyse<br />

aus. Über sie kann eine Migrationsstrategie verschiedene Informationen<br />

über die Zugriffe <strong>auf</strong> ein Objekt ermitteln. Hierbei wird zwischen<br />

entfernten und lokalen und zwischen Lese-, Schreib- und Nicht-Lese-Oder-<br />

Schreibzugriffen unterschieden (siehe Abschnitt 6.1.1). Somit kann eine<br />

Entscheidungsstrategie sowohl die Anzahl aller lokalen oder entfernten<br />

Zugriffe <strong>auf</strong> ein Objekt als auch die Anzahl aller Zugriffe von einem konkreten<br />

Rechner aus <strong>auf</strong> das Objekt ermitteln. Im Anhang C ist ein Beispiel für die von<br />

der Basisimplementierung der Informationserfassungs-Komponente erfassten<br />

Informationen nachzulesen.<br />

Über die Informationserfassungs-Komponente stehen der Entscheidungsstrategie<br />

auch die Ergebnisse aus der statischen Programmanalyse <strong>zur</strong> Verfügung.<br />

Sie werden über einen StaticAnalysisResultsManager aus einer Text-<br />

Datei eingelesen.<br />

Die Transfer-Komponente einer Migrationsstrategie stellt eine Schnittstelle <strong>zur</strong><br />

Serialisierung und Deserialisierung von Memento-Objekten bereit. Des<br />

Weiteren bietet sie eine Schnittstelle an, über die eine Entscheidungsstrategie<br />

die Migration oder Replikation eines Instanzenanteil- oder Klassenanteilrepräsentierenden<br />

Objektes initiieren kann.<br />

Die Entscheidungs-Komponente ist die zentrale Komponente einer Migrationsstrategie.<br />

In ihr erfolgen die Auswertung der durch die Informationserfassungs-<br />

Komponente gewonnenen Informationen. Die Entscheidungsstrategie enthält<br />

eine Schnittstelle, über die die Schwellwerte der Migraionsstrategie zugegriffen<br />

werden können. In der Basisimplementierung sind die Schwellwerte über<br />

einen StrategyPropertiesManager konfigurierbar. Er liest die vordefi-<br />

62


6.4 MIGRATIONSSTRATEGIEN<br />

nierten Werte aus einer Datei ein. Auf diese Weise kann eine Migraionsstrategie<br />

ohne die Implementierung zu verändern für eine auszuführende<br />

Anwendung eingestellt werden.<br />

Eine Migrationsstrategie wird <strong>zur</strong> L<strong>auf</strong>zeit durch ein MigrationStrategy-<br />

Objekt repräsentiert. Es enthält eine Schnittstelle zu den drei Komponenten<br />

einer Migraionsstrategie (siehe Abbildung 6.4). Beim Implementieren einer<br />

neuen Migrationsstrategie kann der Programmierer entweder die Schnittstellen<br />

der einzelnen Komponenten selbst implementieren oder deren Basisimplementierung<br />

erweitern bzw. verwenden. Bei den meisten <strong>Strategien</strong> wird jedoch<br />

nur die Entscheidungsstrategie verändert werden.<br />

MigrationStrategy<br />

+ getInformationRegistration() : InformationRegistration<br />

+ getDecidionStrategy() : DecidionStrategy<br />

+ getObjectTransferer() : ObjectTransferer<br />

Abbildung 6.4: Schnittstelle einer Migrationsstrategie<br />

Eine Entscheidungsstrategie stellt die Methode checkMigration(...)<br />

bereit. Über diese Schnittstelle fordert die Informationserfassungs-Komponente<br />

die aktuelle Entscheidungsstrategie <strong>auf</strong>, für ein lokal platziertes Objekt die<br />

Migration oder Replikation des Objektes zu überprüfen.<br />

Im JScatter-System ist ein Mechanismus implementiert, mit dem in einer<br />

Entscheidungsstrategie kontrolliert werden kann, dass eine Migration nur dann<br />

stattfindet, wenn zu diesem Zeitpunkt keine andere <strong>Objektmigration</strong> oder<br />

Objektreplikation erfolgt. Für diesen Mechanismus steht am zentralen Punkt<br />

des Systems, dem RuntimeManager, eine Schnittstelle bereit, die von einer<br />

Entscheidungsstrategie über das lokale RuntimeEnvironment verwendet<br />

werden kann. Zusammenfassend stellt Abbildung 6.5 die Architektur einer<br />

Migrationsstrategie dar.<br />

RuntimeEnvironment<br />

InformationRegistration<br />

DefaultInformationRegistration<br />

StrategyPropertiesManager<br />

Legende<br />

StrategyManager<br />

Klasse<br />

Legende<br />

MigrationStrategy<br />

DefaultMigrationStrategy<br />

DefaultDecidionStrategy<br />

Abbildung 6.5: Architektur einer Migrationsstrategie<br />

+ Sichtbarkeiten public<br />

Interface<br />

Sichtbarkeit Methodenname() : Rückgabewert<br />

X Y<br />

X implementiert Y<br />

Interface X Y X referenziert Y<br />

X Y X erzeugt Y<br />

ObjectTransferer<br />

DefaultObjectTransferer<br />

DecidionStrategy<br />

63


KAPITEL 6 DIE REALISIERUNG DES MODELLS IM JSCATTER-SYSTEM<br />

Über den StrategyPropertiesManager lädt der StrategyManager die<br />

vom Benutzer eingestellte Migrationsstrategie. Die Erzeugung einer Migrationsstrategie<br />

erfolgt über Java-Reflektion. Der StrategyManager ist durch<br />

Einsatz des Entwurfsmusters Singleton [GHJV1995] modelliert. Hierüber ist<br />

sichergestellt, dass im JScatter-System <strong>zur</strong> L<strong>auf</strong>zeit alle Migrations- und<br />

Replikationsentscheidungen von der selben Migrationsstrategie durchgeführt<br />

werden.<br />

64


7 EVALUATION<br />

7 EVALUATION<br />

In diesem Kapitel werden die Messergebnisse zweier Beispielsimulationen für<br />

verschiedene Migrationsstrategien vorgestellt. Insgesamt wurden für die<br />

Evaluation 460 Messungen durchgeführt, deren Ergebnisse im Einzelnen im<br />

Anhang D nachgeschlagen werden können.<br />

Die Messergebnisse, welche in den Abschnitten 7.1 und 7.2 vorgestellt werden,<br />

wurden <strong>auf</strong> unterschiedlichen Rechnersystemen erzielt. Für die Messungen des<br />

Abschnitts 7.1 kamen ein Rechner mit zwei Pentium III Prozessoren mit 900<br />

MHz, ein Rechner mit einem Pentium III Prozessor mit 800 MHz und ein<br />

Rechner mit einem Pentium 4 Prozessor mit 1,70 GHz zum Einsatz. Die Messungen<br />

des Abschnitts 7.2 wurden <strong>auf</strong> zwei Rechnern mit jeweils einem<br />

Pentium 4 Prozessor mit 1,70 GHz und zwei Rechnern mit jeweils einem Pentium<br />

4 Prozessor mit 2.40 GHz durchgeführt. Zum Zeitpunkt der Messungen<br />

war Java in Version 1.4.2 und das Betriebssystem Linux (Red Hat) <strong>auf</strong> den<br />

verwendeten Rechnern installiert.<br />

Für die Initialplatzierung der Objekte beider Beispielsimulationen wurde eine<br />

zyklische Verteilungsstrategie eingesetzt. Durch den Einsatz dieser Verteilungsstrategie<br />

wurden die zu verteilenden Objekte bei jeder vergleichbaren<br />

Messung <strong>auf</strong> den selben Rechnern platziert.<br />

7.1 Unterschiede bei Migration und Replikation<br />

Für die Untersuchung der Unterschiede Instanzenanteil- und Klassenanteilrepräsentierender<br />

Objekte im Zusammenhang mit Migration und Replikation,<br />

wurde eine Simulation namens Post eingesetzt. Diese Simulation besteht aus<br />

drei Objekten, von denen ein Objekt (Posttasche) zwischen zwei nicht migrierbaren<br />

bzw. replizierbaren Thread-Objekten (Post und Briefträger) abwechselnd<br />

lesend und schreibend zugegriffen wird. Die Anwendung simuliert folgende<br />

Situation:<br />

Ein Briefträger trägt die in einer Posttasche befindlichen Briefe aus. Ist<br />

die Posttasche leer, geht er <strong>zur</strong> Post und lässt sie sich wieder <strong>auf</strong>füllen.<br />

Ist die Posttasche neu gefüllt, verteilt er alle in ihr befindlichen Briefe.<br />

Nachdem der Briefträger alle Briefe verteilt hat, und <strong>auf</strong> der Post keine<br />

Briefe mehr vorhanden sind, wird die Post geschlossen.<br />

Bei dieser Simulation können zwei Parameter konfiguriert werden, zum Einen<br />

die Anzahl der in der Posttasche pro Füllung befindlichen Briefe und zum<br />

Anderen die Anzahl der Füllungen der Posttasche.<br />

Durch die Variierung der Anzahl der Briefe pro Füllung wird die Anzahl der<br />

ohne Unterbrechung nacheinander von einem Thread-Objekt stattfindenden<br />

Zugriffe verändert. Im Gegensatz hierzu ändert die Verwendung unterschiedlicher<br />

Werte für die Anzahl der Füllungen der Posttasche, die Anzahl der<br />

Iterationen, der abwechselnd <strong>auf</strong> die Posttasche zugreifenden Thread-Objekte.<br />

65


KAPITEL 7 EVALUATION<br />

Um die Unterschiede von Instanzenanteil- und Klassenanteil-repräsentierenden<br />

Objekten im Zusammenhang mit Migration und Replikation untersuchen zu<br />

können, wurde die Simulation zweimal implementiert. Hierbei wird die Posttasche<br />

einmal durch ein Instanzenanteil- und einmal durch ein Klassenanteilrepräsentierendes<br />

Objekt dargestellt.<br />

Des Weiteren wurden unterschiedliche Migrations- bzw. Replikationsstrategien<br />

eingesetzt. Die eingesetzte Migrationsstrategie lautet wir folgt:<br />

Überprüfe nach 20 Zugriffen <strong>auf</strong> ein Objekt, ob die Anzahl der entfernten<br />

Zugriffe vom Rechner des aktuell zugreifenden Objekts aus größer ist,<br />

als die Anzahl aller lokalen Zugriffe <strong>auf</strong> das zugegriffene Objekt. Ist dies<br />

der Fall, dann migriere das zugegriffene Objekt <strong>auf</strong> den Rechner des<br />

aktuell zugreifenden Objektes. Sonst brich die Migration ab.<br />

Führe nach 50 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen<br />

aus, und beginne mit der Überprüfung von vorn.<br />

Eine Migration soll jedoch nur stattfinden, wenn aktuell keine Migration<br />

im System durchgeführt wird.<br />

Mehrfachmigration ist erlaubt.<br />

Die beiden Replikationsstrategien sind analog <strong>auf</strong>gebaut, mit dem Unterschied,<br />

dass statt Migration Replikation durchgeführt wird. Die Replikationsstrategien<br />

unterscheiden sich jedoch untereinander. Die eine Replikationsstrategie setzt<br />

den Replikationstyp DELETE_REPLICAS_BY_WRITEACCESS ein, während<br />

die andere Strategie UPDATE_REPLICAS_BY_WRITEACCESS durchführt.<br />

Die Implementierung aller <strong>zur</strong> Evaluation verwendeten <strong>Strategien</strong> inklusive<br />

einer ausführlichen Beschreibung, kann im Anhang E nachgeschlagen<br />

werden.<br />

Abbildung 7.1: Instanzenanteil-repräsentierende Objekte Gesamtübersicht<br />

66<br />

L<strong>auf</strong>zeit in Millisekunden<br />

275000<br />

250000<br />

225000<br />

200000<br />

175000<br />

150000<br />

125000<br />

100000<br />

75000<br />

50000<br />

25000<br />

0<br />

10 Füllungen der Posttasche<br />

100 1000 5000<br />

Anzahl Briefe pro Posttaschenfüllung<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

mit Replikation<br />

DELETE_REPLICAS_BY_<br />

WRITEACCESS<br />

mit Replikation<br />

UPDATE_REPLICAS_BY_<br />

WRITEACCESS


7.1 UNTERSCHIEDE BEI MIGRATION UND REPLIKATION<br />

Um nicht nur den Vergleich der <strong>Strategien</strong> untereinander durchführen zu<br />

können, wurden auch Messungen ohne Einsatz von Migration oder Replikation<br />

für den verteilten Fall durchgeführt. Die Ergebnisse der Messungen der<br />

Postsimulation für Instanzenanteil-repräsentierende Objekte mit 10 Füllungen<br />

der Posttasche sind im Abbildung 7.1 dargestellt.<br />

Die Abbildung 7.1 zeigt deutlich, dass eine Replikationsstrategie wegen der<br />

vielen Schreibzugriffe für diese Anwendung ungeeignet ist. Jedoch zeigt die<br />

Abbildung auch, dass die Replikationsstrategie mit Aktualisierung der Replikate<br />

bei einem Schreibzugriff vorteilhafter ist, als alle Replikate bei einem<br />

Schreibzugriff zu löschen.<br />

Ein Vergleich der Messergebnisse ohne Migration und Replikation mit den<br />

Ergebnissen der Strategie mit Migration (siehe Abbildung 7.2) zeigt, dass die<br />

Migration mit zunehmender Anzahl der nacheinander folgenden lokale Zugriffe<br />

einen L<strong>auf</strong>zeitvorteil gegenüber der verteilten Ausführung ohne Migration<br />

und Replikation ergibt.<br />

L<strong>auf</strong>zeit in Millisekunden<br />

130000<br />

120000<br />

110000<br />

100000<br />

90000<br />

80000<br />

70000<br />

60000<br />

50000<br />

40000<br />

30000<br />

20000<br />

10000<br />

0<br />

10 Füllungen der Posttasche<br />

100 1000 5000<br />

Anzahl Briefe pro Posttaschenfüllung<br />

ohne Migration<br />

und Replikation<br />

mit Migration<br />

Abbildung 7.2: Instanzenanteil-repräsentierende Objekte; Ausschnitt ohne<br />

Replikation<br />

Bei den Messungen der Postsimulation für Klassenanteil-repräsentierende<br />

Objekte mit 10 Füllungen der Posttasche verhält es sich ähnlich (siehe<br />

Abbildung 7.3). Hier ist der Effekt zwischen den unterschiedlichen Replikationsstrategien<br />

jedoch noch deutlicher zu sehen. Auch der L<strong>auf</strong>zeitvorteil mit<br />

Migration ist gegenüber der verteilten Ausführung der Postsimulation ohne<br />

Migration und Replikation deutlicher zu sehen.<br />

Bei einem Vergleich der in Abbildung 7.2 und Abbildung 7.4 dargestellten<br />

Messergebnisse, kann eindeutig festgestellt werden, dass die Migration Klassenanteil-repräsentierender<br />

Objekte effizienter ist, als die Instanzenanteilrepräsentierender<br />

Objekte. Diese Unterschiede sind leicht zu erklären.<br />

67


KAPITEL 7 EVALUATION<br />

Abbildung 7.3: Klassenanteil-repräsentierende Objekte Gesamtübersicht<br />

Abbildung 7.4: Klassenanteil-repräsentierende Objekte; Ausschnitt ohne<br />

Replikation<br />

Bei der Migration eines Instanzenanteil-repräsentierenden Objektes wird das<br />

migrierte Original-Objekt an seiner alten Platzierung durch einen Stellvertreter<br />

ersetzt. Beim nächsten Zugriff <strong>auf</strong> das ehemalige Original-Objekt löst der<br />

Stellvertreter, welcher das migrierte Original-Objekt an der alten Platzierung<br />

ersetzt, eine MovedException aus und leitet diese über einen Instance-<br />

Manager zum Stellvertreter an der Platzierung des zugreifenden Objekts<br />

weiter. Anschließend erfolgt eine Referenzaktualisierung und ein erneuter<br />

Versuch des Zugriffs. Dieser Vorgang stellt einen erheblichen, jedoch<br />

notwendigen Aufwand dar.<br />

Bei der Migration eines Klassenanteil-repräsentierenden Objektes hingegen<br />

wird die alte Referenz durch eine neue ersetzt und nach Referenzaktualisierung<br />

direkt <strong>auf</strong> das Objekt an seiner neuen Platzierung zugegriffen. Wie in den<br />

68<br />

L<strong>auf</strong>zeit in Millisekunden<br />

450000<br />

400000<br />

350000<br />

300000<br />

250000<br />

200000<br />

150000<br />

100000<br />

50000<br />

0<br />

L<strong>auf</strong>zeit in Millisekunden<br />

10 Füllungen der Posttasche<br />

100 1000 5000<br />

Anzahl Briefe pro Posttaschenfüllung<br />

35000<br />

32500<br />

30000<br />

27500<br />

25000<br />

22500<br />

20000<br />

17500<br />

15000<br />

12500<br />

10000<br />

7500<br />

5000<br />

2500<br />

0<br />

10 Füllungen der Posttasche<br />

100 1000 5000<br />

Anzahl Briefe pro Posttaschenfüllung<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

ohne Migration<br />

und Replikation<br />

mit Migration<br />

mit Replikation<br />

DELETE_REPLICAS_BY_<br />

WRITEACCESS<br />

mit Replikation UPDA-<br />

TE_REPLICAS_BY_WR<br />

ITEACCESS


7.1 UNTERSCHIEDE BEI MIGRATION UND REPLIKATION<br />

Abbildungen 7.5 und 7.6 zu sehen ist, verändert sich dieser Effekt auch nicht,<br />

wenn die Anzahl der Füllungen der Posttasche <strong>auf</strong> 100 erhöht wird.<br />

L<strong>auf</strong>zeit in Millisekunden<br />

Abbildung 7.5: Instanzenanteil-repräsentierende Objekte Gesamtübersicht<br />

L<strong>auf</strong>zeit in Millisekunden<br />

275000<br />

250000<br />

225000<br />

200000<br />

175000<br />

150000<br />

125000<br />

100000<br />

75000<br />

50000<br />

25000<br />

0<br />

650000<br />

600000<br />

550000<br />

500000<br />

450000<br />

400000<br />

350000<br />

300000<br />

250000<br />

200000<br />

150000<br />

100000<br />

50000<br />

0<br />

100 Füllungen der Posttasche<br />

100 500<br />

Anzahl Briefe pro Posttaschenfüllung<br />

100 Füllungen der Posttasche<br />

100 500<br />

Anzahl Briefe pro Posttaschenfüllung<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

mit Replikation<br />

DELETE_REPLICAS_BY_<br />

WRITEACCESS<br />

mit Replikation UPDA-<br />

TE_REPLICAS_BY_WR<br />

ITEACCESS<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

mit Replikation<br />

DELETE_REPLICAS_BY_<br />

WRITEACCESS<br />

mit Replikation<br />

UPDATE_REPLICAS_BY_<br />

WRITEACCESS<br />

Abbildung 7.6: Klassenanteil-repräsentierende Objekte Gesamtübersicht<br />

Die Abbildungen 7.7 und 7.8 machen jedoch deutlich, dass sowohl bei<br />

Instanzenanteil- als auch bei Klassenanteil-repräsentierenden Objekten erst<br />

nach einer gewissen Anzahl von Zugriffen die Ausführung der Postsimulation<br />

mit Migrationsstrategie gegenüber der verteilten Ausführung ohne Migration<br />

und Replikation L<strong>auf</strong>zeitvorteile einbringt.<br />

Das bei der Postsimulation verwendete Zugriffsmuster mit vielen Schreibzugriffen<br />

und wenigen Lesezugriffen ist in vielen Consumer-Producer-<br />

Anwendungen zu finden.<br />

69


KAPITEL 7 EVALUATION<br />

Abbildung 7.7: Instanzenanteil-repräsentierende Objekte; Ausschnitt ohne<br />

Replikation<br />

Abbildung 7.8: Klassenanteil-repräsentierende Objekte; Ausschnitt ohne<br />

Replikation<br />

7.2 Eine komplexere Migrationsstrategie<br />

Nach dem in Abschnitt 7.1 die Evaluation einfacher Migrationsstrategien, die<br />

entweder Migration oder Replikation durchführen, vorgestellt wurde, wird in<br />

diesem Abschnitt die Evaluation einer Strategie vorgestellt, die in Abhängigkeit<br />

der Art und der Anzahl der Zugriffe <strong>auf</strong> eine Objekt entweder Migration<br />

oder Replikation des zugegriffenen Objektes durchführt. Die genaue<br />

Beschreibung der Strategie lautet:<br />

70<br />

L<strong>auf</strong>zeit in Millisekunden<br />

L<strong>auf</strong>zeit in Millisekunden<br />

140000<br />

130000<br />

120000<br />

110000<br />

100000<br />

90000<br />

80000<br />

70000<br />

60000<br />

50000<br />

40000<br />

30000<br />

20000<br />

10000<br />

0<br />

35000<br />

32500<br />

30000<br />

27500<br />

25000<br />

22500<br />

20000<br />

17500<br />

15000<br />

12500<br />

10000<br />

7500<br />

5000<br />

2500<br />

0<br />

100 Füllungen der Posttasche<br />

100 500<br />

Anzahl Briefe pro Posttasche<br />

100 Füllungen der Posttasche<br />

100 500<br />

Anzahl Briefe pro Posttasche<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

ohne Migration und<br />

Replikation<br />

mit Migration<br />

Wenn 70% aller entfernten Zugriffe vom Rechner des zugreifenden<br />

Objektes größer oder gleich der Summe aus Lese- und Nicht-Lese-Oder-<br />

Schreibzugriffen entspricht, dann Repliziere das zugegriffene Objekt


7.2 EINE KOMPLEXERE MIGRATIONSSTRATEGIE<br />

zum Rechner des zugreifenden Objektes. Ist dies nicht der Fall, dann<br />

überprüfe, ob die Gesamtanzahl der entfernten Zugriffe vom Rechner des<br />

zugreifenden Objektes größer ist, als die Anzahl aller lokalen Zugriffe<br />

des zugegriffenen Objektes. Ist dies der Fall, dann migriere das zugegriffene<br />

Objekt <strong>auf</strong> den Rechner des zugreifenden Objektes. Sonst<br />

brich die Migration ab.<br />

Führe nach 100 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen<br />

aus, und beginne mit der Überprüfung von vorn.<br />

Eine Migration oder Replikation soll jedoch nur stattfinden, wenn aktuell<br />

keine Migration oder Replikation im System durchgeführt wird.<br />

Mehrfachmigration ist erlaubt.<br />

Wird eine Replikation durchgeführt, dann führe bei einem Schreibzugriff<br />

eine Aktualisierung der Replikate der zugehörigen Replikatgruppe durch.<br />

Für die Evaluation der Strategie wurde folgende Bibliothekensimulation<br />

verwendet:<br />

In einer Universitätsbibliothek stehen Regale mit Büchern verschiedener<br />

Fachbereiche. Jeder Student kann alle Bücher seines Fachbereiches<br />

ausleihen und lesen. Ein Buch kann jedoch nicht gleichzeitig von<br />

mehreren Studenten gelesen werden. Für jeden der sieben Fachbereiche<br />

gibt es eine unterschiedliche Anzahl von Büchern. Nach dem ein Student<br />

die gewünschten Bücher gelesen hat, geht er nach Hause. Haben alle<br />

Studenten die Bibliothek verlassen, wird sie geschlossen.<br />

Bücher werden in der Simulation durch Book-Objekte repräsentiert. Book-<br />

Objekte können nur lesend zugegriffen werden. Bei der Transformation der<br />

Anwendung werden diese Objekte als immutable erkannt und einer Migrationsstrategie<br />

unter Verwendung statischer Analyseergebnisse <strong>zur</strong> Replikation<br />

vorgeschlagen. BookShelf-Objekte, die in der Simulation Bücherregale darstellen,<br />

werden sowohl lesend als auch schreibend zugegriffen. Durch ein<br />

Lesezugriff kann die Anzahl der Bücher im Regal festgestellt werden. Ein<br />

Schreibzugriff dient dazu, ein Buch aus dem Regal zu entnehmen oder in das<br />

Regal <strong>zur</strong>ückzustellen.<br />

Bei der Ausführung der Simulation wurden je zwei Student-Objekte<br />

(Threads) und ein BookShelf-Objekt pro Fachbereich sowie jeweils eine<br />

unterschiedliche Anzahl von Book-Objekten pro Fachbereich erzeugt. Die<br />

Simulation wurde <strong>auf</strong> 2, 3 und 4 Rechnern ausgeführt (siehe Abbildung 7.9).<br />

Die Abbildung 7.9 zeigt deutlich, dass der Einsatz der am Anfang dieses<br />

Abschnittes geschilderten Migrationsstrategie bei der Ausführung der Bibliothekensimulation<br />

deutliche L<strong>auf</strong>zeitvorteile gegenüber der verteilten Ausführung<br />

ohne Migration und Replikation bringt. Bei 3 und 4 Rechnern sind die<br />

erzielten L<strong>auf</strong>zeitvorteile jedoch wesentlich geringer, als bei der Ausführung<br />

71


KAPITEL 7 EVALUATION<br />

<strong>auf</strong> 2 Rechnern.<br />

Abbildung 7.9: Ergebnisse der Migrationstrategie bei der Bibliothekensimulation<br />

Der geringere L<strong>auf</strong>zeitvorteil bei der Ausführung <strong>auf</strong> 3 und 4 Rechnern ergibt<br />

sich durch mehrere Einflussfaktoren. Während bei 2 Rechnern durch die<br />

Replikation der Book-Objekte die meisten Zugriffe <strong>auf</strong> die Book-Objekte lokal<br />

ausgeführt werden, müssen bei der Verwendung von 3 und 4 Rechnern<br />

wesentlich mehr Zugriffe erfolgen, bis ein Book-Objekt repliziert wird.<br />

Ein weiterer Grund ist die zyklische Verteilung der Objekte bei ihrer Initialplatzierung.<br />

Während es bei 2 Rechnern öfter vorkommt, dass das zugreifende<br />

Objekt bereits lokal platziert ist, muss bei 3 und 4 Rechnern erst eine<br />

Replikation erfolgen, bevor alle weiteren Zugriffe lokal ausgeführt werden<br />

können.<br />

Wie die Postsimulation enthält auch die Bibliothekensimulation ein in vielen<br />

Anwendungen <strong>auf</strong>tretendes Zugriffsmuster. Im Unterschied <strong>zur</strong> Postsimulation<br />

werden bei der Bibliothekensimulation nur wenige Schreibzugriffe und viele<br />

Lesezugriffe durchgeführt. Aus diesem Grund sind bei der Postsimulation<br />

Migrationsstrategien mit Migration und bei der Bibliothekensimulation<br />

Migrationsstrategien mit Replikation zum Erzielen eines L<strong>auf</strong>zeitgewinns<br />

vorteilhafter.<br />

72<br />

L<strong>auf</strong>zeit in Millisekunden<br />

60000<br />

55000<br />

50000<br />

45000<br />

40000<br />

35000<br />

30000<br />

25000<br />

20000<br />

15000<br />

10000<br />

5000<br />

0<br />

Bibliothekensimulation<br />

2 3 4<br />

Anzahl Rechner<br />

ohne Strategie<br />

mit Strategie


8 ZUSAMMENFASSUNG UND AUSBLICK<br />

8 ZUSAMMENFASSUNG UND AUSBLICK<br />

In dieser Diplomarbeit wurde ein Modell für Migrationsstrategien entwickelt,<br />

das unter Einsatz von statischer und dynamischer Programmanalyse die<br />

Migration und Replikation von Objekten einer verteilt auszuführenden<br />

Anwendung ermöglicht. Die Struktur des entwickelten Modells ist so flexibel<br />

gehalten, dass eine einmal transformierte Anwendung unter Einsatz verschiedener<br />

<strong>Strategien</strong> ausgeführt werden kann.<br />

Damit bei der Migration eines Objektes nur dessen purer Objektzustand zum<br />

Rechner der neuen Platzierung übertragen wird, wurde in Anlehnung an das<br />

Entwurfsmuster Memento [GHJV1995] ein effizientes Konzept zum Übertragen<br />

eines serialisierten Objektzustandes entwickelt.<br />

Für die Replikation von Objekten werden zwei grundlegende Replikationstypen<br />

unterstützt. Bei dem Einen werden die vorhandenen Replikate bei einem<br />

Schreibzugriff <strong>auf</strong> dessen Original-Objekt gelöscht. Im Gegensatz hierzu<br />

werden alle vorhandenen Replikate eines Original-Objektes bei dem zweiten<br />

Replikationstyp nach dem Ausführen des Schreibzugriffs aktualisiert.<br />

Für die Evaluation des Modells wurden jeweils eine Basisimplementierung für<br />

die dynamische Programmanalyse und den Objekttransfer, sowie verschiedene<br />

einfache und eine komplexere Entscheidungsstrategie implementiert.<br />

Die Realisierung des Modells wurde im JScatter-System durchgeführt. Hierbei<br />

zeigen erste Messungen, dass schon bei kleinen Anwendungen die Migration<br />

und Replikation von Objekten L<strong>auf</strong>zeitvorteile gegenüber der verteilten Ausführung<br />

ohne Migration und Replikation einbringen.<br />

Bisher sind jedoch nur wenige, zumeist einfache Migrationsstrategien implementiert.<br />

Somit steht es noch aus, komplexe <strong>Strategien</strong> zu implementieren<br />

und für Anwendungen verschiedener Zugriffsmuster zu evaluieren.<br />

Damit der Entscheidungsstrategie einer Migrationsstrategie detailierte Ergebnisse<br />

aus der statischen Programmanalyse <strong>zur</strong> L<strong>auf</strong>zeit <strong>zur</strong> Verfügung stehen,<br />

ist es notwendig, eine Konzept <strong>zur</strong> Repräsentation statischer Programmanalyse-Ergebnisse<br />

zu entwickeln und in das JScatter-System zu integrieren.<br />

Eine weitere vorteilhafte Erweiterung wäre die Realisierung eines History-<br />

Mechanismus <strong>zur</strong> Speicherung der <strong>zur</strong> L<strong>auf</strong>zeit durchgeführten <strong>Objektmigration</strong>en.<br />

Auf der <strong>Grundlage</strong> einer solchen History könnte eine Verteilungsstrategie<br />

die Auswirkungen einer Migrationsentscheidung bestimmen<br />

und die Ergebnisse bei der Initialplatzierung eines Objektes berücksichtigen.<br />

Um die Effizienz der Basisimplementierung der Informationserfassung zu erhöhen,<br />

bietet sich eine effizientere Zählung der Objektzugriffe an. Beispielsweise<br />

bietet die Arbeit von MICHAEL STILLER [Sti2003], der eine effiziente<br />

Instrumentierung entwickelte, hierfür einen Ansatzpunkt.<br />

Bevor jedoch die vorgeschlagenen Verbesserungen in Angriff genommen<br />

werden, muss das um Migration und Replikation erweiterte Transformations-<br />

73


KAPITEL 8 ZUSAMMENFASSUNG UND AUSBLICK<br />

konzept in den Transformator des JScatter-Systems integriert werden.<br />

74


LITERATUR<br />

LITERATUR<br />

[BH2002] SARA BOUCHENAK, DANIEL HAGIMONT: Zero Overhead Java Thread<br />

Migration. INRIA, Technischer Report RT-0261, Mai 2002.<br />

http://sardes.inrialpes.fr/papers/reports.shtml<br />

[Bor2002] JENS BORRMANN: Replikation statischer Konstanten in JavaParty.<br />

Studienarbeit, Universität Karlsruhe, Institut für Programmstrukturen<br />

und Datenorganisation, Wintersemester 2001/2002.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

[BW1994] LARRY BROWN, JIE WU: Dynamic Snooping in a Fault-Tolerant<br />

Distributed Shared Memory. 14h ICDCS, 1994, Seite 218-226.<br />

[Dah1998] MARKUS DAHM: Byte Code Engineering. Freie Universität Berlin,<br />

Institut für Informatik, 1998.<br />

http://bcel.sourceforge.net/downloads/paper.pdf<br />

[Dah2000] MARKUS DAHM: The Doorastha System. Technical report B-1-<br />

2000, Freie Universität Berlin, Berlin 2000.<br />

ftp://ftp.inf.fu-berlin.de/pub/doorastha/report.pdf<br />

[Dah2001] MARKUS DAHM: Byte Code Engineering with the BCEL API.<br />

Technischer Bericht B-17-98, Freie Universität Berlin, Institut<br />

für Informatik, April 2001.<br />

http://bcel.sourceforge.net/downloads/report.pdf<br />

[DW1994] SABINE DENECKE, THOMAS WAHLBUHL: Eine adaptive Komponente<br />

für <strong>Objektmigration</strong>ssysteme unter Benutzung Neuronaler Netze.<br />

Hildesheimer Informatik-Berichte, 28/94, November 1994.<br />

http://www.mathematik.uni-hildesheim.de/fb4/berichte/<br />

1994.asp<br />

[FFF+2002] MATTHIAS FAUST, MARTIN FREUND, JYRKI FRIEDRICH, MICHAEL<br />

STILLER, MARCO WEISSENBORN, HERMANN WESSELS:<br />

Abschlussbericht der Projektgruppe Verteilung von parallelen<br />

Java-Anwendungen. Universität Paderborn, Fachbereich<br />

Mathematik-Informatik, November 2002.<br />

[GHJV1995] ERICH GAMMA, RICHARD HELM, RALPH JOHNSON, JOHN VLISSIDES:<br />

Design Patterns – Elements of Reusable Object-Oriented Software.<br />

Addison Wesley, 1995.<br />

[GJS1996] JAMES GOSLING, BILL JOY, GUY STEEL: The Java TM Language<br />

Specification. First Edition, Addison-Wesley, 1996.<br />

http://java.sun.com/docs/books/jls/index.html<br />

75


KAPITEL LITERATUR<br />

[Gol1997] MICHAEL GOLM: Design and Implementation of a Meta Architecture<br />

for Java. Diplomarbeit DA-14-02-97, Universität Erlangen-Nürnberg,<br />

IMMD 4, Erlangen 1997.<br />

http://www4.informatik.uni-erlangen.de/Projects/PM/Java/<br />

DA.abs.html<br />

[GL1994] ARND GERNS, JUNG SUN LIE: Diomedes – <strong>Objektmigration</strong>sstrategien<br />

für verteilte Umgebungen. Hildesheimer Informatik-<br />

Berichte, 27/94, November 1994.<br />

http://www.mathematik.uni-hildesheim.de/fb4/berichte/<br />

1994.asp<br />

[Jäg1999] MARKUS JÄGER: RepliXa – Ein Framework <strong>zur</strong> transparenten<br />

anpassbaren Objektreplikation. Studienarbeit, Universität Erlangen-Nürnberg,<br />

IMMD 4, Erlangen 1999.<br />

http://www4.informatik.uni-erlangen.de/Projects/PM/Java/<br />

replixa/<br />

[JGK1999] MARKUS JÄGER, MICHAEL GOLM, JÜRGEN KLEINÖDER: RepliXa – Ein<br />

Framework <strong>zur</strong> transparenten anpassbaren Objektreplikation.<br />

Universität Erlangen-Nürnberg, IMMD 4, Erlangen 1999.<br />

http://www4.informatik.uni-erlangen.de/TR/pdf/TR-I4-99-<br />

11.pdf<br />

[JLHB1988] ERIC JUL, HENRY LEVY, NORMAN HUTCHINSON, ANDREW BLACK:<br />

Fine- Grained Mobility in the Emerald System. ACM Trans. On<br />

Computer Systems 6(1), Seiten 109 -133, Februar 1988.<br />

http://www.cs.ubc.ca/nest/dsg/emerald.html<br />

[JSN2002] JODE.SOURCEFORGE.NET: Jode – Decompiler und Optimizer for<br />

Java. September 2002.<br />

http://jode.sourceforge.net<br />

[KLK2002] KASTEN KLOHS, DINH KHOI LE, UWE KASTENS: Automatic Distribution<br />

of Multi-Threated Java Programs using Distributed Plans.<br />

Technischer Bericht, Reihe Informatik tr-ri-02-234, Universität<br />

Paderborn, Fachbereich Mathematik und Informatik, 2002.<br />

http://ag-kastens.uni-paderborn.de/literatur/<br />

literatur.all.php<br />

[Klo2002] KARSTEN KLOHS: Analyse nebenläufiger Prozesse <strong>zur</strong><br />

Berechnung von Verteilungsstrategien. Diplomarbeit,<br />

Universität Paderborn, Fachbereich Mathematik-Informatik,<br />

Februar 2002.<br />

http://ag-kastens.uni-paderborn.de/literatur/<br />

literatur.all.php<br />

76


LITERATUR<br />

[Lin2002] MARKUS LINDERMEIER: Ein Konzept <strong>zur</strong> Lastverwaltung in verteilten<br />

objektorientierten Systemen. Doktorarbeit, Technische Universität<br />

München, Fakultät für Informatik – Lehrstuhl für<br />

Rechnertechnik und Rechnerorganisation, Mai 2002.<br />

http://wwwbode.cs.tum.edu/~linderme/publications.html<br />

[Lux1995] WOLFGANG LUX: Adaptierbare <strong>Objektmigration</strong> und eine Realisierung<br />

im Betriebssystem Birlix. GMD – Berichte, Nr. 252,<br />

Oldenbourg Verlag München 1995.<br />

[Mae1987] PATTIE MAES: Computational Reflection. PhD Thesis, Artifical<br />

Intelligence Laboratory, Vrieje Universiteit, Brüssel 1987.<br />

[MC1992] SILVANO MAFFEIS, CLEMENS H. CAP: Replication Heuristics and<br />

Polling Algorithms for Object Replication and a Replicating<br />

File Transfer Protocol. Institut für Informatik der Universität<br />

Zürich (IFI), IFI TR 92.06, Zürich 1992.<br />

http://www.ifi.unizh.ch/techreports/TR_1992.html<br />

[MPV1991] DEJAN S. MILOJIČIĆ, MILAN PJEVAČ, DUSAN VELAŠEVIČ: Load<br />

Balancing Survey. In: Tagungsberichte EurOpen Autumn 1991<br />

Conference, Seiten 157-172, Budapest, September 1991.<br />

[Nes1999] CHRISTIAN NESTER: Ein flexibles RMI Design für eine effiziente<br />

Cluster Computing Implementierung. Diplomarbeit, Universität<br />

Karlsruhe, Institut für Programmstrukturen und Datenorganisation,<br />

1. Juli 1999.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

[NPH1999] CHRISTIAN NESTER, MICHAEL PHILIPPSEN, BERNHARD HAUMACHER:<br />

Effizientes RMI für Java. Universität Karlsruhe, Institut für<br />

Programmstrukturen und Datenorganisation, 1999.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

[PH1999] MICHAEL PHILIPPSEN, BERNHARD HAUMACHER: More Effizient Object<br />

Serialization. In: Parallel and Distributed Processing, number<br />

1586 in Lecture Notes in Computer Science, pages 718-732,<br />

Springer Verlag. Puerto Rico, 12. - 16. April 1999.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

[PZ1997] MICHAEL PHILIPPSEN, MATTHIAS ZENGER: JavaParty – Transparent<br />

Remote Objects in Java. In: Concurrency: Practice & Experience,<br />

Volume 9, Number 11, Seiten 1225-1242, November<br />

1997.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

77


KAPITEL LITERATUR<br />

[PZJ1998] MICHAEL PHILIPPSEN, MATTHIAS ZENGER, MATTHIAS JACOB:<br />

JavaParty – portables paralleles und verteiltes Programmieren<br />

in Java. In: Proc. JIT'98, Java-Informations-Tage 1998, C. H.<br />

Cap (Hrsg.), Seiten 22-38, Frankfurt, 12.-13. November 1998,<br />

Springer Verlag (Serie Informatik Aktuell). November 1998.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

[Rei1997] NIELS REIMER: Untersuchung von <strong>Strategien</strong> für verteiltes Lastund<br />

Ressourcenmanagement. Technische Universität München,<br />

3. Juli 1997.<br />

http://www13.in.tum.de/forschung/publikationen/<br />

rei_evaadambericht.ps<br />

[RP1999] PETER RECHENBERG, GUSTAV POMBERG: Handbuch-Informatik.<br />

2., aktualisierte und erweiterte Auflage, Carl Hanser Verlag<br />

München, Wien, 1999.<br />

[SH1998] MICHAEL SCHRÖDER, FRANZ J. HAUCK: Juggle – Eine verteilte<br />

virtuelle Maschine für Java. Universität Erlangen-Nürnberg,<br />

Lehrstuhl für Betriebssysteme – IMMD IV, Erlangen 1998.<br />

http://www4.informatik.uni-erlangen.de/~fzhauck/<br />

Pub/Doc/1999-jit-juggle.pdf<br />

[Sti2003] MICHAEL STILLER: Unterstützung der <strong>automatischen</strong> Verteilung<br />

von Java-Programmen durch L<strong>auf</strong>zeitdaten. Diplomarbeit,<br />

Universität Paderborn, Fakultät für Elektrotechnik, Informatik<br />

und Mathematik, Arbeitsgruppe Programmiersprachen und<br />

Übersetzer, November 2003.<br />

[Sun2001] SUN: Java TM Object Serialization Specification. August 2001.<br />

ftp://ftp.java.sun.com/docs/j2se1.4/serial-spec.pdf<br />

[Sun2003] SUN: Java Remote Methode Invocation Specification. Juli 2003.<br />

ftp://ftp.java.sun.com/docs/j2se1.4/rmi-spec-1.4.pdf<br />

[Thi1999] MICHAEL THIES: Static compositional analysis of libraries in<br />

support of dynamic optimization. Technischer Bericht, Reihe<br />

Informatik tr-ri-99-210, Universität Paderborn Fachbereich<br />

Mathematik-Informatik, August 1999.<br />

http://ag-kastens.uni-paderborn.de/literatur/<br />

literatur.all.php<br />

[Thi2001] MICHAEL THIES: Combining Static Analysis of Java Libraries<br />

with Dynamic Optimization. Dissertation, Universität Paderborn,<br />

April 2001. SHAKER-Verlag, Achen 2002.<br />

[TK1998] MICHAEL THIES, UWE KASTENS: Statische Analyse von<br />

Bibliotheken als <strong>Grundlage</strong> dynamischer Optimierung. In:<br />

Clemens Cap, Editor, Proceedings JIT'98, Springer Verlag 1998.<br />

78


LITERATUR<br />

[TS2002a] ELI TILEVICH, YANNIS SMARAGDAKIS: J-Orchestra – Automatic Java<br />

Application Partitioning. Center for Experimental Research in<br />

Computer Sciences, College of Computing, Georgia Institut of<br />

Computer Technology, Atlanta, 2002.<br />

http://j-orchestra.org<br />

[TS2002b] ELI TILEVICH, YANNIS SMARAGDAKIS: J-Orchestra – Automatic Java<br />

Application Partitioning. Report. Center for Experimental<br />

Research in Computer Sciences, College of Computing, Georgia<br />

Institut of Computer Technology, Atlanta, 2002.<br />

http://j-orchestra.org<br />

[Veh1992] UWE VEHMEIER: Bewertung von Lastverteilungsverfahren – Eine<br />

Übersicht. Diplomarbeit, Universität Hildesheim, Dezember<br />

1992.<br />

[Zen1997] MATTHIAS ZENGER: Transparente Objektverteilung in Java. Studienarbeit,<br />

Februar 1997.<br />

http://www.ipd.uka.de/JavaParty/papers.html<br />

79


KAPITEL LITERATUR<br />

80


ANHANG A: SCHNITTSTELLEN DER REALISIERUNG<br />

ANHANG A: SCHNITTSTELLEN DER REALISIERUNG<br />

Anhang A.1: Instanzenanteil-repräsentierende Objekte<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Schnittstelle eines migrierbaren Instanzenanteil-repräsentierenden Objektes<br />

public interface MigrateableCuckoo extends Serializable<br />

{<br />

public void _setObjectID(int _objectID); // zum Aktualisieren der _objectID<br />

}<br />

public int _getObjectId();<br />

public void _setObjectIP(String _objectIP); // zum Aktualisieren der _objectIP<br />

public String _getObjectIp();<br />

public void _setClassName(String _className); // zum Setzen des Namens nach Migration<br />

public void _migrated(); // setzt das Cuckoo-Objekt migriert<br />

public boolean _isMigrated(); // Schnittstelle für Einfachmigration<br />

// Schnittstelle zum Ermitteln der aktuell aktiven Methodenzugriffe<br />

public int _numberOfActivedMethodCalls();<br />

// Schnittstelle zum Ermitteln ob das ClassObject aktuell migriert oder repliziert wird<br />

public boolean _isMigrationActive();<br />

// Schnittstelle zum Migrieren eines Cuckoo-Objekts<br />

public Handle _migrate(int destinationNodehostID) throws RemoteException;<br />

// Schnittstelle zum Replizieren eines Cuckoo-Objekts<br />

public void _replicate(int destinationNodehostID) throws RemoteException;<br />

// makiert ein Cuckoo-Objekt als Replikat<br />

public void _replicated();<br />

// gibt an, ob das Cuckoo-Objekt ein Replikat ist<br />

public boolean _isReplicated();<br />

// Schnittstelle für Replikationsstrategie: Replikate-Bei-Schreibzugriff-Löschen<br />

public void deleteReplicaByWriteAccess() throws RemoteException;<br />

// Schnittstelle für Replikationsstrategie: Replikate-Bei-Schreibzugriff-Aktualisieren<br />

public void lockReplicaForWriteAccess() throws RemoteException;<br />

// Schnittstelle für Replikationsstrategie: Replikate-Bei-Schreibzugriff-Aktualisieren<br />

public void updateAndReleaseReplicaForWriteAccess() throws RemoteException;<br />

// sperrt ein Cuckoo-Replikat Aktualisierung des Objektzustandes<br />

public void _lockReplica();<br />

// gibt an, ob das Cuckoo-Replikat aktuell <strong>zur</strong> Aktualisierung des Objektzustand<br />

// gesperrt ist<br />

public boolean isReplicaLocked();<br />

// Schnittstelle zum Aktualisieren des Objektzustandes eines Cuckoo-Replikats<br />

public void _updateReplica(byte[] serialisedState);<br />

// Erzeugt ein Abbild des Objektzustandes eines Cuckoo-Objekts für dessen Migration<br />

public Memento createMigrationMemento();<br />

// Erzeugt ein Abbild des Objektzustandes eines Cuckoo-Objekts für dessen Replikation<br />

public Memento createReplicationMemento();<br />

// Schnittstelle zum Setzen des Objektzustandes nach Migration eines Cuckoo-Objektes<br />

public void setStateAfterMigration(Memento migrationMemento);<br />

// Schnittstelle zum Aktualisieren des Objektzustandes eines Replikats nach einem<br />

// Schreibzugriff <strong>auf</strong> ein repliziertes Original-Cuckoo-Objekt<br />

public void updateStateAfterReplication(Memento replicationMemento);<br />

// Schnittstelle zum Setzen des Objektzustandes nach Replikation eines Cuckoo-Objektes<br />

public void setStateAfterReplication(Memento replicationMemento);<br />

81


KAPITEL ANHANG A: SCHNITTSTELLEN DER REALISIERUNG<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Schnittstelle eines Cuckoo-Objekt-Handles<br />

public interface MigrateableHandle extends Handle<br />

{<br />

// Schnittstelle zum Setzen der neuen Platzierung des migrierten Cuckoo-Objekts<br />

public void _setMigratedNodeHostID(int migratedNodeHostID);<br />

}<br />

public void _migrated();// makiert ein Cuckoo-Objekt-Handle als migriert;<br />

// über ein als migriert gekennzeichnetes Cuckoo-Handle-Objekt<br />

// werden alle Zugriffe eines mirgriertes Cuckoo-Objekts an<br />

// dessen neue Platzierung delegiert<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Schnittstelle eines Cuckoo-Objekt-InstanceManagers<br />

public interface MigrateableInstanceManager extends Remote<br />

{<br />

// Schnittstelle zum Löschen eines Cuchoo-Objekt-Replikats<br />

public void removeReplicaInstance(int _objectID, String _objectIP)<br />

throws RemoteException;<br />

}<br />

// Schnittstelle zum Sperren eines Cuchoo-Objekt-Replikats<br />

public void lockReplicaInstance(int _objectID, String _objectIP)<br />

throws RemoteException;<br />

// Schnittstelle zum Aktualisieren und Freigeben eines Cuchoo-Objekt-Replikats<br />

public void updateReplicaInstance(int _objectID, String _objectIP, byte[]<br />

serialisedState) throws RemoteException;<br />

// Schnittstelle zum Ermitteln, ob ein konkretes Cuchoo-Objekt-Replikat lokal existiert<br />

public boolean containsReplicaInstance(int _objectID, String _objectIP)<br />

throws RemoteException;<br />

Anhang A.2: Klassenanteil-repräsentierende Objekte<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Schnittstelle aller migrierbaren ClassObject-Objekte für die Transfer-Komponente<br />

// einer Migrationsstrategie<br />

public interface MigrateableClassObject extends Remote<br />

{<br />

// Schnittstelle zum Migrieren eines ClassObject-Objektes<br />

public void _migrate(int destinationNodehostID) throws RemoteException;<br />

}<br />

// Schnittstelle zum Replizieren eines ClassObject-Objektes<br />

public void _replicate(int destinationNodehostID) throws RemoteException;<br />

// Schnittstelle zum Ermitteln, ob das ClassObject-Objekt bereits migriert wurde;<br />

// nur für Migrationsstrategien mit Einfachmigration von Interesse<br />

public boolean _isMigrated() throws RemoteException;<br />

// Schnittstelle zum Sperren eines ClassObject-Replikats eines ClassObject-Objekts.<br />

public void lockReplicaInstance() throws RemoteException;<br />

// Schnittstelle zum Aktualisieren eines ClassObject-Replikats eines<br />

// ClassObject-Objekts.<br />

public void updateReplicaInstance(byte[] serialisedState) throws RemoteException;<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Oberklasse aller migrierbaren ClassObject-Objekte des JScatter-Systems<br />

public abstract class AbstractMigrateableClassObject extends ClassObject implements Remote<br />

{<br />

private int _objectID = -1; // Teil der eindeutigen ID eines migrierbaren ClassObjects<br />

82<br />

private String _objectIP; // Teil der eindeutigen ID eines migrierbaren ClassObjects<br />

private String _className; // Teil der eindeutigen ID eines migrierbaren ClassObjects


ANHANG A.2: KLASSENANTEIL-REPRÄSENTIERENDE OBJEKTE<br />

protected boolean _isMigrated = false; // gibt an, ob das Objekt bereits einmal<br />

// migriert wurde<br />

protected boolean _isMigrationActive = false; // gibt an, ob das Objekt gerade<br />

// migriert wird<br />

protected int _numberOfActivedMethodCalls = 0; // gibt die Anzahl der <strong>zur</strong> Zeit aktiven<br />

// Methodenzugriffe <strong>auf</strong> das Objekt an<br />

protected boolean _updateReferenz = false; // gibt an, dass die Objektreferenz<br />

// (nach einer Migration) vor der Methodenausführung aktualisiert werden muss<br />

private ReplicaGroup replicaGroup; // Enthält Replikatgruppe des ClassObjectes,<br />

// wenn vorhanden<br />

private boolean _isReplicated = false; // gibt an, ob das ClassObject ein Replikat ist<br />

protected boolean _isReplicaLocked = false; // gibt an, ob das Object für die<br />

// Aktualisierung seiner Replikate gesperrt ist<br />

public AbstractMigrateableClassObject(String _className) throws RemoteException<br />

{<br />

super();<br />

this._className = _className;<br />

_objectIP = RuntimeEnvironment.localNode.getIp();<br />

}<br />

// Schnittstelle zum Migrieren eines ClassObject-Objektes<br />

public final void migrate(int destinationNodehostID) throws RemoteException<br />

{<br />

_isMigrationActive = true; // Objekt als Migration <strong>zur</strong> Zeit Aktiv markieren<br />

}<br />

// warten bis alle vor der Migrationsentscheidung begonnenen Methodenzugriffe<br />

while(_numberOfActivedMethodCalls() > 1){} // abgearbeitet sind<br />

deleteReplicaByWriteAccess(); // vorhandene Replikate löschen<br />

// Objektzustand über Memento serialisieren<br />

byte[] serializedObject = RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().convertMementoToByteArray(createMemento());<br />

RuntimeEnvironment.localNode.createDeserializedClassObjectAtHost(serializedObject,<br />

destinationNodehostID, _getClassName());<br />

_isMigrationActive = false; // Migration als abgeschlossen setzen<br />

// Schnittstelle zum Replizieren eines ClassObject-Objektes<br />

public final void replicate(int destinationNodehostID) throws RemoteException<br />

{<br />

_isMigrationActive = true; // Objekt als Migration <strong>zur</strong> Zeit Aktiv markieren<br />

}<br />

while (_numberOfActivedMethodCalls() > 1) {} // warten bis alle vor der<br />

// Migrationsentscheidung begonnenen Methodenzugriffe abgearbeitet sind<br />

// Objektzustand über Memento serialisieren<br />

byte[] serializedObject = RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().convertMementoToByteArray(createMemento());<br />

// Replikat erzeugen und Stub zu Replikat-Gruppe hinzufügen<br />

createRemoteReplica(serializedObject, destinationNodehostID);<br />

_isMigrationActive = false; // Migration als abgeschlossen setzen<br />

_updateReferenz = true; // Referenzen <strong>auf</strong> das Objekt vor Methodenausführung<br />

// aktualisieren<br />

// Schnittstelle zum Abfragen, ob aktuell die Migration des ClassObject-Objektes<br />

// aktiv ist<br />

public boolean _isMigrationActive() throws RemoteException<br />

{ return _isMigrationActive; }<br />

// makiert das ClassObject als migiert<br />

public void _migrated() throws RemoteException<br />

{<br />

_setObjectIP(RuntimeEnvironment.localNode.getIp()); // _objectIP aktualisieren<br />

}<br />

_isMigrated = true; // ClassObject als bereits einmal migriert setzen<br />

83


KAPITEL ANHANG A: SCHNITTSTELLEN DER REALISIERUNG<br />

84<br />

// Schnittstelle zum Aktualisieren des Objektzustandes eines ClassObject-Replikats<br />

public boolean _updateReferenz() throws RemoteException<br />

{ return _updateReferenz; }<br />

// zum Setzen des Namens nach Migration<br />

public String _getClassName() throws RemoteException<br />

{ return _className; }<br />

// dient zum abfragen der Anzahl der aktuell <strong>auf</strong> das ClassObject ausgeführten Zugriffe<br />

public int _numberOfActivedMethodCalls() throws RemoteException<br />

{ return _numberOfActivedMethodCalls; }<br />

// Schnittstelle zum Abfragen, ob das ClassObjekt bereits migriert wurde;<br />

// diese Schnittstelle ist nur bei Einfachmigration von Interesse<br />

public boolean isMigrated() throws RemoteException<br />

{ return _isMigrated; }<br />

// zum Aktualisieren der _objectID<br />

public void _setObjectID(int _objectID) { this._objectID = _objectID; }<br />

// zum Aktualisieren der _objectIP<br />

public void _setObjectIP(String _objectIP) { this._objectIP = _objectIP; }<br />

public int _getObjectID() { return _objectID; }<br />

public String _getObjectIP() { return _objectIP; }<br />

// makiert ein ClassObject als Replikat<br />

public void _replicated() { _isReplicated = true; }<br />

// dient zum Abfragen, ob das ClassObject ein Replikat ist<br />

public boolean _isReplicated() { return _isReplicated; }<br />

// Schnittstelle zum Abfragen, ob der Objektzustand des ClassObject-Replikats aktuell<br />

// aktualisiert wird<br />

public boolean isReplicaLocked() { return _isReplicaLocked; }<br />

// sperrt ein Replikat für weitere Zugriffe<br />

public void _lockReplica() { this._isReplicaLocked = true; }<br />

// erzeugt ein ClassObject-Replikat <strong>auf</strong> einem entfernten Rechner<br />

protected final void createRemoteReplica(byte[] serializedObject,<br />

int destinationNodeHostID) throws RemoteException<br />

{<br />

if(replicaGroup == null) replicaGroup = new ReplicaGroup();<br />

}<br />

// Replikat des ClassObjects nur dann erzeugen, wenn es <strong>auf</strong> dem entfernten Rechner<br />

// noch kein Exemplar existiert<br />

if (!replicaGroup.hasClassObjectMemberAtHost(destinationNodeHostID))<br />

{<br />

MigrateableRemoteClassObject stub = RuntimeEnvironment.localNode.<br />

createDeserializedClassObjectReplicaAtHost(serializedObject,<br />

destinationNodeHostID, _getClassName());<br />

}<br />

// Replikat <strong>zur</strong> Replikatgruppe hinzufügen<br />

replicaGroup.addClassObjectMember(destinationNodeHostID, stub);<br />

// für Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Löschen"<br />

protected final void deleteReplicaByWriteAccess() throws RemoteException<br />

{<br />

// alle vorhandenen Replikate des ClassObjects löschen<br />

if (replicaGroup != null)<br />

{<br />

Iterator keys = replicaGroup.getClassObjectMemberKeys();<br />

}<br />

}<br />

while (keys.hasNext())<br />

{<br />

NodeRemoteInterface node = RuntimeEnvironment.localNode.<br />

getNodeWithId(((Integer)keys.next()).intValue());<br />

node.removeClassObjectReplica(_getClassName());<br />

}<br />

replicaGroup = null;


}<br />

ANHANG A.2: KLASSENANTEIL-REPRÄSENTIERENDE OBJEKTE<br />

// für Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Aktualisieren"<br />

protected final void lockReplicaForWriteAccess() throws RemoteException<br />

{<br />

// alle vorhandenen Replikate des ClassObjects für weitere Zugriffe sperren<br />

if (replicaGroup != null)<br />

{<br />

Iterator keys = replicaGroup.getClassObjectMemberKeys();<br />

}<br />

}<br />

while (keys.hasNext())<br />

{<br />

MigrateableRemoteClassObject classObject = replicaGroup.<br />

getClassObjectMember(keys.next());<br />

classObject.lockReplicaInstance();<br />

}<br />

// für Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Aktualisieren"<br />

protected final void updateAndReleaseReplicaForWriteAccess(Memento toSerializeMemento)<br />

throws RemoteException<br />

{<br />

// alle vorhandenen Replikate des ClassObjects aktualisieren<br />

// und für weitere Zugriffe freigeben<br />

if (replicaGroup != null)<br />

{<br />

Iterator keys = replicaGroup.getClassObjectMemberKeys();<br />

}<br />

}<br />

while (keys.hasNext())<br />

{<br />

MigrateableRemoteClassObject classObject = replicaGroup.<br />

getClassObjectMember(keys.next());<br />

}<br />

// Objektzustand serialisieren<br />

byte[] serialisedState = RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().convertMementoToByteArray(toSerializeMemento);<br />

classObject.updateReplicaInstance(serialisedState);<br />

// dient zum Aktualisieren des Objektzustands eines ClassObject-Replikats<br />

public final void updateReplica(byte[] serialisedState) throws RemoteException<br />

{<br />

// übertragenen Objektzustand als Memento deserialisieren<br />

Memento memento = RuntimeEnvironment.getMigrationStrategy().getObjectTransferer().<br />

createMementoForByteArray(serialisedState);<br />

}<br />

setState(memento); // neuen Objektzustand das Replikats über Memento setzen<br />

_isReplicaLocked = false; // ClassObject für weitere Zugriffe freigeben<br />

// Schnittstelle zum Setzen des Objektzustandes nach Replikation<br />

public abstract void setState(Memento memento) throws RemoteException;<br />

// Schnittstelle zum Serialisieren des Zustandes eines ClassObjects als Memento<br />

public abstract Memento createMemento() throws RemoteException;<br />

Anhang A.3: Replikatgruppen-Objekte<br />

package de.upb.jscatter.runtime.migration;<br />

import ...;<br />

// Repräsentiert eine Replikatgruppe im JScatter-System<br />

public class ReplicaGroup<br />

{<br />

// definiert die Datenstrukturen welche die Stubs <strong>auf</strong> die Replikate eines Cuckoo- oder<br />

// ClassObjects enthalten<br />

private HashMap instanceManagerStubs;<br />

private HashMap classObjectStubs;<br />

public ReplicaGroup() // initialisiert ReplicaGroup-Objekt<br />

{ ... }<br />

// Schnittstelle zum Hinzufügen eines InstanceManager-Stubs<br />

public void addInstanceManagerMember(int destinationNodeHostID, Object instanceManager)<br />

85


KAPITEL ANHANG A: SCHNITTSTELLEN DER REALISIERUNG<br />

}<br />

86<br />

{ ... }<br />

// dient zum Ermitteln, ob <strong>auf</strong> den angegebenen Rechner ein InstanceManager-Replikat<br />

// existiert<br />

public boolean hasInstanceManagerMemberAtHost(int destinationNodeHostID)<br />

{ ... }<br />

// Schnittstelle zum Hinzufügen eines ClassObject-Stubs<br />

public void addClassObjectMember(int destinationNodeHostID, Object classObject)<br />

{ ... }<br />

// dient zum Ermitteln, ob <strong>auf</strong> den angegebenen Rechner ein ClassObject-Replikat<br />

// existiert<br />

public boolean hasClassObjectMemberAtHost(int destinationNodeHostID)<br />

{ ... }<br />

// gibt die Schlüssel aller InstanceManager-Stubs der Replikatgruppe <strong>zur</strong>ück<br />

public Iterator getInstanceManagerMemberKeys()<br />

{ ... }<br />

// gibt den InstanceManager-Stub zum übergebenen Schlüssel <strong>zur</strong>ück<br />

public Object getInstanceManagerMember(Object key)<br />

{ ... }<br />

// gibt die Schlüssel aller ClassObject-Stubs der Replikatgruppe <strong>zur</strong>ück<br />

public Iterator getClassObjectMemberKeys()<br />

{ ... }<br />

// gibt den ClassObject-Stub zum übergebenen Schlüssel <strong>zur</strong>ück<br />

public MigrateableClassObject getClassObjectMember(Object key)<br />

{ ... }


ANHANG B: TRANSFORMATIONSBEISPIELE<br />

ANHANG B: TRANSFORMATIONSBEISPIELE<br />

Dieser Anhang gibt jeweils ein Beispiel für die Transformation eines migrierbaren<br />

Instanzenanteil- oder Klassenanteil-repräsentierenden Objektes im<br />

JScatter-System. Hierbei wird jeweils zuerst die Original-Klasse und<br />

anschließend die transformierte Klasse dargestellt.<br />

Anhang B.1: Instanzenanteil-repräsentierende Objekte<br />

### Original ###<br />

package post;<br />

public class LetterCase<br />

{<br />

private int letter;<br />

}<br />

public LetterCase()<br />

{ this.letter = 0; }<br />

public void addLetter(int i)<br />

{ this.letter += i; }<br />

public int nextLetter()<br />

{ this.letter--; return 1; }<br />

public boolean hasLetter()<br />

{ return letter > 0; }<br />

### nach Transformation ###<br />

package post;<br />

import ...;<br />

public class LetterCase implements MigrateableCuckoo<br />

{<br />

public int letter;<br />

private int _objectID = -1;<br />

private String _objectIP;<br />

private String _className;<br />

private boolean _isMigrated = false;<br />

private boolean _isMigrationActive = false;<br />

private int _numberOfActivedMethodCalls = 0;<br />

private boolean _isReplicated = false;<br />

private transient ReplicaGroup _replicaGroup;<br />

private boolean _isReplicaLock = false;<br />

public LetterCase()<br />

{ this.letter = 0; }<br />

public LetterCase(ConstructorParam dummy)<br />

{ }<br />

// Implementierung einer einen Schreibzugriff-ausführenden Methode<br />

public synchronized void addLetter(int i, String _accessObjectName, int<br />

_accessObjectID, String _accessObjectHostIP) throws RemoteException, MovedException<br />

{<br />

// den Zähler der aktiven Methodenzugiffe inkrementieren<br />

_numberOfActivedMethodCalls++;<br />

while(isReplicaLocked()) {} // warten...<br />

// 1. Replikations-Strategie überprüfen<br />

int replicationStrategieType = RuntimeEnvironment.getMigrationStrategy().<br />

getDecidionStrategy().getReplicationStrategieType();<br />

// 1.1 Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Löschen"<br />

if (replicationStrategieType == DecidionStrategy.<br />

REPLICATIONTYPE_DELETE_REPLICA_BY_WRITEACCESS)<br />

{<br />

deleteReplicaByWriteAccess(); // 1.1.1 vorhandene Replikate löschen<br />

}<br />

this.letter += i; // 1.1.2 Schreibzugriff ausführen<br />

87


KAPITEL ANHANG B: TRANSFORMATIONSBEISPIELE<br />

88<br />

}<br />

// 1.2 Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Aktualisieren"<br />

else if (replicationStrategieType == DecidionStrategy.<br />

REPLICATIONTYPE_UPDATE_REPLICA_BY_WRITEACCESS)<br />

{<br />

// wenn Cuckoo-Objekt-Replikate existieren<br />

if (_replicaGroup != null)<br />

{<br />

lockReplicaForWriteAccess(); // 1.2.1 Replikate sperren<br />

}<br />

this.letter += i; // 1.2.2 Schreibzugriff ausführen<br />

// 1.2.3 Replikate aktualisieren und freigeben<br />

updateAndReleaseReplicaForWriteAccess();<br />

}<br />

else<br />

{<br />

this.letter += i; // 2. Schreibzugriff ausführen<br />

}<br />

// 3. Ist das Objekt kein Replikat, dann zähle den Zugriff.<br />

if (!_isReplicated())<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration()<br />

.incrementWriteAccesses(_className, _objectID, _objectIP,<br />

_accessObjectName, _accessObjectID, _accessObjectHostIP);<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

public synchronized int nextLetter(String _accessObjectName, int _accessObjectID,<br />

String _accessObjectHostIP) throws RemoteException, MovedException<br />

{<br />

... // analog zu Methode addLetter(...)<br />

}<br />

// Implementierung einer einen Leszugriff-ausführenden Methode<br />

public synchronized boolean hasLetter(String _accessObjectName, int _accessObjectID,<br />

String _accessObjectHostIP) throws RemoteException, MovedException<br />

{<br />

// den Zähler der aktiven Methodenzugiffe inkrementieren<br />

_numberOfActivedMethodCalls++;<br />

}<br />

while(isReplicaLocked()) {} // warten...<br />

// Ist das Objekt kein Replikat, dann zähle den Zugriff.<br />

if (!_isReplicated())<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration().<br />

incrementReadAccesses(_className, _objectID, _objectIP, _accessObjectName,<br />

_accessObjectID, _accessObjectHostIP);<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

return letter > 0; // Lesezugriff ausführen<br />

public Handle _getHandle()<br />

{ return new _LetterCaseHandle(this); }<br />

// Implementierung der Methoden der Schnittstelle MigratableCuckoo<br />

// (unvollständig) siehe auch Anhang A<br />

...<br />

public Handle _migrate(int deserialisationNodeHostID) throws RemoteException<br />

{<br />

// Cuckoo-Objekt als Migration <strong>zur</strong> Zeit aktiv markieren<br />

_isMigrationActive = true;<br />

while (_numberOfActivedMethodCalls() > 1) {} // warten...<br />

deleteReplicaByWriteAccess(); // alle vorhandenen Replikate löschen<br />

// serialisieren des Cuckoo-Objektes<br />

byte[] serializedObject = RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().convertMementoToByteArray(createMigrationMemento());<br />

// Referenz <strong>auf</strong> den entfernten InstanceManager beschaffen<br />

_LetterCaseInstanceManagerInterface destinationNodeInstanceManager =<br />

(_LetterCaseInstanceManagerInterface)


}<br />

ANHANG B.1: INSTANZENANTEIL-REPRÄSENTIERENDE OBJEKTE<br />

RuntimeEnvironment.getInstanceManager(deserialisationNodeHostID, _className);<br />

// Stellvertreter <strong>auf</strong> migriertes Cuckoo-Object beschaffen<br />

Handle handle = destinationNodeInstanceManager.<br />

createDeserializedInstance(serializedObject, deserialisationNodeHostID);<br />

return handle;<br />

public void _replicate(int destinationNodeHostID) throws RemoteException<br />

{<br />

_isMigrationActive = true; // Cuckoo-Objekt als Migration <strong>zur</strong> Zeit aktiv markieren<br />

}<br />

while (_numberOfActivedMethodCalls() > 1) {} // warten...<br />

if (_replicaGroup == null) { _replicaGroup = new ReplicaGroup(); }<br />

// Wenn es <strong>auf</strong> dem entfernten Rechner noch kein Replikat des Objektes<br />

// existiert, dann erzeuge eins. Sonst brich die Replikation ab.<br />

if (!_replicaGroup.hasInstanceManagerMemberAtHost(destinationNodeHostID))<br />

{<br />

// serialisieren des Cuckoo-Objektes<br />

byte[] serializedObject = RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().convertMementoToByteArray(createReplicationMemento());<br />

// Referenz <strong>auf</strong> entfernten ReplicaGroup-InstanceManager beschaffen.<br />

_LetterCaseInstanceManagerInterface nodeIM =<br />

(_LetterCaseInstanceManagerInterface) RuntimeEnvironment.<br />

getReplicaInstanceManager(destinationNodeHostID, _className);<br />

nodeIM.createDeserializedReplicaInstance(serializedObject);<br />

// Hinzufügen des Replikats <strong>zur</strong> Replikat-Gruppe.<br />

_replicaGroup.addInstanceManagerMember(destinationNodeHostID, nodeIM);<br />

}<br />

_isMigrationActive = false; // Migrationsaktivierung <strong>auf</strong>heben<br />

public void _migrated()<br />

{<br />

_isMigrationActive = false; // Cuckoo-Objekt als Migration abgeschlossen markieren<br />

_isMigrated = true; // Cuckoo-Objekt als migriert markieren<br />

}<br />

// Schnittstelle <strong>zur</strong> Replikationsstrategie: “Bei-Schreibzugriff-Replikate-Löschen“<br />

public void deleteReplicaByWriteAccess() throws RemoteException<br />

{<br />

if (_replicaGroup != null)<br />

{<br />

Iterator keys = _replicaGroup.getInstanceManagerMemberKeys();<br />

}<br />

}<br />

while (keys.hasNext())<br />

{<br />

_LetterCaseInstanceManagerInterface instanceManager =<br />

(_LetterCaseInstanceManagerInterface)<br />

_replicaGroup.getInstanceManagerMember(keys.next());<br />

instanceManager.removeReplicaInstance(_objectID, _objectIP);<br />

}<br />

_replicaGroup = null;<br />

// Schnittstelle <strong>zur</strong> Replikationsstrategie: “Bei-Schreibzugriff-Replikate-Aktualisieren“<br />

// Teil 1: Replikate für Zugriffe sperren<br />

public void lockReplicaForWriteAccess() throws RemoteException<br />

{<br />

Iterator keys = _replicaGroup.getInstanceManagerMemberKeys();<br />

}<br />

while (keys.hasNext())<br />

{<br />

_LetterCaseInstanceManagerInterface instanceManager =<br />

(_LetterCaseInstanceManagerInterface)<br />

_replicaGroup.getInstanceManagerMember(keys.next());<br />

instanceManager.lockReplicaInstance(_objectID, _objectIP);<br />

}<br />

89


KAPITEL ANHANG B: TRANSFORMATIONSBEISPIELE<br />

}<br />

// Schnittstelle <strong>zur</strong> Replikationsstrategie: “Bei-Schreibzugriff-Replikate-Aktualisieren“<br />

// Teil 2: Objektzustand der Replikate aktualisieren und für weitere Zugriffe freigeben<br />

public void updateAndReleaseReplicaForWriteAccess() throws RemoteException<br />

{<br />

Memento memento = null;<br />

Iterator keys = _replicaGroup.getInstanceManagerMemberKeys();<br />

}<br />

if(keys.hasNext())<br />

memento = createMigrationMemento(); // nur die Zustandsõnderung übertragen<br />

while (keys.hasNext())<br />

{<br />

_LetterCaseInstanceManagerInterface instanceManager =<br />

(_LetterCaseInstanceManagerInterface)<br />

_replicaGroup.getInstanceManagerMember(keys.next());<br />

byte[] serialisedState = RuntimeEnvironment.getMigrationStrategy()<br />

.getObjectTransferer().convertMementoToByteArray(memento);<br />

instanceManager.updateReplicaInstance(_objectID, _objectIP, serialisedState);<br />

}<br />

// Schnittstelle zum Aktualisieren eines Cuckoo-Objekt-Replikats<br />

public void _updateReplica(byte[] serialisedState)<br />

{<br />

updateStateAfterReplication(RuntimeEnvironment.getMigrationStrategy().<br />

getObjectTransferer().createMementoForByteArray(serialisedState));<br />

this._isReplicaLock = false;<br />

}<br />

public Memento createMigrationMemento()<br />

{ return new _LetterCaseMigrationMemento(letter); }<br />

public Memento createReplicationMemento()<br />

{ return new _LetterCaseReplicationMemento(letter, _objectID, _objectIP, _className); }<br />

// Schnittstelle zum Setzen des Objektzustandes nach einer Migration<br />

public void setStateAfterMigration(Memento migrationMemento)<br />

{<br />

_LetterCaseMigrationMemento memento = (_LetterCaseMigrationMemento)migrationMemento;<br />

this.letter = memento._getLetter();<br />

}<br />

// Schnittstelle zum Aktualisieren des Objektzustandes eines Cuckoo-Objekt-Replikats<br />

// nach einem Schreibzugriff <strong>auf</strong> das Original-Cuckoo-Objekt<br />

public void updateStateAfterReplication(Memento migrationMemento)<br />

{<br />

_LetterCaseMigrationMemento memento = (_LetterCaseMigrationMemento)migrationMemento;<br />

this.letter = memento._getLetter();<br />

}<br />

// Schnittstelle zum Setzen des Objektzustandes nach einer Replikation eines<br />

// Cuckoo-Objekts<br />

public void setStateAfterReplication(Memento replicationMemento)<br />

{<br />

_LetterCaseReplicationMemento memento = (_LetterCaseReplicationMemento)replicationMemento;<br />

}<br />

this.letter = memento._getLetter();<br />

this._objectID = memento._getObjectID();<br />

this._objectIP = memento._getObjectIP();<br />

this._className = memento._getClassName();<br />

Anhang B.2: Klassenanteil-repräsentierende Objekte<br />

### Original ###<br />

package post2;<br />

public class LetterCase<br />

{<br />

private static int letter = 0;<br />

90<br />

public LetterCase()<br />

{}<br />

public static void addLetter(int i)<br />

{ letter += i; }


}<br />

ANHANG B.2: KLASSENANTEIL-REPRÄSENTIERENDE OBJEKTE<br />

public static int nextLetter()<br />

{ letter--; return 1; }<br />

public static boolean hasLetter()<br />

{ return letter > 0; }<br />

### nach Transformation ###<br />

package post2;<br />

import ...;<br />

public class _LetterCaseClassObject extends AbstractMigrateableClassObject<br />

implements _LetterCaseClassObjectInterface<br />

{<br />

private int letter;<br />

public _LetterCaseClassObject() throws RemoteException<br />

{ super("post2._LetterCaseClassObject"); this.letter = 0; }<br />

// Implementierung einer einen Schreibzugriff-ausführenden Methode<br />

public void addLetter(int i, String _accessObjectName, int _accessObjectID,<br />

String _accessObjectHostIP) throws RemoteException<br />

{<br />

while(_isMigrationActive()){} // warten...<br />

}<br />

// den Zähler der aktiven Methodenzugiffe inkrementieren<br />

_numberOfActivedMethodCalls++;<br />

if(_updateReferenz())<br />

{<br />

_updateReferenz = false;<br />

// Wenn das Objekt migriert ist, leite den Methoden<strong>auf</strong>ruf an das migrierte<br />

// Objekt weiter.<br />

_LetterCaseClassObjectInterface _classObject =<br />

(_LetterCaseClassObjectInterface)RuntimeEnvironment.<br />

getClassObject(_getClassName());<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

_classObject.addLetter(i, _accessObjectName, _accessObjectID,<br />

_accessObjectHostIP);<br />

}<br />

else<br />

{<br />

// 1. Replikations-Strategie überprüfen<br />

int replicationStrategieType = RuntimeEnvironment.getMigrationStrategy().<br />

getDecidionStrategy().getReplicationStrategieType();<br />

}<br />

// 1.1 Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Löschen"<br />

if (replicationStrategieType == DecidionStrategy.<br />

REPLICATIONTYPE_DELETE_REPLICA_BY_WRITEACCESS)<br />

{<br />

deleteReplicaByWriteAccess(); // vorhandene Replikate löschen<br />

this.letter += i; // 1.1.1 Zugriff ausführen<br />

}<br />

// 1.2 Replikationsstrategie: "Replikate-Bei-Schreibzugriff-Aktualisieren"<br />

else if (replicationStrategieType == DecidionStrategy.<br />

REPLICATIONTYPE_UPDATE_REPLICA_BY_WRITEACCESS)<br />

{<br />

lockReplicaForWriteAccess(); // 1.2.1 Replikate sperren<br />

}<br />

this.letter += i; // 1.2.2 Zugriff ausführen<br />

// 1.2.3 Replikate aktualisieren und freigeben<br />

updateAndReleaseReplicaForWriteAccess(createMemento());<br />

// Zugriffszählung durch Informationserfassungs-Komponente<br />

if(!_isReplicated())<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration().<br />

incrementWriteAccesses(_getClassName(), _getObjectID(), _getObjectIP(),<br />

_accessObjectName, _accessObjectID, _accessObjectHostIP);<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

91


KAPITEL ANHANG B: TRANSFORMATIONSBEISPIELE<br />

}<br />

92<br />

public int nextLetter(String _accessObjectName, int _accessObjectID,<br />

String _accessObjectHostIP) throws RemoteException<br />

{<br />

// analog <strong>zur</strong> Methode addLetter(...);<br />

}<br />

// Implementierung einer einen Lesezugriff-ausführenden Methode<br />

public boolean hasLetter(String _accessObjectName, int _accessObjectID,<br />

String _accessObjectHostIP) throws RemoteException<br />

{<br />

while(_isMigrationActive()){} // warten...<br />

}<br />

// den Zähler der aktiven Methodenzugiffe inkrementieren<br />

_numberOfActivedMethodCalls++;<br />

// Wenn ClassObject migriert oder repliziert wurde Referenz aktualisieren<br />

if(_updateReferenz())<br />

{<br />

_updateReferenz = false;<br />

_LetterCaseClassObjectInterface _classObject = (_LetterCaseClassObjectInterface)<br />

RuntimeEnvironment.getClassObject(_getClassName());<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

return _classObject.hasLetter(_accessObjectName, _accessObjectID,<br />

_accessObjectHostIP);<br />

}<br />

else<br />

{<br />

// Wenn ClassObject kein Replikat ist, dann zähle den Zugriff<br />

if(!_isReplicated())<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration().<br />

incrementReadAccesses(_getClassName(), _getObjectID(), _getObjectIP(),<br />

_accessObjectName, _accessObjectID, _accessObjectHostIP);<br />

}<br />

// den Zähler der aktiven Methodenzugiffe dekrementieren<br />

_numberOfActivedMethodCalls--;<br />

return letter > 0; // Lesezugriff ausführen<br />

public void _migrate(int destinationNodehostID) throws RemoteException<br />

{ migrate(destinationNodehostID); }<br />

public void _replicate(int destinationNodehostID) throws RemoteException<br />

{ replicate(destinationNodehostID); }<br />

public boolean _isMigrated() throws RemoteException<br />

{ return isMigrated(); }<br />

public void lockReplicaInstance() throws RemoteException<br />

{ _lockReplica(); }<br />

public void updateReplicaInstance(byte[] serialisedState) throws RemoteException<br />

{ updateReplica(serialisedState); }<br />

public Memento createMemento()<br />

{ return new _LetterCaseClassObjectMemento(letter); }<br />

public void setState(Memento memento) throws RemoteException<br />

{<br />

_LetterCaseClassObjectMemento classObject = (_LetterCaseClassObjectMemento)memento;<br />

}<br />

this.letter = classObject._getLetter();


ANHANG C: BASISIMPLEMENTIERUNG DER INFORMATIONS<br />

ERFASSUNG<br />

ANHANG C: BASISIMPLEMENTIERUNG DER INFORMATIONS-<br />

ERFASSUNG<br />

In diesem Anhang wird die Organisation der gesammelten Informationen in<br />

der Basisimplementierung der entwickelten Informationserfassungs-Komponente<br />

einer Migrationsstrategie vorgestellt. Für die Beschreibung dient das in<br />

Abbildung Anhang C.1 dargestellte Szenario. Zur Vereinfachung werden nur<br />

Lese- und Schreibzugriffe betrachtet. Nicht-Lese-Oder-Schreibzugriffe (siehe<br />

Abschnitt 6.1.1) können analog ergänzt werden. Die Informationserfassungs-<br />

Komponente macht keine Unterschiede zwischen Instanzenanteil- und Klassenanteil-repräsentierenden<br />

Objekten, sie werden über eine einheitliche<br />

Schnittstelle gezählt.<br />

Rechner<br />

1<br />

Netzwerk / KaRMI<br />

Legende<br />

X<br />

Rechner<br />

2<br />

O<br />

z<br />

Y<br />

B<br />

w<br />

r<br />

A 2<br />

Objekt<br />

C<br />

rw<br />

X greift <strong>auf</strong> Y zu<br />

A 1<br />

r<br />

rw<br />

z {r, w} | r ≙ Lesezugriff, w ≙ Schreibzugriff<br />

r<br />

w w<br />

D<br />

E<br />

A1<br />

B<br />

C<br />

A2<br />

D<br />

E<br />

B<br />

C<br />

E<br />

Objektzugriffe <strong>auf</strong> Rechner 1<br />

lokales Objekt<br />

r<br />

2<br />

lokales Objekt<br />

r<br />

0<br />

w<br />

3<br />

w<br />

1<br />

E<br />

entferntes Objekt<br />

r<br />

3<br />

w<br />

0<br />

entferntes Objekt<br />

lokales Objekt entferntes Objekt<br />

Objektzugriffe <strong>auf</strong> Rechner 2<br />

D<br />

E<br />

lokales Objekt<br />

r<br />

0<br />

r<br />

5<br />

lokales Objekt<br />

r<br />

0<br />

w<br />

2<br />

w<br />

0<br />

w<br />

1<br />

D<br />

r<br />

2<br />

w<br />

0<br />

entferntes Objekt<br />

B<br />

r<br />

3<br />

w<br />

0<br />

entferntes Objekt<br />

lokales Objekt entferntes Objekt<br />

Abbildung Anhang C.1: Beispielszenario der Informationserfassung<br />

Auf der Abbildung Anhang C.1 sind zwei Rechner zu sehen, <strong>auf</strong> denen jeweils<br />

drei Objekte platziert sind, die sowohl lokal als auch über die Rechnergrenzen<br />

in Interaktion stehen. Hierbei greift beispielsweise Objekt B <strong>auf</strong> Rechner 1<br />

lesend <strong>auf</strong> Objekt A2 <strong>auf</strong> Rechner 2 zu. Des Weiteren ist es für Objekt D <strong>auf</strong><br />

Rechner 2 möglich, sowohl lesend als auch schreibend <strong>auf</strong> Objekt C <strong>auf</strong><br />

Rechner 1 zuzugreifen.<br />

93


KAPITEL ANHANG C: BASISIMPLEMENTIERUNG DER INFORMATIONS<br />

ERFASSUNG<br />

Bei Zugriffen <strong>auf</strong> ein Objekt wird einerseits zwischen zugreifenden Objekten<br />

und andererseits zwischen Lese- und Schreibzugriffen unterschieden. So wurde<br />

im Beispielszenario <strong>auf</strong> Objekt A1 <strong>auf</strong> Rechner 1 von Objekt B aus zweimal<br />

lesend und dreimal schreibend, und von Objekt E aus dreimal lesend und kein<br />

mal schreibend zugegriffen. Hierbei ist anzumerken, dass es sich bei den<br />

Zugriffen von Objekt B um lokale Zugriffe handelt, die Zugriffe von Objekt E<br />

hingegen entfernte Zugriffe sind.<br />

In der Basisimplementierung der Informationserfassungs-Komponente wird für<br />

jedes Objekt, welches kein Replikat oder ein aktives Objekt repräsentiert eine<br />

Instanz eines AccessObjectManager-Objekts in eine Datenstruktur <strong>auf</strong>genommen.<br />

Somit steht ein AccessObjectManager für ein lokal platziertes<br />

Objekt. Jedes <strong>auf</strong> ein Objekt zugreifende Objekt ist durch ein sogenanntes<br />

AccessObject dargestellt. Ein AccessObjectManager verwaltet für jedes<br />

<strong>auf</strong> sein lokal platziertes Objekt zugreifendes Objekt ein AccessObject.<br />

AccessObject-Objekte stellen eine Schnittstelle bereit, über die Zugriffe<br />

untergliedert nach Lese-, Schreib- und Nicht-Lese-Oder-Schreibzugriffen<br />

registriert und abgefragt werden können.<br />

94


ANHANG D: LAUFZEITMESSUNGEN<br />

Werte der verwendeten Rechner:<br />

ANHANG D: LAUFZEITMESSUNGEN<br />

Akkordeon: Pentium 4 Prozessor mit 1,7 GHz<br />

Cello: Pentium 4 Prozessor mit 2,4 GHz<br />

Fidel: Pentium III Prozessor Coppermine mit 800 MHz<br />

Oboe: Pentium III Prozessor Coppermine mit 900 MHz<br />

Pentium III Prozessor Coppermine mit 900 MHz<br />

Panfloete: Pentium 4 Prozessor mit 1,7 GHz<br />

Piano: Pentium 4 Prozessor mit 1,7 GHz<br />

Zither: Pentium 4 Prozessor mit 2,4 GHz<br />

Während der Messungen ist <strong>auf</strong>gefallen, dass durch Schwankungen im<br />

Netzwerk die erzielten Ergebnisse teilweise stark variieren. Aus diesem Grund<br />

wurden pro Messreihe 15 Messungen durchgeführt und nur die 10 mittleren<br />

Ergebnisse bei der Auswertung berücksichtigt.<br />

Alle Messungen in den folgenden Tabellen sind in Millisekunden angegeben.<br />

Anhang D.1 Beispiel Post: Instanzenanteil<br />

Anzahl der verwendeten Rechner: 3 (Piano, Fidel, Oboe)<br />

ANHANG D.1.1: 10 FÜLLUNGEN DER POSTTASCHE<br />

Tabelle Anhang D.1: Anzahl der Briefe pro Füllung der Posttasche: 100<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 6911 6919 10133 10501<br />

2 6888 6937 9593 10554<br />

3 6875 7480 9625 10250<br />

4 7121 7404 10045 10427<br />

5 6971 7314 10451 10728<br />

6 7095 7451 10054 10347<br />

7 7125 7467 10438 10464<br />

8 7297 7488 10490 10454<br />

9 7435 7430 9944 10654<br />

10 7404 7453 10546 10778<br />

Durchschnitt: 7112, 2 7334, 3 10131, 9 10515, 7<br />

95


KAPITEL ANHANG D: LAUFZEITMESSUNGEN<br />

Tabelle Anhang D.2: Anzahl der Briefe pro Füllung der Posttasche: 1000<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 31346 28650 61161 53779<br />

2 29773 29288 60374 53480<br />

3 30705 29213 61333 53182<br />

4 29104 28965 61782 53667<br />

5 28587 28131 62290 53374<br />

6 33665 29143 62020 53242<br />

7 29439 28078 61466 52832<br />

8 29821 29273 62273 52747<br />

9 29297 29146 61749 53770<br />

10 29532 28558 62338 53012<br />

Durchschnitt: 30126, 9 28844, 5 61678, 6 53308, 5<br />

Tabelle Anhang D.3: Anzahl der Briefe pro Füllung der Posttasche: 5000<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 129792 120710 252573 233722<br />

2 129200 119877 250233 235557<br />

3 127744 120625 251674 232492<br />

4 125516 121197 251167 236557<br />

5 127814 118716 251435 236346<br />

6 127814 121140 251564 237640<br />

7 131939 120216 250917 234588<br />

8 127799 121061 251607 232849<br />

9 129208 121359 250657 233657<br />

10 128925 120574 251097 231457<br />

Durchschnitt: 128575, 1 120547, 5 251292, 4 234486, 5<br />

96


ANHANG D.1.2: 100 FÜLLUNGEN DER POSTTASCHE<br />

ANHANG D.1 BEISPIEL POST: INSTANZENANTEIL<br />

Tabelle Anhang D.4: Anzahl der Briefe pro Füllung der Posttasche: 100<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 30266 32573 60064 54676<br />

2 30093 32100 59074 55644<br />

3 30525 32682 59677 55205<br />

4 30445 33429 59254 55024<br />

5 30436 33244 60629 55778<br />

6 30748 33415 58839 56672<br />

7 30435 33521 60190 55598<br />

8 30616 33501 60058 55330<br />

9 29864 33527 60121 55934<br />

10 30252 32253 60014 56026<br />

Durchschnitt: 30368, 0 33024, 5 59792, 0 55588, 7<br />

Tabelle Anhang D.5: Anzahl der Briefe pro Füllung der Posttasche: 500<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 134227 128938 251553 241074<br />

2 130191 124689 248857 240531<br />

3 126706 127574 250510 236180<br />

4 131597 128955 249763 237994<br />

5 130282 127941 248336 244756<br />

6 129249 128702 254997 238100<br />

7 132639 126800 251302 243646<br />

8 133261 125436 254631 237555<br />

9 131925 125929 247728 240575<br />

10 128978 124323 250764 246464<br />

Durchschnitt: 130905, 5 126928, 7 250844, 1 240687, 5<br />

97


KAPITEL ANHANG D: LAUFZEITMESSUNGEN<br />

Anhang D.2: Beispiel Post: Klassenanteil<br />

Anzahl der verwendeten Rechner: 3 (Piano, Fidel, Oboe)<br />

ANHANG D.2.1: 10 FÜLLUNGEN DER POSTTASCHE<br />

Tabelle Anhang D.6: Anzahl der Briefe pro Füllung der Posttasche: 100<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 1955 1867 12644 6700<br />

2 1930 2142 11950 6533<br />

3 1936 1859 12244 6688<br />

4 1997 2226 11459 6814<br />

5 1927 1893 13279 6730<br />

6 1973 2367 13132 6799<br />

7 2108 2110 13860 6688<br />

8 2014 1827 13573 6915<br />

9 2046 2003 13449 6595<br />

10 1958 1898 13333 6865<br />

Durchschnitt: 1984, 4 2019, 2 12892, 3 6732, 7<br />

Tabelle Anhang D.7: Anzahl der Briefe pro Füllung der Posttasche: 1000<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 7936 2753 127315 40608<br />

2 8101 2788 114212 39938<br />

3 7768 2728 115177 40012<br />

4 7476 2737 132139 39925<br />

5 8131 2776 125592 39513<br />

6 8047 2791 136970 41172<br />

7 8558 2737 121129 39943<br />

8 8101 2719 105855 40507<br />

9 8168 2775 102799 40487<br />

10 8177 2706 107733 39475<br />

Durchschnitt: 8046, 3 2751, 0 118892, 1 40158, 0<br />

98


ANHANG D.2: BEISPIEL POST: KLASSENANTEIL<br />

Tabelle Anhang D.8: Anzahl der Briefe pro Füllung der Posttasche: 5000<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 33049 5481 511122 185200<br />

2 33148 5478 429724 179927<br />

3 33662 5116 406705 182307<br />

4 33166 5421 403083 184318<br />

5 32647 5169 410965 180361<br />

6 33034 6422 405885 182739<br />

7 32771 5474 429900 181379<br />

8 32987 5101 422148 184366<br />

9 32394 5965 548923 179659<br />

10 32526 5127 413911 184345<br />

Durchschnitt: 32938, 4 5475, 4 438236, 6 182460, 1<br />

ANHANG D.2.2 100 FÜLLUNGEN DER POSTTASCHE<br />

Tabelle Anhang D.9: Anzahl der Briefe pro Füllung der Posttasche: 100<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 8594 11961 104390 42067<br />

2 8565 12231 100862 42433<br />

3 8321 11242 101093 41806<br />

4 8596 12917 100819 42608<br />

5 8650 11911 106981 44783<br />

6 8571 12452 90135 43485<br />

7 8362 12771 104751 43685<br />

8 8843 12344 94284 43307<br />

9 8573 11946 106498 42310<br />

10 8658 11443 102478 42205<br />

Durchschnitt: 8573, 3 12121, 8 101229, 1 42868, 9<br />

99


KAPITEL ANHANG D: LAUFZEITMESSUNGEN<br />

Tabelle Anhang D.10: Anzahl der Briefe pro Füllung der Posttasche: 500<br />

Messung ohne Migration<br />

und Replikation<br />

mit Migration mit Replikation<br />

Delete<br />

mit Replikation<br />

Update<br />

1 33690 13523 602040 192122<br />

2 33393 13422 574745 194372<br />

3 34409 13915 595878 190917<br />

4 33628 13271 589697 193471<br />

5 35287 13749 559675 199885<br />

6 32843 13953 643045 195233<br />

7 33653 13658 655737 199768<br />

8 33661 14186 608692 199977<br />

9 34662 13580 630951 204883<br />

10 33594 13892 625103 202668<br />

Durchschnitt: 33882, 0 13714, 9 608556, 3 197329, 6<br />

Anhang D.3: Beispiel Bibliothek<br />

Tabelle Anhang D.11: Zwei Rechner (Akkordeon, Cello)<br />

Messung ohne Strategie mit Strategie<br />

1 59644 28700<br />

2 58278 27225<br />

3 60443 27874<br />

4 63682 30948<br />

5 58490 27736<br />

6 59724 27501<br />

7 59192 27040<br />

8 60823 30069<br />

9 59845 30268<br />

10 58972 27039<br />

Durchschnitt: 59909, 3 28440, 0<br />

100


ANHANG D.3: BEISPIEL BIBLIOTHEK<br />

Tabelle Anhang D.12: Drei Rechner (Akkordeon, Cello, Zither)<br />

Messung ohne Strategie mit Strategie<br />

1 10715 9575<br />

2 10576 9502<br />

3 11579 8930<br />

4 9460 8228<br />

5 10790 8664<br />

6 9401 9230<br />

7 11766 9087<br />

8 11213 9100<br />

9 10214 9652<br />

10 12157 9374<br />

Durchschnitt: 10787, 1 9134, 2<br />

Tabelle Anhang D.13: Vier Rechner (Akkordeon, Cello, Zither, Panfloete)<br />

Messung ohne Strategie mit Strategie<br />

1 9923 8917<br />

2 9643 8339<br />

3 9623 7617<br />

4 9386 8063<br />

5 9804 7363<br />

6 9471 7725<br />

7 9797 8691<br />

8 9445 8907<br />

9 9888 7369<br />

10 9813 8757<br />

Durchschnitt: 9679, 3 8174, 8<br />

101


KAPITEL ANHANG D: LAUFZEITMESSUNGEN<br />

102


ANHANG E: BEISPIELIMPLEMENTIERUNGEN FÜR<br />

ENTSCHEIDUNGSSTRATEGIEN<br />

ANHANG E: BEISPIELIMPLEMENTIERUNGEN FÜR<br />

ENTSCHEIDUNGSSTRATEGIEN<br />

/**<br />

* Entscheidungsstrategie für Migrationsstrategie des Post-Beispiels<br />

* für Migration Instanzenanteil- oder Klassenanteil-repräsentierender Objekte.<br />

*<br />

* Strategiebeschreibung:<br />

* Überprüfe nach 20 Zugriffen <strong>auf</strong> das Objekt, ob die Anzahl der entfernten<br />

* Zugriffe vom Rechner des aktuell zugreifenden Objekt aus größer ist, als<br />

* die Anzahl aller lokalen Zugriffe <strong>auf</strong> das zugegriffene Objekt. Ist dies<br />

* der Fall, dann migriere das zugegriffene Objekt <strong>auf</strong> den Rechner des aktuell<br />

* zugreifenden Objektes. Sonst brich die Migration ab.<br />

* Führe nach 50 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen aus, und<br />

* beginne mit der Überprüfung von vorn.<br />

* Eine Migration soll jedoch nur stattfinden, wenn aktuell keine Migration im<br />

* System durchgeführt wird.<br />

* Mehrfachmigration des zugegriffenen Objektes ist erlaubt.<br />

*/<br />

public class PostMigrationDecidionStrategy extends DefaultDecidionStrategy<br />

{<br />

// legt Startschwellwert 20 für Migrationsüberprüfung fest<br />

public int getStartMigrationDecitionThreshold()<br />

{ return 20; }<br />

// legt Schwellwert 50 für Reset <strong>auf</strong> die gesammelten Informationen fest<br />

public int getResetThreshold()<br />

{ return 50; }<br />

// erlaubt Mehrfachmigration<br />

public boolean doSingleMigration()<br />

{ return false; }<br />

public void checkMigration(AccessObjectManager accessObjectManager,<br />

String className, int objectID, String objectIP, String accessObjectName,<br />

int accessObjectID, String remoteHostIP)<br />

{<br />

int totalNumberOfAllAccesses = accessObjectManager.getTotalNumberOfAllAccesses();<br />

// Führe nach 50 Zugriffen <strong>auf</strong> die gesammelten Informationen des Objekts ein Reset aus.<br />

if(totalNumberOfAllAccesses >= getResetThreshold())<br />

accessObjectManager.clearAllAccesses();<br />

// Überprüfe, ob die Anzahl aller Zugriffe <strong>auf</strong> das Objekt mindestens 20 sind.<br />

if(totalNumberOfAllAccesses >= getStartMigrationDecitionThreshold())<br />

{<br />

// Führe Migration nur durch, wenn derzeit keine Migration im System aktiv ist.<br />

if(!RuntimeEnvironment.localNode.migrationAtSystemActive())<br />

{<br />

int totalNumberOfRemoteAccesses = accessObjectManager.<br />

getTotalNumberOfRemoteAccessesFromNode(remoteHostIP);<br />

int totalNumberOfLocalAccesses = accessObjectManager.<br />

getTotalNumberOfLocalAccesses();<br />

// Ist die Anzahl der entfernten Zugriffe größer als die Anzahl der<br />

// lokalen Zugriffe, dann migriere.<br />

if(totalNumberOfRemoteAccesses > totalNumberOfLocalAccesses)<br />

{<br />

// Ermittle die ID des Zielrechners über dessen IP-Adresse.<br />

int destinationNodeHostID = RuntimeEnvironment.<br />

localNode.getNodeHostIDForHostIP(remoteHostIP);<br />

}<br />

// Überprüfe, ob die ID des Zielrechners nicht die ID des lokalen<br />

// Rechners ist.<br />

if(!RuntimeEnvironment.localNode.getIp().trim().<br />

equals(remoteHostIP.trim()) && destinationNodeHostID != -1)<br />

{<br />

// Migriere zugegriffenes Objekt zum/<strong>auf</strong> den Rechner der/des<br />

// zugreifenden Objektes.<br />

RuntimeEnvironment.getMigrationStrategy().getObjectTransferer().<br />

migrateObject(className, objectID, objectIP, destinationNodeHostID);<br />

}<br />

// Nachdem die Migration durchgeführt wurde, werden alle gesammelten<br />

// Informationen des migrierten Objektes aus der Registrierung gelöscht.<br />

103


KAPITEL ANHANG E: BEISPIELIMPLEMENTIERUNGEN FÜR<br />

ENTSCHEIDUNGSSTRATEGIEN<br />

}<br />

}<br />

}<br />

}<br />

// Hierdurch wird nicht mehr benötigter Speicher freigegeben.<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration().<br />

removeObjectFromRegistration(className, objectID);<br />

// Migrationsfreigabe: Migration im System als beendet erklären. Dies ist<br />

// notwendig, damit neue Migration im System durchgeführt werden kann.<br />

RuntimeEnvironment.localNode.releaseMigrationAtSystem();<br />

/**<br />

* Entscheidungsstrategie für Replikationsstrategie DELETE des Post-Beispiels<br />

* für Replikation Instanzenanteil- oder Klassenanteil-repräsentierender Objekte.<br />

*<br />

* Strategiebeschreibung:<br />

* Überprüfe nach 20 Zugriffen <strong>auf</strong> das Objekt, ob die Anzahl der entfernten<br />

* Zugriffe vom Rechner des aktuell zugreifenden Objekt aus größer ist, als<br />

* die Anzahl aller lokalen Zugriffe <strong>auf</strong> das zugegriffene Objekt. Ist dies<br />

* der Fall, dann repliziere das zugegriffene Objekt <strong>auf</strong> den Rechner des aktuell<br />

* zugreifenden Objektes. Sonst brich die Replikation ab.<br />

* Führe nach 50 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen aus, und<br />

* beginne mit der Überprüfung von vorn.<br />

* Eine Replikation soll jedoch nur stattfinden, wenn aktuell keine Replikation im<br />

* System durchgeführt wird.<br />

* Wird eine Replikation durchgeführt, dann lösche bei einem Schreibzugriff alle<br />

* Replikate der zugehörigen Replikatgruppe.<br />

*/<br />

public class PostDeleteReplicationDecidionStrategy extends PostMigrationDecidionStrategy<br />

{<br />

// definiert den zu verwendenden Replikationstyp der Strategie<br />

public int getReplicationStrategieType()<br />

{ return REPLICATIONTYPE_DELETE_REPLICAS_BY_WRITEACCESS; }<br />

}<br />

public void checkMigration(AccessObjectManager accessObjectManager,<br />

String className, int objectID, String objectIP, String accessObjectName,<br />

int accessObjectID, String remoteHostIP)<br />

{<br />

... // anlalog zu PostMigrationDecidionStrategy<br />

// Überprüfe, ob die ID des Zielrechners nicht die ID des lokalen<br />

// Rechners ist.<br />

if(!RuntimeEnvironment.localNode.getIp().trim().<br />

equals(remoteHostIP.trim()) && destinationNodeHostID != -1)<br />

{<br />

// Repliziere zugegriffenes Objekt zum/<strong>auf</strong> den Rechner des zugreifenden<br />

// Objektes.<br />

RuntimeEnvironment.getMigrationStrategy().getObjectTransferer().<br />

replicateObject(className, objectID, objectIP, destinationNodeHostID);<br />

}<br />

}<br />

... // anlalog zu PostMigrationDecidionStrategy<br />

/**<br />

* Entscheidungsstrategie für Replikationsstrategie UPDATE des Post-Beispiels<br />

* für Replikation Instanzenanteil- oder Klassenanteil-repräsentierender Objekte.<br />

*<br />

* Strategiebeschreibung:<br />

* Überprüfe nach 20 Zugriffen <strong>auf</strong> das Objekt, ob die Anzahl der entfernten<br />

* Zugriffe vom Rechner des aktuell zugreifenden Objekt aus größer ist, als<br />

* die Anzahl aller lokalen Zugriffe <strong>auf</strong> das zugegriffene Objekt. Ist dies<br />

* der Fall, dann repliziere das zugegriffene Objekt <strong>auf</strong> den Rechner des aktuell<br />

* zugreifenden Objektes. Sonst brich die Replikation ab.<br />

* Führe nach 50 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen aus, und<br />

* beginne mit der Überprüfung von vorn.<br />

* Eine Replikation soll jedoch nur stattfinden, wenn aktuell keine Replikation im<br />

* System durchgeführt wird.<br />

* Wird eine Replikation durchgeführt, dann führe bei einem Schreibzugriff eine<br />

* Aktualisierung der Replikate der zugehörigen Replikatgruppe durch.<br />

*/<br />

public class PostUpdateReplicationDecidionStrategy extends<br />

PostDeleteReplicationDecidionStrategy<br />

{ // definiert den zu verwendenden Replikationstyp der Strategie<br />

public int getReplicationStrategieType()<br />

{ return REPLICATIONTYPE_UPDATE_REPLICA_BY_WRITEACCESS; }<br />

}<br />

104


ANHANG E: BEISPIELIMPLEMENTIERUNGEN FÜR<br />

ENTSCHEIDUNGSSTRATEGIEN<br />

/**<br />

* Entscheidungsstrategie für Migrationsstrategie des Bibliotheken-Beispiels<br />

* für Migration oder Replikation Instanzenanteil- oder Klassenanteil-repräsentierender<br />

* Objekte.<br />

*<br />

* Strategiebeschreibung:<br />

* Wenn 70% aller entfernten Zugriffe vom Rechner des zugreifenden Objektes größer oder<br />

* gleich der Summe aus Lese- und Nicht-Lese-Oder-Schreibzugriffen entsprechen, dann<br />

* Repliziere das zugegriffene Objekt zum Rechner des zugreifenden Objektes. Ist dies<br />

* nicht der Fall, dann überprüfe, ob die Gesamt- Anzahl der entfernten Zugriffe vom<br />

* Rechner des zugreifenden Objektes aus größer ist, als die Anzahl aller lokalen<br />

* Zugriffe des zugegriffenen Objektes. Ist dies der Fall, dann migriere das zugegriffene<br />

* Objekt zum Rechner des zugreifenden Objektes. Sonst brich die Migration ab.<br />

* Führe nach 100 Zugriffen ein Reset <strong>auf</strong> die gesammelten Informationen aus, und<br />

* beginne mit der Überprüfung von vorn.<br />

* Eine Migration oder Replikation soll jedoch nur stattfinden, wenn aktuell keine<br />

* Migration oder Replikation im System durchgeführt wird.<br />

* Mehrfachmigration des zugegriffenen Objektes ist erlaubt.<br />

* Wird eine Replikation durchgeführt, dann führe bei einem Schreibzugriff eine<br />

* Aktualisierung der Replikate der zugehörigen Replikatgruppe durch.<br />

*/<br />

public class LibraryMigrationDecidionStrategy extends DefaultDecidionStrategy<br />

{<br />

// legt Startschwellwert 20 für Migrationsüberprüfung fest<br />

public int getStartMigrationDecitionThreshold()<br />

{ return 20; }<br />

// legt Schwellwert 100 für Reset <strong>auf</strong> die gesammelten Informationen fest<br />

public int getResetThreshold()<br />

{ return 100; }<br />

// erlaubt Mehrfachmigration<br />

public boolean doSingleMigration()<br />

{ return false; }<br />

// legt Replikationstyp bei Replikation fest<br />

public int getReplicationStrategieType()<br />

{ return REPLICATIONTYPE_UPDATE_REPLICAS_BY_WRITEACCESS; }<br />

// legt Replikationsschwellwert 70% fest<br />

// Dass heißt, 70% aller Zugriffe vom Rechner des zugreifenden Objektes<br />

// aus müssen Lesezugriffe oder Nicht-Lese-Oder-Schreibzugriffe sein,<br />

// damit eine Replikation durchgeführt wird.<br />

public int getReplicationThreshold()<br />

{ return 70; }<br />

public void checkMigration(AccessObjectManager accessObjectManager,<br />

String className, int objectID, String objectIP, String accessObjectName,<br />

int accessObjectID, String remoteHostIP)<br />

{<br />

// Anzahl aller Zugriffe <strong>auf</strong> das zugegriffene Objekt.<br />

int totalNumberOfAllAccesses = accessObjectManager.getTotalNumberOfAllAccesses();<br />

// Anzahl aller Zugriffe vom Rechner des zugreifenden Objektes aus.<br />

int totalNumberOfRemoteAccessesFromAccessObjectNode = accessObjectManager.<br />

getTotalNumberOfRemoteAccessesFromNode(remoteHostIP);<br />

// Anzahl aller Lesezugriffe vom Rechner des zugreifenden Objektes aus.<br />

int totalNumberOfRemoteReadAccessesFromAccessObjectNode = accessObjectManager.<br />

getTotalNumberOfRemoteReadAccessesFromNode(remoteHostIP);<br />

// Anzahl aller Nicht-Lese-Oder-Schreibzugriffe vom Rechner des<br />

// zugreifenden Objektes aus.<br />

int totalNumberOfRemoteNonReadOrWriteAccessesFromAccessObjectNode =<br />

accessObjectManager.getTotalNumberOfRemoteNonReadOrWriteAccessesFromNode(remoteHostIP);<br />

// Führe nach 100 Zugriffen <strong>auf</strong> die gesammelten Informationen des Objekts ein<br />

// Reset aus.<br />

if(totalNumberOfAllAccesses >= getResetThreshold())<br />

accessObjectManager.clearAllAccesses();<br />

// Überprüfe, ob die Anzahl aller Zugriffe <strong>auf</strong> das Objekt mindestens 20 sind.<br />

if(totalNumberOfAllAccesses >= getStartMigrationDecitionThreshold())<br />

{<br />

// Prüfe Replikation<br />

if(((totalNumberOfRemoteAccessesFromAccessObjectNode/100)<br />

*getReplicationThreshold())


KAPITEL ANHANG E: BEISPIELIMPLEMENTIERUNGEN FÜR<br />

ENTSCHEIDUNGSSTRATEGIEN<br />

}<br />

}<br />

106<br />

}<br />

{<br />

totalNumberOfRemoteNonReadOrWriteAccessesFromAccessObjectNode))<br />

// Führe Replikation nur durch, wenn derzeit keine Migration oder<br />

// Replikation im System aktiv ist.<br />

if(!RuntimeEnvironment.localNode.migrationAtSystemActive())<br />

{<br />

// Ermittle die ID des Zielrechners über dessen IP-Adresse.<br />

int destinationNodeHostID = RuntimeEnvironment.<br />

localNode.getNodeHostIDForHostIP(remoteHostIP);<br />

}<br />

// Überprüfe, ob die ID des Zielrechners nicht die ID des lokalen<br />

// Rechners ist.<br />

if(!RuntimeEnvironment.localNode.getIp().trim().<br />

equals(remoteHostIP.trim()) && destinationNodeHostID != -1)<br />

{<br />

// Repliziere zugegriffenes Objekt <strong>auf</strong> den Rechner der/des<br />

// zugreifenden Objekte/Objektes.<br />

RuntimeEnvironment.getMigrationStrategy().getObjectTransferer().<br />

replicateObject(className, objectID, objectIP, destinationNodeHostID);<br />

}<br />

// Migrationsfreigabe: Replikation im System als beendet erklären.<br />

// Dies ist notwendig, damit eine neue Migration oder Replikation<br />

// im System durchgeführt werden kann.<br />

RuntimeEnvironment.localNode.releaseMigrationAtSystem();<br />

}<br />

else // sonst prüfe Migration<br />

{<br />

// Führe Migration nur durch, wenn derzeit keine Migration im System aktiv ist.<br />

if(!RuntimeEnvironment.localNode.migrationAtSystemActive())<br />

{<br />

// Anzahl aller lokale Zugriffe des zugegriffenen Objektes.<br />

int totalNumberOfLocalAccesses = accessObjectManager.<br />

getTotalNumberOfLocalAccesses();<br />

}<br />

}<br />

// Ist die Anzahl der entfernten Zugriffe vom Rechner des Zugreifers<br />

// größer als die Anzahl der lokalen Zugriffe, dann migriere.<br />

if(totalNumberOfRemoteAccessesFromAccessObjectNode ><br />

totalNumberOfLocalAccesses)<br />

{<br />

// Ermittle die ID des Zielrechners über dessen IP-Adresse.<br />

int destinationNodeHostID = RuntimeEnvironment.<br />

localNode.getNodeHostIDForHostIP(remoteHostIP);<br />

// Überprüfe, ob die ID des Zielrechners nicht die ID des lokalen<br />

// Rechners ist.<br />

if(!RuntimeEnvironment.localNode.getIp().trim().<br />

equals(remoteHostIP.trim()) && destinationNodeHostID != -1)<br />

{<br />

// Migriere zugegriffenes Objekt zum <strong>auf</strong> den Rechner der/des<br />

// zugreifenden Objekte/Objektes.<br />

RuntimeEnvironment.getMigrationStrategy().getObjectTransferer().<br />

migrateObject(className, objectID, objectIP, destinationNodeHostID);<br />

}<br />

}<br />

// Nachdem die Migration durchgeführt wurde, werden alle gesammelten<br />

// Informationen des migrierten Objektes aus der Registrierung<br />

// gelöscht. Hierdurch wird nicht mehr benötigter Speicher freigegeben.<br />

RuntimeEnvironment.getMigrationStrategy().getInformationRegistration().<br />

removeObjectFromRegistration(className, objectID);<br />

// Migrationsfreigabe: Migration im System als beendet erklären.<br />

// Dies ist notwendig, damit neue Migration im System durchgeführt<br />

// werden kann.<br />

RuntimeEnvironment.localNode.releaseMigrationAtSystem();

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!