Zum Inhalt
Zur Navigation

Beschreibung des Bildes
 

Projekte


Navigation:

  1. Teil A
  2. Teil B
  3. Teil B mit Durability (= Teil C)
  4. Teil D (RM RMI-fähig machen)
  5. Teil E (2PC ohne Fehlerbehandlung)
  6. 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:

  1. Eine Ressource wird über einen eindeutigen Namen identifiziert.

  2. 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).

  3. Ein Kunde kann eine Ressource nur genau einmal reservieren.

  4. 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).

X/Open DTP Architecture

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:

Nachrichtenfluss

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

© 2010 Freie Universität Berlin | Feedback |
Stand: 06.05.2010

News

21/06/2010: Update von Teil E
24/05/2010: Update der ResourceMangerConsole
21/05/2010: Teil B zweite Aufgabenstellung verfügbar
16/05/2010: Update der Testcases aufgrund eines Fehlers in test4

06/05/2010: Teil B verfügbar

29/04/2010: Testklassen für den LockManager sind verfügbar