Projekte
Navigation:
- Teil A
- Teil B
- Teil B mit Durability (= Teil C)
- Teil D (RM RMI-fähig machen)
- Teil E (2PC ohne Fehlerbehandlung)
- Teil F (2PC mit Restart-Protokollen)
Überblick:
Veranstaltungsbegleitende Projektaufgabe ist die Implementierung eines
einfachen
verteilten Buchungssystems für Reisebüros. Die
Implementierung
erfolgt in Java.
Das Projekt ist in mehrere Teile gegliedert,
wobei zunächst ein Lock Manager (LM) und ein darauf basierender
einfacher Resourcemanager (RM) implementiert werden
sollen. Der RM unterstützt konkurrierende
Transaktionen und garantiert dabei die ACID Kriterien.
Folgend soll ein Workflow-Controller und ein
Transaktionsmanager implementiert werden,
die im Zusammenspiel verteilte Transaktionen über mehrere
Resourcemanager koordinieren.
Teil A: Implementierung eines Lock Managers
(Abgabe: 06.05.2010)
Downloads:
-->
Eclipse-Projekt
-->
Test-Klassen (als *.jar)
ACHTUNG UPDATE vom 29/04/2010
16:42 Uhr (Überprüfung der Transaktionsabschlusszeiten
verbessert)
Schreiben Sie einen Lock Manager in Java, der folgendes Interface
implementiert:
public interface ILockManager {
public boolean lock(int xid, String strData, LockType lockType)
throws DeadlockException;
public boolean unlockAll(int xid);
}
Ein
LockManager speichert Informationen darüber, welche Transaktion
(hier durch eine numerische TransaktionsID repräsentiert) Sperren
auf welche Objekte hält, bzw. auf solche wartet. Objekte
könnten Datensätze oder ganze Seiten sein, in unserem o.g.
Interface werden sie einfach durch einen eindeutigen String
referenziert.
Es können Lese oder Schreibsperren angefordert werden, wobei
jeweils nur eine Transaktion eine Schreibsperre auf ein Objekt halten
darf, während mehrere Transaktion gleichzeitig Lesesperren auf ein
Objekt haben können. Die Sperren einer Transaktion werden alle
gleichzeitig freigegeben, d.h. dieses Interface würde sich z.B.
für striktes 2PL eignen.
Über die Speicherung von Sperren hinaus ist es die Aufgabe eines
LockManagers,
Verklemmungen zu erkennen. Eine einfache Lösung dafür
wären Timeouts, die aber
problematisch sind. Hier soll daher möglichst eine
Deadlock-Erkennung durch Wartegraphen realisiert werden. In einem
Wartegraphen sind die Transaktionen die Knoten und gerichtete Kanten
zeigen von Transaktion, die eine Sperre anfordern auf solche, die
gerade eine unverträgliche Sperre halten. Wenn in diesem Graphen
ein Zyklus auftritt, handelt es sich um eine Verklemmung und eine der
Transaktionen im Zyklus muss abgebrochen werden. Zu beachten: es sollte
möglich sein, dass Transaktionen ihre Sperren „upgraden“, d.h. aus
einer Lesesperre wird eine Schreibsperre.
Variante A: Implementieren Sie
den Lock Manager mit einem Wartegraphen als Deadlock Erkennung (100%
der Punkte für dieses Teilprojekt)
Variante B: Implementieren Sie
den Lock Manager mit Timeouts als Deadlock Erkennung 75% der Punkte
für dieses Teilprojekt)
Abzugeben ist ein .jar-Archiv mit Ihrer Implementierung (ggf.
Hilfklassen).
Teil B: Implementierung eines Resource Managers
(ohne "Durability")
--> Abgabe 20.05.2010
Downloads:
-->
Eclipse-Projekt
-->
Update der Testcases (wegen
Fehler in test4)
-->
Update für Aufgabe bis
27.05.2010
-->
Update der
ResourceManagerConsole
Implementieren Sie einen einfachen Resource Manager (RM) der
konkurrierende Transaktionen mit ACI (Atomicity, Consistency,
Isolation) Eigenschaften ausführen
kann. Zunächst wird auf die Eigenschaft Durability, sprich
Dauerhaftigkeit verzichtet. Es reicht aus, wenn alle Daten im Speicher
liegen. Der RM speichert Daten über beliebige Ressourcen wie z.B.
Flüge, Mietwagen,
Hotelzimmer. Zusätzlich erlaubt er es Ressourcen für Kunden
zu reservieren.
Clients greifen parallel auf einen Resource Manager zu und können
über dessen Interface Anfragen an die "Datenbank" absetzen und
Daten
ändern. Der RM ist für die korrekte Ausführung der
Transaktionen und die Einhaltung der ACI Eigenschaften verantwortlich.
Der RM verwaltet dabei die folgenden Tabellen:
Vereinfachend soll angenommen werden:
- Eine Ressource wird über einen eindeutigen Namen
identifiziert.
- Eine Ressource hat als einzige Attribute einen Preis, eine totale
Kapazität (z.B. Anzahl Hotelbetten) und die Anzahl von
Reservierungen (z.B. reservierte Hotelbetten).
- Ein Kunde kann eine Ressource nur genau einmal reservieren.
- Der Preis einer Ressource bezieht sich immer auf die gesamte
Ressource. Wird also z.B. die Kapazität einer Ressource
erhöht und gleichzeitig der Preis geändert, so bezieht sich
der Preis nicht nur auf die neu hinzugekommenen Einheiten, sondern auf
alle Einheiten der Ressource.
Für den RM stellen wir ein Interface
IResourceManager
bereit, welches Methoden zum
Manipulieren der Ressourcen und Reservierungen definiert. Dies sind im
wesentlichen das
Ändern und Hinzufügen von Ressourcen sowie das Reservieren
und Auslesen von Ressourcen.
Aufgabe ist es, dieses Interface zu implementieren. Die API
Dokumentation für den RM liegt
hier.
Clients können Transaktionen über den RM mit Hilfe der
Methoden start(), commit() und abort() ausführen. Die start()
Methode gibt eine eindeutige TransaktionsID zurück, die bei allen
Operationen innerhalb der Transaktion übergeben werden muss. Eine
TransaktionsID darf niemals mehrfach vergeben werden. Eine
Transaktion könnte z.B. wie folgt aussehen:
int xid = rm.begin();
// Wenn die Ressource billiger als 300 ist
// und Einheiten verfügbar, dann buchen
if (rm.queryResourcePrice(xid, "347") < 300 && rm.queryResourceAvailability(xid, "347") > 0) {
rm.reserveResource(xid,"347","John");
}
rm.commit(xid);
Isolation soll durch die Verwendung des Two-Phase
Locking Protokolls (2PL) erreicht werden. Das 2PL-Protokoll muss
ebenfalls vom RM
implementiert werden. Wenn ein Klient eine Operation ausführen
will, so ist der RM dafür verantwortlich entsprechende Sperren zu
setzten und später wieder aufzuheben. Nutzen Sie dazu Ihren Lock
Manager aus Teil A des Projekts.
Implementierungshinweise:
Im folgenden werden einige Hinweise
gegeben, die in etwa aufzeigen sollen worauf bei der
Implementierung zu achten ist.
Atomarität:
Eine Schwierigkeit ist es die Atomarität einer Transaktion zu
gewährleisten, also dass entweder alle Datenänderungen einer
Transaktion oder keine einzige in der Datenbasis Anwendung findet.
Hierzu wird ein LogManager
bereit gestellt. Dieser bietet als wesentliche Funktionalität alle
Datenänderungen einer Transaktion aufzuzeichnen. Eine Transaktion
schreibt also während sie aktiv ist nicht direkt in die
Datenbasis, sondern loggt zunächst alle Änderungen. Bei einem
Commit werden die Aufzeichnungen dann genutzt, um diese atomar in die
Datenbasis einzubringen. Hierzu kann die Methode updateDatabase
der Klasse ResourceManagerImpl
genutzt werden. Bei einem Transaktionsabbruch können die
Aufzeichnungen einfach gelöscht werden und nichts wird in die
Datenbasis eingetragen.
Eine Transaktion liest immer die letzten committeten Daten der
Datenbasis oder Ihre eigenen Änderungen. Um die eigenen
Änderungen zu lesen ist es notwendig die Logeinträge zu
durchsuchen. Dazu werden in der Klasse ResourceManageImpl
schon die beiden Methoden
getLatestResourceData
und getLatestReservations
bereitgestellt. Bitte eine Lesezugriff auf die Datenbasis immer
über einen Aufruf an diese Methoden kapseln.
Isolation:
Sie sollen ein striktes 2PL-Protokoll implementieren, sprich alle
gehaltenen Sperren (Lese- und Schreibsperren) dürfen erst am Ende
einer Transaktion aufgegeben werden. Wir erreichen dadurch rigorose
Historien, was später von Vorteil ist, wenn verteilte
Transaktionen implementiert werden sollen. Ihre Aufgabe ist es an den
richtigen Stellen Sperren anzufordern und wieder aufzugeben. Gehen Sie
möglichst sparsam mit Sperren um, setzen Sie z.B. keine
Schreibsperre, wenn eigentlich nur eine Lesesperre benötigt wird.
Sperren Sie auch möglichst feingranular. Sperren Sie also z.B.
nicht die komplette Reservierungstabelle, wenn eine Reservierung
vorgenommen wird. Nutzen Sie Ihren eigenen Lock Manager oder die nun
verfügbare Referenzimplementierung.
Tests
Wie auch zu Teil A werden wieder Junit4-Tests zur Verfügung
gestellt, die Anhaltspunkte für die Korrektheit Ihrer
Implementierung liefern sollen. Sie finden die Testfälle in den
Klassen TestResourceManagerBasic.java und
TestResourceManagerMultiThreaded.java. Die ersten Testfälle
überprüfen dabei die Basisfunktionalität (wie z.B. das
Hinzufügen oder das Reservieren von Ressourcen) Ihrer
Implementierung im Single-Thread-Modus. Ergänzen Sie ggf. die
Klasse um weitere Testfälle. Die zweite Klasse testet Ihre
Implementierung mit mehreren parallel laufenden Transaktionen
(Threads). Unter anderem wird ein Deadlock provoziert.
Durability - Aufgabe bis zum 27.05.2010
-->
Update des Projekts
Erweitern Sie bis zum 27.05.2010 den ResourceManager um einen
Schattenspeichermechanismus, so dass nun auch die Dauerhaftigkeit der
Daten gewährleistet werden kann. Nutzen Sie dazu zwei Dateien auf
dem Hintergrundspeicher für die Datenbank. Eine Datei enthält
stets den aktuellen konsistenten Zustand der DB. In einer weiteren
Datei merken Sie sich einen Zeiger auf die Datei mit dem aktuellen
Zustand.
Beim Commit einer Transaktion wird die DB zunächst im
Hauptspeicher aktualisiert, dann in die Schattendatei kopiert und bei
erfolgreichem Schreiben der Zeiger auf die Schattendatei umgesetzt. Die
Schattendatei enthält nun den aktuellen konsistenten Zustand.
Um die korrekte Umsetzung testen zu können wurden dem Interface
IResourceManager die drei Methoden
public boolean dieNow()
public boolean dieBeforePointerSwitch()
public boolean dieAfterPointerSwitch()
hinzugefügt. Diese sollen durch Aufruf von System.exit() an
geeigneter Stelle einen unerwarteten Abbruch des RM simulieren. Die
genaue Semantik der Methoden entnehmen Sie bitte der JavaDoc des
Interfaces IResourceManager.
Ebenso wurde eine Konsolenapplikation zur Steuerung eines
ResourceManagers hinzugefügt. Hiermit können Sie über
die Konsole die Aktionen eines RM steuern. Testen Sie mit diesem
Werkzeug Ihre Implementierung auf Korrektheit. Den Umgang mit der
Applikation entnehmen Sie bitte wiederum der JavaDoc der Klasse.
Teil D: Resource Manager für remote
procedure calls (über RMI) verfügbar machen.
--> Abgabe bis zum 10.06.2010, 10 Uhr
Downloads:
-->
Eclipse-Projekt
Ergänzen Sie die vorgegebene Referenzimplementierung des Resource
Managers in der Art, dass dieser über remote procedure calls
verwendbar wird. Dazu müssen Sie hauptsächlich einige
Änderungem am Interface IResourceManager vornehmen.
Beachten Sie, dass das Interface IResourceManager in dieser Version um
eine Methode start(String workingDir) ergänzt wurde. Die
Referenzimplementiertung implementiert bereits die Methode auf
geeignete Art und Weise. Die Semantik der Methode ist den RM zu
starten. Vor Aufruf der Methode kann ein RM nicht genutzt werden.
Beachten Sie, dass nun zwei neue packages "server" und "client"
existieren. Im "server" package befinden sich 3 Klassen, die jeweils
nur eine main()-Methode enthalten. Die 3 Klassen sind dazu gedacht
einen RM für Hotels, einen für Flüge und einen für
Mietwagen zu starten. Ihre Aufgabe ist es die main()-Methode so zu
ergänzen, dass der jeweils erzeugte und gestartete RM in der RMI
registry registriert wird und so von Klienten gefunden werden kann.
Im package "client" befindet sich eine Konsolenanwendung, die Sie
nutzen können, um die remote verfügbar gemachten RMs zu
testen. Die Anwendung entspricht in etwa der bekannten Konsole aus Teil
B/C nur, dass diese um einen Befehl conn <RMI_NAME> erweitert
wurde. Nutzen Sie diesen Befehl, um sich mit einem bestimmten RM zu
verbinden. Ein RM mit dem sich verbunden werden soll muss
natürlich zuvor gestartet und bei der RMI registry eingetragen
worden sein.
Wichtig 1: Um einen RM starten zu können, müssen Sie der Java
Virtual Machine mitteilen, wo der kompilierte Code Ihrer
Implementierung zu finden ist. Ergänzen Sie dazu die
Eclipse-Run-Configuration um den folgenden VM-Parameter:
-Djava.rmi.server.codebase=file:<path_to_project>/classes/ (der
letzte Slash ist wichtig)
Schauen Sie dazu auch hier:
http://java.sun.com/docs/books/tutorial/rmi/running.html
Wichtig 2: Sie benötigen keinen Security Manager. Wenn Sie den
Beispielen aus dem Sun RMI Tutorial folgen, lassen Sie einfach alle
Aufrufe bzgl. des Security Managers weg.
Teil E: Travel Agency und Koordinierung
verteilter Transaktionen
mit 2PC
--> Abgabe bis zum 24.06.2010
Downloads:
-->
Eclipse-Projekt
-->
UPDATE
+ Konsole zum Testen
+ connectTM-Methode im RM
+ Locking aus RM entfernt, da stets nur ein Thread aktiv sein kann.
+ Copy&Paste-Fehler in Schnittstelle ITravelAgency verbessert
-->
API-Doku
Nachdem Sie erfolgreich Teil D des Projektes bearbeitet haben, sind
alle Resource Manager über Remote-Procedure-Calls erreichbar. Ihre
Aufgabe ist es nun ein Anwendungsprogramm für eine Reiseagentur zu
schreiben. In einer Reiseagentur kann man Hotels, Flüge und
Mietwagen buchen. Ebenso kann man auch direkt eine Pauschalreise
buchen. Da die einzelnen Ressourcen (Hotels, Flüge, Autos) von
unterschiedlichen, autonomenn Resource Managern verwaltet werden, wird
für eine Buchung (jeglicher Art) stets eine verteilte Transaktion
gestartet. Die geforderte Funktionalität der Reisagentur ist in
der Schnittstelle
ITravelAgency
spezifiziert.
Damit das Gesamtsystem überhaupt mit verteilten Transaktionen
unterstützen kann, bedarf es zum einen der Erweiterung der
Schnittstelle
IResourceManager
und zum anderen der Einführung eines Transaktionsmanagers (TM). Im
Rahmen des Projektes halten wir uns dazu in etwa an die
X/Open
Spezifikation bzgl. der Architektur eines verteilten
Transaktionsverarbeitungssystems (X/Open DTP).
Das Applikationsprogramm (AP) ist bei uns durch die Reiseagentur, also
durch das Interface
ITravelAgency
repräsentiert. Soll eine neue Buchung vorgenommen werden, so teilt
AP dies dem TM über einen Aufruf der Methode begin() (via RMI)
mit. Als Rückgabe erhält AP eine global eindeutige
Transaktions ID. Um nun eine Subtransaktion bei einem der RM zu starten
und diese der verteilten Transaktion zuzuordnen wird dort die Methode
beginDistributed(int
globalXID) aufgerufen. Der RM erhält also die ID der
verteilten Transaktion. Damit meldet er sich beim TM, um mitzuteilen,
dass er nun als Teilnehmer dieser verteilten Transaktion ist und bei
der Commit-Koordination einbezogen werden muss. Alle nachfolgende
Kommunikation zwischen AP
und TM findet unter Angabe der globalen Transaktions ID statt. Im RM
werden keine lokalen Transaktions IDs mehr generiert. Es gibt nur noch
verteilte Transaktionen und jede Transaktion wird unter Einbeziehung
des TM abgeschlossen. Im
folgenden ist der Nachrichtenfluss noch einmal schematisch dargestellt:
Ihre Aufgabe ist es den RM so zu erweitern, dass er verteilte
Transaktionen unterstützt und als Teilnehmer im 2PC Protokoll
fungieren kann. Dazu wurde die Schnittstelle
IResourceManger
um die beiden Methoden beginDistributed(int globalXID) und prepare(int
xid) ergänzt. Implementieren Sie die Methoden auf geeignete Weise.
Weiterhin ist es Ihre Aufgabe einen Transaktionsmanager zur
Verfügung zu stellen. Implementieren Sie dazu die Schnittstelle
ITransactionManager.
Beachten Sie dabei, dass die TM-Methoden über RMI aufrufbar sind.
Bei der Implementierung des 2PC-Protokolls können Sie
zunächst davon ausgehen, dass keine Fehler auftreten. Kümmern
Sie sich also
NICHT um
"forced writes" und Restart-/Timeout-Protokolle.
Implementieren Sie ebenso die Schnittstelle
ITravelAgency.
Abgabe: Implementierungen von
IResourceManager, ITransactionManager und ITravelAgency, sowie
zugehörige Hilfklassen als Quellcode in einem Jar-Archiv.
Teil F: Ergänzung des 2PC um
Restart-Protokolle
Downloads:
-->
Eclipse-Projekt
--> Abgabe am 08.07.2010
In Teil E des Projektes wurden Fehler vollkommen vernachlässigt.
Ihre Aufgabe ist es nun Ihre Implementierung robust gegenüber TM-
und RM-Ausfällen zu machen. Implementieren Sie dazu entsprechende
Restart-Protokolle beim TM und beim RM, die jeweils dann
ausgeführt werden, wenn ein Neustart der Komponente
durchgeführt wird. Timeout-Protokolle können Sie
vernachlässigen.
Sowohl die Schnittstellenbeschreibung des Resource Managers als auch
des Transaktions Managers wurden zu obigem Zwecke um eine Methode
restart()
erweitert. Die Implementierung des jeweiligen
Restart-Protokolls soll an dieser Stelle erfolgen.
Um den Ausfall des Transaktions Managers oder eines Resource Managers
zu simulieren wurden die Schnittstellenbeschreibungen zusätzlich
um Methoden der Art
dieBeforeXXX(),
dieAfterXXX()
ergänzt. Hier
soll jeweils ein Flag gesetzt werden, so dass die Komponente im
vorgegebenem Augenblick durch einen Aufruf von
System.exit() beendet
wird. Beim (manuellen) Neustart muss dann das jeweilige
Restart-Protokoll ausgeführt werden.
Beachten Sie, dass beim Neustart ggf. RMI-Objekte aus der Registry
erneut akquiriert werden müssen (z.B. in der TravelAgency).
Beachten Sie ebenso, dass ggf.
mehrere Komponenten gleichzeitig ausfallen können. Sie können
davon ausgehen, dass in der Reihenfolge des manuellen Neustarts
zuallerst immer der TM neugestartet wird.
Nutzen Sie zum Testen Ihrer Implementierung das
TravelAgencyTerminal, das nun um
Befehle erweitert wurde, um RM sowie TM zum Absturz zu bringen.
FAQ